feat: polish nightdrive road sun and signage

This commit is contained in:
2026-09-03 00:29:26 +03:00
parent bc18bc2043
commit a03d4d8dba
44 changed files with 1208 additions and 202 deletions

View File

@@ -101,16 +101,32 @@ describe('clock-driven sign lifecycle', () => {
expect(controller.needsFrame()).toBe(true);
});
it('cancels an unreadable sign on reverse and departs a readable sign cleanly', () => {
it('cancels a concealed sign on reverse and departs a visible approaching sign cleanly', () => {
const early = createSignController();
early.sample(sample());
expect(early.sample(sample({ now: 500, direction: -1, deltaY: -20 }))).toBeNull();
expect(early.sample(sample({ now: 310, direction: -1, deltaY: -20 }))).toBeNull();
expect(early.needsFrame()).toBe(false);
const readable = createSignController();
readable.sample(sample());
readable.sample(sample({ now: 1_200, direction: 0 }));
expect(readable.sample(sample({ now: 1_500, direction: -1, deltaY: -20 }))).toMatchObject({
expect(readable.sample(sample({ now: 700, direction: -1, deltaY: -20 }))).toMatchObject({
phase: 'depart',
progress: 0,
});
});
it('preserves the minimum readable dwell when reverse input requests departure', () => {
const controller = createSignController();
controller.sample(sample());
controller.sample(sample({ now: 1_200, direction: 0 }));
expect(controller.sample(sample({ now: 1_500, direction: -1, deltaY: -20 }))).toMatchObject({
phase: 'dwell',
});
expect(controller.sample(sample({ now: 2_599, direction: 0, deltaY: 0 }))).toMatchObject({
phase: 'dwell',
});
expect(controller.sample(sample({ now: 2_600, direction: 0, deltaY: 0 }))).toMatchObject({
phase: 'depart',
progress: 0,
});
@@ -132,18 +148,22 @@ describe('clock-driven sign lifecycle', () => {
it.each([
['multiple crossed sections', { crossedBoundaries: 2 }],
['a jump over 0.75 viewport heights', { deltaY: 601 }],
])('departs a readable stale sign cleanly after %s', (_label, overrides) => {
])('coalesces %s while preserving the readable dwell', (_label, overrides) => {
const controller = createSignController();
controller.sample(sample());
controller.sample(sample({ now: 1_200, direction: 0 }));
expect(controller.sample(sample({ now: 1_400, ...overrides }))).toMatchObject({
targetId: 'profile',
phase: 'dwell',
});
expect(controller.needsFrame()).toBe(true);
expect(controller.sample(sample({ now: 2_600, direction: 0, deltaY: 0 }))).toMatchObject({
targetId: 'profile',
phase: 'depart',
progress: 0,
opacity: 1,
});
expect(controller.needsFrame()).toBe(true);
});
it('departs when the authoritative section has already passed the latched target', () => {
@@ -161,6 +181,33 @@ describe('clock-driven sign lifecycle', () => {
});
});
it('does not truncate dwell when the authoritative section passes the target', () => {
const controller = createSignController();
controller.sample(sample());
controller.sample(sample({ now: 1_200, direction: 0 }));
expect(controller.sample(sample({
now: 1_350,
direction: 1,
activeSectionId: 'experience',
candidateId: 'infrastructure',
}))).toMatchObject({ targetId: 'profile', phase: 'dwell' });
expect(controller.sample(sample({
now: 2_599,
direction: 0,
deltaY: 0,
activeSectionId: 'experience',
candidateId: 'infrastructure',
}))).toMatchObject({ phase: 'dwell' });
expect(controller.sample(sample({
now: 2_600,
direction: 0,
deltaY: 0,
activeSectionId: 'experience',
candidateId: 'infrastructure',
}))).toMatchObject({ phase: 'depart', progress: 0 });
});
it('retargets a changed far-away candidate only while the sign is concealed', () => {
const controller = createSignController();
controller.sample(sample());