fix: refine responsive sign placement

This commit is contained in:
2026-09-03 18:34:39 +03:00
parent b075041918
commit 5dba071950
15 changed files with 388 additions and 17 deletions

View File

@@ -28,7 +28,7 @@ describe('clock-driven sign lifecycle', () => {
expect(SIGN_ROUTE_ORDER.map((id) => signRouteFor(id))).toEqual([
{ id: 'start', label: 'Start', marker: '01', eligible: false, corridor: null, triggerMaximumVh: 0 },
{ id: 'profile', label: 'Profile', marker: '02', eligible: true, corridor: 'left', triggerMaximumVh: 0.55 },
{ id: 'experience', label: 'Experience', marker: '03', eligible: true, corridor: 'left', triggerMaximumVh: 0.55 },
{ id: 'experience', label: 'Experience', marker: '03', eligible: true, corridor: 'right', triggerMaximumVh: 0.55 },
{ id: 'infrastructure', label: 'Infrastructure', marker: '04', eligible: true, corridor: 'outer-right', triggerMaximumVh: 0.55, dwellOffsetX: -1.6 },
{ id: 'selected-builds', label: 'Selected builds', marker: '05', eligible: true, corridor: 'right', triggerMaximumVh: 0.55 },
{ id: 'additional-work', label: 'Additional work', marker: '06', eligible: true, corridor: 'outer-left', triggerMaximumVh: 0.55 },

View File

@@ -724,7 +724,7 @@ describe("procedural world models", () => {
});
expect(face.userData.textureUpdateCount).toBe(2);
expect(sign.inspect(createChaseCamera(16 / 9), 1_440, 900)?.corridor).toBe("left");
expect(sign.inspect(createChaseCamera(16 / 9), 1_440, 900)?.corridor).toBe("right");
resources.dispose();
});
@@ -925,6 +925,48 @@ describe("procedural world models", () => {
resources.dispose();
});
it("grows a medium portrait navigator sign without enlarging the phone pose", () => {
const phoneResources = new SceneResourceBag();
const phoneSign = createSign(phoneResources);
const phoneCamera = createChaseCamera(390 / 844);
phoneSign.setViewportSize(390, 844);
phoneSign.update({
targetId: "profile",
phase: "dwell",
progress: 1,
opacity: 1,
navigationArrival: true,
});
const phone = phoneSign.inspect(phoneCamera, 390, 844);
if (phone === null) throw new Error("Missing phone navigator sign");
const mediumResources = new SceneResourceBag();
const mediumSign = createSign(mediumResources);
const mediumCamera = createChaseCamera(692 / 793);
mediumSign.setViewportSize(692, 793);
mediumSign.update({
targetId: "profile",
phase: "dwell",
progress: 1,
opacity: 1,
navigationArrival: true,
});
const medium = mediumSign.inspect(mediumCamera, 692, 793);
if (medium === null) throw new Error("Missing medium navigator sign");
expect(phone.bounds.width).toBeGreaterThanOrEqual(85);
expect(phone.bounds.width).toBeLessThanOrEqual(110);
expect(medium.viewportProfile).toBe("tall");
expect(medium.bounds.width).toBeGreaterThanOrEqual(145);
expect(medium.bounds.width).toBeLessThanOrEqual(210);
expect(medium.bounds.width).toBeGreaterThanOrEqual(phone.bounds.width * 1.45);
expect(medium.bounds.top).toBeLessThanOrEqual(555);
expect(medium.bounds.bottom).toBeLessThanOrEqual(590);
phoneResources.dispose();
mediumResources.dispose();
});
it("lands a resized Profile lifecycle in the short-landscape safe corridor", () => {
const resources = new SceneResourceBag();
const sign = createSign(resources);
@@ -961,10 +1003,26 @@ describe("procedural world models", () => {
expect(profileBounds.maxX).toBeLessThan(0.08);
sign.update(null);
sign.update({ targetId: "contact", phase: "dwell", progress: 1, opacity: 1 });
sign.update({ targetId: "experience", phase: "dwell", progress: 1, opacity: 1 });
sign.group.updateMatrixWorld(true);
const experienceBounds = projectedBounds(profileFace, camera);
const experiencePresentation = sign.inspect(camera, 1_440, 900);
expect(experienceBounds.minX).toBeGreaterThan(0);
expect(experiencePresentation?.corridor).toBe("right");
expect(experiencePresentation?.bounds.top).toBeGreaterThanOrEqual(540);
expect(experiencePresentation?.bounds.bottom).toBeLessThanOrEqual(630);
sign.update(null);
sign.setViewportSize(844, 390);
sign.update({ targetId: "experience", phase: "dwell", progress: 1, opacity: 1 });
const shortCamera = createChaseCamera(844 / 390);
shortCamera.position.set(-1.35, 8.6, 22);
shortCamera.lookAt(1.6, 4.8, -128);
const shortExperience = sign.inspect(shortCamera, 844, 390);
expect(shortExperience?.corridor).toBe("right");
expect(shortExperience?.bounds.left).toBeGreaterThan(422);
expect(shortExperience?.bounds.top).toBeGreaterThanOrEqual(210);
expect(shortExperience?.bounds.bottom).toBeLessThanOrEqual(300);
resources.dispose();
});
});