feat: integrate the cinematic nightdrive scene
This commit is contained in:
@@ -89,7 +89,7 @@ test('draws only the independently eligible next roadside sign during passive fo
|
||||
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||
const headingTop = profile.getBoundingClientRect().top + window.scrollY;
|
||||
const destination = Math.max(1, headingTop - viewportHeight * (0.35 + 0.8));
|
||||
return { destination, viewportHeight, expectedLabel: '01 / PROFILE' };
|
||||
return { destination, viewportHeight, expectedLabel: 'Profile' };
|
||||
});
|
||||
await page.evaluate(() => window.dispatchEvent(new WheelEvent('wheel', { deltaY: 1 })));
|
||||
for (let y = 1; y < oracle.destination; y += oracle.viewportHeight * 0.5) {
|
||||
|
||||
@@ -78,13 +78,23 @@ export async function installSceneInstrumentation(page: Page): Promise<void> {
|
||||
}, { once: true });
|
||||
|
||||
const nativeFillText = CanvasRenderingContext2D.prototype.fillText;
|
||||
const routeLabels = new Set([
|
||||
'Start',
|
||||
'Profile',
|
||||
'Experience',
|
||||
'Infrastructure',
|
||||
'Selected builds',
|
||||
'Additional work',
|
||||
'Toolkit',
|
||||
'Contact',
|
||||
]);
|
||||
CanvasRenderingContext2D.prototype.fillText = function fillText(
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
maxWidth?: number,
|
||||
): void {
|
||||
if (/^0[0-7] \/ /u.test(text)) state.routeLabelsDrawn.push(text);
|
||||
if (routeLabels.has(text)) state.routeLabelsDrawn.push(text);
|
||||
if (maxWidth === undefined) nativeFillText.call(this, text, x, y);
|
||||
else nativeFillText.call(this, text, x, y, maxWidth);
|
||||
};
|
||||
|
||||
@@ -192,9 +192,10 @@ describe('scene render quality', () => {
|
||||
});
|
||||
|
||||
it('keeps route sign labels canonical and compact', () => {
|
||||
expect(routeLabelFor('start')).toBe('00 / START');
|
||||
expect(routeLabelFor('infrastructure')).toBe('03 / INFRASTRUCTURE');
|
||||
expect(routeLabelFor('contact')).toBe('07 / DESTINATION');
|
||||
expect(routeLabelFor('start')).toBe('Start');
|
||||
expect(routeLabelFor('infrastructure')).toBe('Infrastructure');
|
||||
expect(routeLabelFor('selected-builds')).toBe('Selected builds');
|
||||
expect(routeLabelFor('contact')).toBe('Contact');
|
||||
});
|
||||
|
||||
it('keeps the chase car recognizably wide and fully inside the desktop view', () => {
|
||||
|
||||
@@ -15,6 +15,7 @@ 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', () => {
|
||||
@@ -299,6 +300,61 @@ describe('procedural world models', () => {
|
||||
expect(resources.snapshot()).toEqual(allocation);
|
||||
resources.dispose();
|
||||
});
|
||||
|
||||
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',
|
||||
]) {
|
||||
expect(sign.group.getObjectByName(name), name).toBeDefined();
|
||||
}
|
||||
const face = sign.group.getObjectByName('sign-face') as {
|
||||
userData: { textureUpdateCount: number };
|
||||
material: { opacity: number };
|
||||
};
|
||||
const allocation = resources.snapshot();
|
||||
|
||||
sign.update(null);
|
||||
expect((sign.group as { visible: boolean }).visible).toBe(false);
|
||||
sign.update({
|
||||
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');
|
||||
|
||||
sign.update({
|
||||
targetId: 'infrastructure',
|
||||
phase: 'approach',
|
||||
progress: 0.5,
|
||||
opacity: 0.7,
|
||||
});
|
||||
expect((sign.group as { visible: boolean }).visible).toBe(true);
|
||||
expect(face.userData.textureUpdateCount).toBe(1);
|
||||
expect(face.material.opacity).toBeCloseTo(0.7, 6);
|
||||
|
||||
const desktopX = (sign.group as { position: { x: number } }).position.x;
|
||||
sign.setViewportAspect(0.6);
|
||||
sign.update({
|
||||
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(resources.snapshot()).toEqual(allocation);
|
||||
resources.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
function paletteSnapshot(palette: ReturnType<typeof createMutableChapterPalette>) {
|
||||
|
||||
Reference in New Issue
Block a user