fix: close final portfolio quality gaps
This commit is contained in:
@@ -1,27 +1,27 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import * as THREE from 'three';
|
||||
import type { EnvironmentChapter } from '@/journey/types';
|
||||
import { renderQualityFor } from '@/scene/render-quality';
|
||||
import { SceneResourceBag } from '@/scene/resources';
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import * as THREE from "three";
|
||||
import type { EnvironmentChapter } from "@/journey/types";
|
||||
import { renderQualityFor } from "@/scene/render-quality";
|
||||
import { SceneResourceBag } from "@/scene/resources";
|
||||
import {
|
||||
CHAPTER_PALETTES,
|
||||
createMutableChapterPalette,
|
||||
writeChapterPalette,
|
||||
} from '@/scene/world/palette';
|
||||
import { createRoad } from '@/scene/world/road';
|
||||
import { createTerrain } from '@/scene/world/terrain';
|
||||
import { createSun } from '@/scene/world/sun';
|
||||
import { createAtmosphere } from '@/scene/world/atmosphere';
|
||||
import { createPostProcessor } from '@/scene/post-processing';
|
||||
import { createCar } from '@/scene/world/car';
|
||||
import { createChaseCamera } from '@/scene/world/environment';
|
||||
import { createSign, routeLabelFor } from '@/scene/world/sign';
|
||||
} from "@/scene/world/palette";
|
||||
import { createRoad } from "@/scene/world/road";
|
||||
import { createTerrain } from "@/scene/world/terrain";
|
||||
import { createSun } from "@/scene/world/sun";
|
||||
import { createAtmosphere } from "@/scene/world/atmosphere";
|
||||
import { createPostProcessor } from "@/scene/post-processing";
|
||||
import { createCar } from "@/scene/world/car";
|
||||
import { createChaseCamera } from "@/scene/world/environment";
|
||||
import { createSign, routeLabelFor } from "@/scene/world/sign";
|
||||
|
||||
describe('procedural world models', () => {
|
||||
it('scales geometry and atmosphere work monotonically by live quality tier', () => {
|
||||
const high = renderQualityFor('high', 3, false);
|
||||
const medium = renderQualityFor('medium', 3, false);
|
||||
const low = renderQualityFor('low', 3, false);
|
||||
describe("procedural world models", () => {
|
||||
it("scales geometry and atmosphere work monotonically by live quality tier", () => {
|
||||
const high = renderQualityFor("high", 3, false);
|
||||
const medium = renderQualityFor("medium", 3, false);
|
||||
const low = renderQualityFor("low", 3, false);
|
||||
|
||||
expect(high.roadSegments).toBeGreaterThan(medium.roadSegments);
|
||||
expect(medium.roadSegments).toBeGreaterThan(low.roadSegments);
|
||||
@@ -31,50 +31,60 @@ describe('procedural world models', () => {
|
||||
expect(low.postEffects).toBe(false);
|
||||
});
|
||||
|
||||
it('builds a shader road whose motion is entirely uniform-driven', () => {
|
||||
it("builds a shader road whose motion is entirely uniform-driven", () => {
|
||||
const resources = new SceneResourceBag();
|
||||
const road = createRoad(resources, renderQualityFor('high', 1, false));
|
||||
const surface = road.group.getObjectByName('road-surface') as ShaderRenderable;
|
||||
const road = createRoad(resources, renderQualityFor("high", 1, false));
|
||||
const surface = road.group.getObjectByName(
|
||||
"road-surface",
|
||||
) as ShaderRenderable;
|
||||
|
||||
expect(surface).toBeInstanceOf(THREE.Mesh);
|
||||
expect(surface.geometry.index).not.toBeNull();
|
||||
expect(surface.geometry.getAttribute('position').count).toBeGreaterThan(1_000);
|
||||
expect(surface.geometry.getAttribute("position").count).toBeGreaterThan(
|
||||
1_000,
|
||||
);
|
||||
expect(surface.material.uniforms).toMatchObject({
|
||||
uDistance: { value: 0 },
|
||||
uSpeed: { value: 0 },
|
||||
uAcceleration: { value: 0 },
|
||||
});
|
||||
expect(surface.material.fragmentShader).toContain('centerLane');
|
||||
expect(surface.material.fragmentShader).toContain('outerEdge');
|
||||
expect(surface.material.fragmentShader).toContain("centerLane");
|
||||
expect(surface.material.fragmentShader).toContain("outerEdge");
|
||||
|
||||
const positionArray = surface.geometry.getAttribute('position').array;
|
||||
const positionArray = surface.geometry.getAttribute("position").array;
|
||||
const before = resources.snapshot();
|
||||
for (let index = 0; index < 300; index += 1) {
|
||||
road.update(index * 0.5, 0.65, 0.4);
|
||||
}
|
||||
|
||||
expect(resources.snapshot()).toEqual(before);
|
||||
expect(surface.geometry.getAttribute('position').array).toBe(positionArray);
|
||||
expect(surface.geometry.getAttribute("position").array).toBe(positionArray);
|
||||
expect(surface.material.uniforms.uDistance?.value).toBe(149.5);
|
||||
expect(surface.material.uniforms.uSpeed?.value).toBe(0.65);
|
||||
expect(surface.material.uniforms.uAcceleration?.value).toBe(0.4);
|
||||
resources.dispose();
|
||||
});
|
||||
|
||||
it('builds one wide indexed line terrain with a flat road-center mask', () => {
|
||||
it("builds one wide indexed line terrain with a flat road-center mask", () => {
|
||||
const resources = new SceneResourceBag();
|
||||
const terrain = createTerrain(resources, renderQualityFor('medium', 1, false));
|
||||
const lines = terrain.group.getObjectByName('terrain-lines') as ShaderRenderable;
|
||||
const terrain = createTerrain(
|
||||
resources,
|
||||
renderQualityFor("medium", 1, false),
|
||||
);
|
||||
const lines = terrain.group.getObjectByName(
|
||||
"terrain-lines",
|
||||
) as ShaderRenderable;
|
||||
|
||||
expect(lines).toBeInstanceOf(THREE.LineSegments);
|
||||
expect(lines.geometry.index).not.toBeNull();
|
||||
lines.geometry.computeBoundingBox();
|
||||
expect(lines.geometry.boundingBox).not.toBeNull();
|
||||
expect(
|
||||
(lines.geometry.boundingBox?.max.x ?? 0) - (lines.geometry.boundingBox?.min.x ?? 0),
|
||||
(lines.geometry.boundingBox?.max.x ?? 0) -
|
||||
(lines.geometry.boundingBox?.min.x ?? 0),
|
||||
).toBeGreaterThan(120);
|
||||
|
||||
const roadMask = lines.geometry.getAttribute('roadMask');
|
||||
const roadMask = lines.geometry.getAttribute("roadMask");
|
||||
expect(roadMask).toBeDefined();
|
||||
const maskValues = Array.from(roadMask.array as ArrayLike<number>);
|
||||
expect(Math.min(...maskValues)).toBe(0);
|
||||
@@ -84,28 +94,28 @@ describe('procedural world models', () => {
|
||||
uSpeed: { value: 0 },
|
||||
uRouteProgress: { value: 0 },
|
||||
});
|
||||
expect(lines.material.vertexShader).toContain('multiOctaveNoise');
|
||||
expect(lines.material.vertexShader).toContain("multiOctaveNoise");
|
||||
|
||||
const indexArray = lines.geometry.index?.array;
|
||||
const positionArray = lines.geometry.getAttribute('position').array;
|
||||
const positionArray = lines.geometry.getAttribute("position").array;
|
||||
const lowColor = lines.material.uniforms.uLowColor?.value;
|
||||
const highColor = lines.material.uniforms.uHighColor?.value;
|
||||
const before = resources.snapshot();
|
||||
for (let index = 0; index < 300; index += 1) {
|
||||
terrain.update(index * 0.4, 0.7, 'builds', index / 299);
|
||||
terrain.update(index * 0.4, 0.7, "builds", index / 299);
|
||||
}
|
||||
|
||||
expect(resources.snapshot()).toEqual(before);
|
||||
expect(lines.geometry.index?.array).toBe(indexArray);
|
||||
expect(lines.geometry.getAttribute('position').array).toBe(positionArray);
|
||||
expect(lines.geometry.getAttribute("position").array).toBe(positionArray);
|
||||
expect(lines.material.uniforms.uLowColor?.value).toBe(lowColor);
|
||||
expect(lines.material.uniforms.uHighColor?.value).toBe(highColor);
|
||||
expect(lines.material.uniforms.uRouteProgress?.value).toBe(1);
|
||||
resources.dispose();
|
||||
});
|
||||
|
||||
it('writes deterministic chapter blends into stable color allocations', () => {
|
||||
const target = createMutableChapterPalette('deep-night');
|
||||
it("writes deterministic chapter blends into stable color allocations", () => {
|
||||
const target = createMutableChapterPalette("deep-night");
|
||||
const colorReferences = [
|
||||
target.sky,
|
||||
target.fog,
|
||||
@@ -116,16 +126,20 @@ describe('procedural world models', () => {
|
||||
];
|
||||
|
||||
expect(Object.keys(CHAPTER_PALETTES)).toEqual([
|
||||
'deep-night',
|
||||
'systems',
|
||||
'infrastructure',
|
||||
'builds',
|
||||
'pre-dawn',
|
||||
'destination',
|
||||
"deep-night",
|
||||
"systems",
|
||||
"infrastructure",
|
||||
"builds",
|
||||
"pre-dawn",
|
||||
"destination",
|
||||
] satisfies EnvironmentChapter[]);
|
||||
expect(writeChapterPalette(target, 'deep-night', 'destination', 0.5)).toBe(target);
|
||||
expect(writeChapterPalette(target, "deep-night", "destination", 0.5)).toBe(
|
||||
target,
|
||||
);
|
||||
const first = paletteSnapshot(target);
|
||||
expect(writeChapterPalette(target, 'deep-night', 'destination', 0.5)).toBe(target);
|
||||
expect(writeChapterPalette(target, "deep-night", "destination", 0.5)).toBe(
|
||||
target,
|
||||
);
|
||||
expect(paletteSnapshot(target)).toEqual(first);
|
||||
expect([
|
||||
target.sky,
|
||||
@@ -135,62 +149,90 @@ describe('procedural world models', () => {
|
||||
target.sun,
|
||||
target.haze,
|
||||
]).toEqual(colorReferences);
|
||||
expect(target.warmth).toBeGreaterThan(CHAPTER_PALETTES['deep-night'].warmth);
|
||||
expect(target.warmth).toBeGreaterThan(
|
||||
CHAPTER_PALETTES["deep-night"].warmth,
|
||||
);
|
||||
expect(target.warmth).toBeLessThan(CHAPTER_PALETTES.destination.warmth);
|
||||
});
|
||||
|
||||
it('renders the sun as a scan-banded disc with a separately owned halo', () => {
|
||||
it("renders the sun as a scan-banded disc with a separately owned halo", () => {
|
||||
const resources = new SceneResourceBag();
|
||||
const sun = createSun(resources);
|
||||
const disc = sun.group.getObjectByName('sun-disc') as ShaderRenderable;
|
||||
const halo = sun.group.getObjectByName('sun-halo');
|
||||
const disc = sun.group.getObjectByName("sun-disc") as ShaderRenderable;
|
||||
const halo = sun.group.getObjectByName("sun-halo");
|
||||
|
||||
expect(disc).toBeInstanceOf(THREE.Mesh);
|
||||
expect(halo).toBeInstanceOf(THREE.Sprite);
|
||||
expect(disc.material.uniforms).toMatchObject({
|
||||
uScanBands: { value: 14 },
|
||||
uWarmth: { value: CHAPTER_PALETTES['deep-night'].warmth },
|
||||
uWarmth: { value: CHAPTER_PALETTES["deep-night"].warmth },
|
||||
});
|
||||
expect(disc.material.fragmentShader).toContain('scanBand');
|
||||
expect(disc.material.fragmentShader).toContain("scanBand");
|
||||
expect(resources.snapshot().owned.texture).toBe(1);
|
||||
|
||||
const palette = createMutableChapterPalette('destination');
|
||||
const palette = createMutableChapterPalette("destination");
|
||||
sun.update(palette, 1);
|
||||
expect(disc.material.uniforms.uWarmth?.value).toBe(1);
|
||||
expect(disc.material.uniforms.uRouteProgress?.value).toBe(1);
|
||||
resources.dispose();
|
||||
});
|
||||
|
||||
it('builds deterministic stars, haze layers, and quality-capped speed streaks', () => {
|
||||
it("builds deterministic stars, haze layers, and quality-capped speed streaks", () => {
|
||||
const highResources = new SceneResourceBag();
|
||||
const lowResources = new SceneResourceBag();
|
||||
const highQuality = renderQualityFor('high', 1, false);
|
||||
const lowQuality = renderQualityFor('low', 1, true);
|
||||
const highQuality = renderQualityFor("high", 1, false);
|
||||
const lowQuality = renderQualityFor("low", 1, true);
|
||||
const high = createAtmosphere(highResources, highQuality);
|
||||
const low = createAtmosphere(lowResources, lowQuality);
|
||||
const highStars = high.group.getObjectByName('atmosphere-stars') as ShaderRenderable;
|
||||
const lowStars = low.group.getObjectByName('atmosphere-stars') as ShaderRenderable;
|
||||
const highStreaks = high.group.getObjectByName('atmosphere-streaks') as ShaderRenderable;
|
||||
const lowStreaks = low.group.getObjectByName('atmosphere-streaks') as ShaderRenderable;
|
||||
const highStars = high.group.getObjectByName(
|
||||
"atmosphere-stars",
|
||||
) as ShaderRenderable;
|
||||
const lowStars = low.group.getObjectByName(
|
||||
"atmosphere-stars",
|
||||
) as ShaderRenderable;
|
||||
const highStreaks = high.group.getObjectByName(
|
||||
"atmosphere-streaks",
|
||||
) as ShaderRenderable;
|
||||
const lowStreaks = low.group.getObjectByName(
|
||||
"atmosphere-streaks",
|
||||
) as ShaderRenderable;
|
||||
|
||||
expect(highStars.geometry.getAttribute('position').count).toBe(highQuality.starCount);
|
||||
expect(lowStars.geometry.getAttribute('position').count).toBe(lowQuality.starCount);
|
||||
expect(highStars.geometry.getAttribute("position").count).toBe(
|
||||
highQuality.starCount,
|
||||
);
|
||||
expect(lowStars.geometry.getAttribute("position").count).toBe(
|
||||
lowQuality.starCount,
|
||||
);
|
||||
expect(highStars.material.uniforms).toMatchObject({
|
||||
uPointScale: { value: 18 },
|
||||
uMaxPointSize: { value: 4.5 },
|
||||
});
|
||||
expect(highStreaks.geometry.getAttribute('position').count).toBe(highQuality.streakCount * 2);
|
||||
expect(lowStreaks.geometry.getAttribute('position').count).toBe(lowQuality.streakCount * 2);
|
||||
expect(high.group.getObjectByName('horizon-haze-near')).toBeInstanceOf(THREE.Mesh);
|
||||
expect(high.group.getObjectByName('horizon-haze-far')).toBeInstanceOf(THREE.Mesh);
|
||||
expect(highStreaks.geometry.getAttribute("position").count).toBe(
|
||||
highQuality.streakCount * 2,
|
||||
);
|
||||
expect(lowStreaks.geometry.getAttribute("position").count).toBe(
|
||||
lowQuality.streakCount * 2,
|
||||
);
|
||||
expect(high.group.getObjectByName("horizon-haze-near")).toBeInstanceOf(
|
||||
THREE.Mesh,
|
||||
);
|
||||
expect(high.group.getObjectByName("horizon-haze-far")).toBeInstanceOf(
|
||||
THREE.Mesh,
|
||||
);
|
||||
|
||||
const highStarPositions = Array.from(highStars.geometry.getAttribute('position').array);
|
||||
const highStarPositions = Array.from(
|
||||
highStars.geometry.getAttribute("position").array,
|
||||
);
|
||||
const secondResources = new SceneResourceBag();
|
||||
const secondHigh = createAtmosphere(secondResources, highQuality);
|
||||
const secondStars = secondHigh.group.getObjectByName('atmosphere-stars') as ShaderRenderable;
|
||||
expect(Array.from(secondStars.geometry.getAttribute('position').array)).toEqual(highStarPositions);
|
||||
const secondStars = secondHigh.group.getObjectByName(
|
||||
"atmosphere-stars",
|
||||
) as ShaderRenderable;
|
||||
expect(
|
||||
Array.from(secondStars.geometry.getAttribute("position").array),
|
||||
).toEqual(highStarPositions);
|
||||
|
||||
const palette = createMutableChapterPalette('builds');
|
||||
const palette = createMutableChapterPalette("builds");
|
||||
const before = highResources.snapshot();
|
||||
for (let index = 0; index < 300; index += 1) {
|
||||
high.update(index, 0.7, 0.8, palette);
|
||||
@@ -204,25 +246,70 @@ describe('procedural world models', () => {
|
||||
secondResources.dispose();
|
||||
});
|
||||
|
||||
it('disposes a composer and its passes exactly once', () => {
|
||||
it("disposes a composer and its passes exactly once", () => {
|
||||
const resources = new SceneResourceBag();
|
||||
const target = (width: number, height: number, depthBuffer: boolean) => ({
|
||||
width,
|
||||
height,
|
||||
depthBuffer,
|
||||
samples: 1,
|
||||
dispose: vi.fn(),
|
||||
});
|
||||
const composer = {
|
||||
addPass: vi.fn(),
|
||||
setPixelRatio: vi.fn(),
|
||||
setSize: vi.fn(),
|
||||
render: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
renderTarget1: target(1_500, 1_000, true),
|
||||
renderTarget2: target(1_500, 1_000, true),
|
||||
};
|
||||
const renderPass = { scene: null, camera: null, setSize: vi.fn(), dispose: vi.fn() };
|
||||
const bloomPass = { setSize: vi.fn(), dispose: vi.fn() };
|
||||
const processor = createPostProcessor(resources, {}, {
|
||||
createComposer: () => composer,
|
||||
createRenderPass: () => renderPass,
|
||||
createBloomPass: () => bloomPass,
|
||||
});
|
||||
const renderPass = {
|
||||
scene: null,
|
||||
camera: null,
|
||||
setSize: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
};
|
||||
const bloomPass = {
|
||||
setSize: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
renderTargetBright: target(750, 500, false),
|
||||
renderTargetsHorizontal: [target(375, 250, false)],
|
||||
renderTargetsVertical: [target(375, 250, false)],
|
||||
};
|
||||
const processor = createPostProcessor(
|
||||
resources,
|
||||
{},
|
||||
{
|
||||
createComposer: () => composer,
|
||||
createRenderPass: () => renderPass,
|
||||
createBloomPass: () => bloomPass,
|
||||
},
|
||||
);
|
||||
|
||||
processor.resize(1_200, 800, 1.25);
|
||||
processor.render({}, {});
|
||||
const allocations = resources.gpuBudgetSnapshot({
|
||||
tier: "high",
|
||||
defaultFramebuffer: {
|
||||
width: 1_500,
|
||||
height: 1_000,
|
||||
bytesPerPixel: 4,
|
||||
attachments: 2,
|
||||
samples: 4,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
allocations.renderTargets.map((allocation) => allocation.id),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
"composer-target-1",
|
||||
"composer-target-2",
|
||||
"bloom-bright",
|
||||
"bloom-horizontal-0",
|
||||
"bloom-vertical-0",
|
||||
]),
|
||||
);
|
||||
processor.dispose();
|
||||
processor.dispose();
|
||||
resources.dispose();
|
||||
@@ -236,19 +323,19 @@ describe('procedural world models', () => {
|
||||
expect(composer.dispose).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('keeps the grand tourer anatomy readable in desktop and portrait chase views', () => {
|
||||
it("keeps the grand tourer anatomy readable in desktop and portrait chase views", () => {
|
||||
const resources = new SceneResourceBag();
|
||||
const car = createCar(resources);
|
||||
for (const name of [
|
||||
'rear-silhouette',
|
||||
'rear-body-face',
|
||||
'rear-light-bar',
|
||||
'rear-glass',
|
||||
'rear-glass-panel',
|
||||
'rear-fascia',
|
||||
'rear-wheel-left',
|
||||
'rear-wheel-right',
|
||||
'car-underglow',
|
||||
"rear-silhouette",
|
||||
"rear-body-face",
|
||||
"rear-light-bar",
|
||||
"rear-glass",
|
||||
"rear-glass-panel",
|
||||
"rear-fascia",
|
||||
"rear-wheel-left",
|
||||
"rear-wheel-right",
|
||||
"car-underglow",
|
||||
]) {
|
||||
expect(car.group.getObjectByName(name), name).toBeDefined();
|
||||
}
|
||||
@@ -262,23 +349,26 @@ describe('procedural world models', () => {
|
||||
expect(desktop.minY).toBeGreaterThan(-0.99);
|
||||
expect(desktop.maxY).toBeLessThan(-0.05);
|
||||
|
||||
car.setDetail('reduced');
|
||||
car.setDetail("reduced");
|
||||
const portrait = projectedBounds(car.group, createChaseCamera(390 / 844));
|
||||
expect(portrait.minX).toBeGreaterThan(-0.99);
|
||||
expect(portrait.maxX).toBeLessThan(0.99);
|
||||
expect(portrait.minY).toBeGreaterThan(-0.99);
|
||||
expect(portrait.maxY).toBeLessThan(0.16);
|
||||
expect((car.group.getObjectByName('secondary-detail') as { visible: boolean }).visible).toBe(false);
|
||||
expect(
|
||||
(car.group.getObjectByName("secondary-detail") as { visible: boolean })
|
||||
.visible,
|
||||
).toBe(false);
|
||||
resources.dispose();
|
||||
});
|
||||
|
||||
it('damps squat and light response without allocating or changing the scene graph', () => {
|
||||
it("damps squat and light response without allocating or changing the scene graph", () => {
|
||||
const resources = new SceneResourceBag();
|
||||
const car = createCar(resources);
|
||||
const light = car.group.getObjectByName('rear-light-bar') as {
|
||||
const light = car.group.getObjectByName("rear-light-bar") as {
|
||||
material: { emissiveIntensity: number };
|
||||
};
|
||||
const underglow = car.group.getObjectByName('car-underglow') as {
|
||||
const underglow = car.group.getObjectByName("car-underglow") as {
|
||||
material: { uniforms: Record<string, { value: number }> };
|
||||
};
|
||||
car.update(0.3, 0, 0);
|
||||
@@ -292,28 +382,36 @@ describe('procedural world models', () => {
|
||||
car.update(0.92, 1, frame / 60);
|
||||
}
|
||||
|
||||
expect((car.group as { position: { y: number } }).position.y).toBeLessThan(restingY);
|
||||
expect((car.group as { position: { y: number } }).position.y).toBeLessThan(
|
||||
restingY,
|
||||
);
|
||||
expect(light.material.emissiveIntensity).toBeGreaterThan(restingLight);
|
||||
expect(underglow.material.uniforms.uOpacity?.value).toBeGreaterThan(restingGlow);
|
||||
expect(Math.abs((car.group as { rotation: { z: number } }).rotation.z)).toBeLessThanOrEqual(0.006);
|
||||
expect((car.group as { children: unknown[] }).children).toHaveLength(childCount);
|
||||
expect(underglow.material.uniforms.uOpacity?.value).toBeGreaterThan(
|
||||
restingGlow,
|
||||
);
|
||||
expect(
|
||||
Math.abs((car.group as { rotation: { z: number } }).rotation.z),
|
||||
).toBeLessThanOrEqual(0.006);
|
||||
expect((car.group as { children: unknown[] }).children).toHaveLength(
|
||||
childCount,
|
||||
);
|
||||
expect(resources.snapshot()).toEqual(allocation);
|
||||
resources.dispose();
|
||||
});
|
||||
|
||||
it('renders one physical roadside sign and updates its plain label once per target', () => {
|
||||
it("renders one physical roadside sign and updates its plain label once per target", () => {
|
||||
const resources = new SceneResourceBag();
|
||||
const sign = createSign(resources);
|
||||
for (const name of [
|
||||
'sign-backplate',
|
||||
'sign-face',
|
||||
'sign-frame',
|
||||
'sign-post-left',
|
||||
'sign-post-right',
|
||||
"sign-backplate",
|
||||
"sign-face",
|
||||
"sign-frame",
|
||||
"sign-post-left",
|
||||
"sign-post-right",
|
||||
]) {
|
||||
expect(sign.group.getObjectByName(name), name).toBeDefined();
|
||||
}
|
||||
const face = sign.group.getObjectByName('sign-face') as {
|
||||
const face = sign.group.getObjectByName("sign-face") as {
|
||||
userData: { textureUpdateCount: number };
|
||||
material: { opacity: number };
|
||||
};
|
||||
@@ -322,18 +420,18 @@ describe('procedural world models', () => {
|
||||
sign.update(null);
|
||||
expect((sign.group as { visible: boolean }).visible).toBe(false);
|
||||
sign.update({
|
||||
targetId: 'infrastructure',
|
||||
phase: 'anticipate',
|
||||
targetId: "infrastructure",
|
||||
phase: "anticipate",
|
||||
progress: 0,
|
||||
opacity: 0,
|
||||
});
|
||||
expect((sign.group as { visible: boolean }).visible).toBe(false);
|
||||
expect(face.userData.textureUpdateCount).toBe(1);
|
||||
expect(routeLabelFor('infrastructure')).toBe('Infrastructure');
|
||||
expect(routeLabelFor("infrastructure")).toBe("Infrastructure");
|
||||
|
||||
sign.update({
|
||||
targetId: 'infrastructure',
|
||||
phase: 'approach',
|
||||
targetId: "infrastructure",
|
||||
phase: "approach",
|
||||
progress: 0.5,
|
||||
opacity: 0.7,
|
||||
});
|
||||
@@ -344,20 +442,24 @@ describe('procedural world models', () => {
|
||||
const desktopX = (sign.group as { position: { x: number } }).position.x;
|
||||
sign.setViewportAspect(0.6);
|
||||
sign.update({
|
||||
targetId: 'selected-builds',
|
||||
phase: 'dwell',
|
||||
targetId: "selected-builds",
|
||||
phase: "dwell",
|
||||
progress: 1,
|
||||
opacity: 1,
|
||||
});
|
||||
expect(face.userData.textureUpdateCount).toBe(2);
|
||||
expect(routeLabelFor('selected-builds')).toBe('Selected builds');
|
||||
expect((sign.group as { position: { x: number } }).position.x).toBeLessThan(desktopX);
|
||||
expect(routeLabelFor("selected-builds")).toBe("Selected builds");
|
||||
expect((sign.group as { position: { x: number } }).position.x).toBeLessThan(
|
||||
desktopX,
|
||||
);
|
||||
expect(resources.snapshot()).toEqual(allocation);
|
||||
resources.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
function paletteSnapshot(palette: ReturnType<typeof createMutableChapterPalette>) {
|
||||
function paletteSnapshot(
|
||||
palette: ReturnType<typeof createMutableChapterPalette>,
|
||||
) {
|
||||
return {
|
||||
sky: palette.sky.getHex(),
|
||||
fog: palette.fog.getHex(),
|
||||
|
||||
Reference in New Issue
Block a user