polish: time the scene payoffs to the gaps between cards

One shared window (journey/card-gaps.ts, CARD_GAP) describes the open road
after each chapter's panel in section-local progress; the district relight
and cutover, the last yard lift and the relay station's beams now key off
it instead of their own numbers, and the chapter margin widens from 44vh
to 48vh so the gap reads as a stretch of road. Unit tests prove every
payoff's peak sits inside the window for every chapter span.
This commit is contained in:
2026-10-12 19:20:43 +03:00
parent c6ce7c24f5
commit 75108562a4
15 changed files with 193 additions and 28 deletions

View File

@@ -17,6 +17,7 @@ import {
YARD_STOPS,
yardStopsPassed,
} from '@/journey/yard-progress';
import { CARD_GAP } from '@/journey/card-gaps';
import { DECK_PASS_Z } from '@/journey/underpass';
import { UNDERPASS_GEOMETRY } from '@/scene/world/underpass';
@@ -24,7 +25,7 @@ const sweep = (steps = 400): number[] => Array.from({ length: steps + 1 }, (_, i
describe('night freight progress', () => {
it('has four ordered stops and stacks far to near', () => {
expect(YARD_STOPS).toEqual([0.1, 0.33, 0.56, 0.79]);
expect(YARD_STOPS).toEqual([0.12, 0.38, 0.64, CARD_GAP.peak]);
for (let i = 1; i < YARD_STOPS.length; i += 1) {
expect(YARD_STOPS[i]!).toBeGreaterThan(YARD_STOPS[i - 1]! + 2 * YARD_HOLD);
expect(STACK_ZS[i]!).toBeGreaterThan(STACK_ZS[i - 1]!);
@@ -39,7 +40,9 @@ describe('night freight progress', () => {
previous = z;
}
expect(craneZ(0)).toBeCloseTo(CRANE_FROM_Z);
expect(craneZ(1)).toBeCloseTo(CRANE_TO_Z);
// The last lift is the chapter's final beat (CARD_GAP): the crane stays parked over the near stack to the boundary.
expect(craneZ(1)).toBeCloseTo(STACK_ZS[STACK_ZS.length - 1]!);
expect(CRANE_TO_Z).toBeGreaterThan(STACK_ZS[STACK_ZS.length - 1]!);
YARD_STOPS.forEach((stop, i) => {
for (const d of [-YARD_HOLD, -0.02, 0, 0.02, YARD_HOLD]) expect(craneZ(stop + d)).toBeCloseTo(STACK_ZS[i]!);
});
@@ -63,10 +66,10 @@ describe('night freight progress', () => {
expect(plateLit(stop - 0.05, i)).toBe(0);
});
expect(spreaderDrop(0.215)).toBe(0);
expect(spreaderDrop(0.95)).toBe(0);
expect(spreaderDrop(0.51)).toBe(0);
expect(plateLit(null, 0)).toBe(0);
expect(plateLit(0.5, 9)).toBe(0);
expect([0, 0.1, 0.2, 0.33, 0.6, 0.8, 1].map(yardStopsPassed)).toEqual([0, 1, 1, 2, 3, 4, 4]);
expect([0, 0.12, 0.2, 0.38, 0.7, 0.95, 1].map(yardStopsPassed)).toEqual([0, 1, 1, 2, 3, 4, 4]);
});
it('is symmetric in reverse: the same progress gives the same frame either way', () => {