polish: scene nits — live-lens yard, square stacks, deck-clear billboard, two-layer lamps, flare reach, phone traffic and exposure
This commit is contained in:
@@ -15,6 +15,10 @@ import {
|
||||
INDICATOR_HZ,
|
||||
TRAFFIC_ASPHALT_HALF_WIDTH,
|
||||
TRAFFIC_BEHIND,
|
||||
TRAFFIC_CONVOY_GAP_MAX,
|
||||
TRAFFIC_LITE_CONVOY_NEAR_Z,
|
||||
TRAFFIC_LITE_DROUGHT_SECONDS,
|
||||
TRAFFIC_LITE_STAGE_SECONDS,
|
||||
TRAFFIC_DENSITY,
|
||||
TRAFFIC_ENTRY_FAR_Z,
|
||||
TRAFFIC_ENTRY_NEAR_Z,
|
||||
@@ -122,6 +126,8 @@ describe('traffic model: long randomised drives', () => {
|
||||
hero += (target - hero) * Math.min(1, dt * 3);
|
||||
if (time >= nextChapter) {
|
||||
step.chapter = CHAPTERS[Math.floor(random() * CHAPTERS.length)]!;
|
||||
// A chapter entry: lite stages a pass (the invariants hold through it).
|
||||
step.cue = true;
|
||||
nextChapter = time + 20 + random() * 40;
|
||||
if (random() < 0.4) sweepUntil = time + 0.5 + random();
|
||||
}
|
||||
@@ -134,6 +140,7 @@ describe('traffic model: long randomised drives', () => {
|
||||
step.cameraX = Math.sin(time * 0.05) * 5.6;
|
||||
step.dt = dt;
|
||||
const f = model.step(step);
|
||||
step.cue = false;
|
||||
const problem = violation(f);
|
||||
if (problem) throw new Error(`seed ${seed} t ${time.toFixed(2)}: ${problem}`);
|
||||
const events = model.events();
|
||||
@@ -489,7 +496,8 @@ describe('traffic model: the interaction is in frame', () => {
|
||||
const f = model.step(step);
|
||||
for (let i = 0; i < f.count; i += 1) {
|
||||
const x = f.active[i] === TRAFFIC_VISIBLE ? f.x[i]! : 9;
|
||||
if (prev[i]! > 3 && x <= 3) entries.push(f.z[i]!);
|
||||
// A pull-out from the right lane (not a pooled slot reappearing in another lane).
|
||||
if (prev[i]! > 3 && prev[i]! !== 9 && x <= 3) entries.push(f.z[i]!);
|
||||
if (Math.abs(prev[i]!) <= 3 && Math.abs(x) > 3 && x !== 9) exits.push(f.z[i]!);
|
||||
prev[i] = x;
|
||||
}
|
||||
@@ -532,8 +540,11 @@ describe('traffic model: the interaction is in frame', () => {
|
||||
seen[i] = 1;
|
||||
if (t === 0) continue;
|
||||
if (f.z[i]! > TRAFFIC_ENTRY_FAR_Z + 1 && f.z[i]! < TRAFFIC_EXIT_NOSE_Z - 1) {
|
||||
// Nearer than the far entry: screen-doored in, still in the haze.
|
||||
expect(f.z[i]!, `vehicle ${i} at ${t.toFixed(1)} s`).toBeLessThan(TRAFFIC_ENTRY_NEAR_Z + 2);
|
||||
// Nearer than the far entry: screen-doored in, at the haze. Lite
|
||||
// queues its convoy car (and stages its truck) a little nearer:
|
||||
// the rear of a car behind a truck at the window's near edge.
|
||||
const liteNear = TRAFFIC_LITE_CONVOY_NEAR_Z + TRAFFIC_KINDS[0]!.length + TRAFFIC_CONVOY_GAP_MAX;
|
||||
expect(f.z[i]!, `vehicle ${i} at ${t.toFixed(1)} s`).toBeLessThan(Math.max(TRAFFIC_ENTRY_NEAR_Z, liteNear) + 2);
|
||||
expect(f.z[i]!).toBeGreaterThan(TRAFFIC_ENTRY_NEAR_Z - TRAFFIC_ENTRY_NEAR_SPAN - 50);
|
||||
expect(f.dither[i]!).toBeGreaterThan(0.9);
|
||||
fading[i] = t;
|
||||
@@ -614,3 +625,79 @@ describe('flash detector', () => {
|
||||
expect(fired.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('traffic model: lite passes (phones)', () => {
|
||||
const LIVE: EnvironmentChapter[] = ['deep-night', 'systems', 'infrastructure', 'builds', 'pre-dawn'];
|
||||
|
||||
it('stages a pass at each chapter entry: a car pulls out within 30 s, 20 s on average', () => {
|
||||
// A truck staged at the haze takes ~15 s to reach the 150-215 m entry band.
|
||||
const waits: number[] = [];
|
||||
for (const seed of [1, 5, 8, 13, 21]) {
|
||||
const model = createTrafficModel({ pool: 6, seed, lite: true });
|
||||
const step = input();
|
||||
for (let t = 0; t < 5; t += 1 / 60) model.step(step);
|
||||
for (let entry = 0; entry < 8; entry += 1) {
|
||||
step.chapter = LIVE[(entry + seed) % LIVE.length]!;
|
||||
step.cue = true;
|
||||
const before = model.events().laneChanges;
|
||||
let waited = 0;
|
||||
while (model.events().laneChanges === before && waited < 40) {
|
||||
const f = model.step(step);
|
||||
step.cue = false;
|
||||
waited += 1 / 60;
|
||||
const problem = violation(f);
|
||||
if (problem) throw new Error(`seed ${seed} entry ${entry}: ${problem}`);
|
||||
}
|
||||
expect(waited, `seed ${seed} entry ${entry} (${step.chapter})`).toBeLessThan(30);
|
||||
waits.push(waited);
|
||||
// Read the chapter for a while before the next entry.
|
||||
for (let t = 0; t < 12; t += 1 / 60) model.step(step);
|
||||
}
|
||||
}
|
||||
expect(waits.reduce((a, b) => a + b, 0) / waits.length).toBeLessThan(20);
|
||||
expect(TRAFFIC_LITE_STAGE_SECONDS).toBeGreaterThanOrEqual(15);
|
||||
});
|
||||
|
||||
it('never goes long without a pass at a steady cruise (the drought rule)', () => {
|
||||
for (const seed of [2, 5, 8, 17, 3, 11, 29, 42]) {
|
||||
const model = createTrafficModel({ pool: 6, seed, lite: true });
|
||||
const step = input({ chapter: 'systems' });
|
||||
let last = 0;
|
||||
let longest = 0;
|
||||
let changes = 0;
|
||||
for (let t = 0; t < 600; t += 1 / 60) {
|
||||
const f = model.step(step);
|
||||
const problem = violation(f);
|
||||
if (problem) throw new Error(`seed ${seed} t ${t.toFixed(1)}: ${problem}`);
|
||||
if (model.events().laneChanges !== changes) {
|
||||
changes = model.events().laneChanges;
|
||||
longest = Math.max(longest, t - last);
|
||||
|
||||
last = t;
|
||||
}
|
||||
}
|
||||
longest = Math.max(longest, 600 - last);
|
||||
// Lane changes count the return too: drought, then ~15 s for a staged truck to reach the band, then the pass.
|
||||
// (Without staging this runs 82-88 s on these seeds.)
|
||||
expect(longest, `seed ${seed}`).toBeLessThan(55);
|
||||
}
|
||||
expect(TRAFFIC_LITE_DROUGHT_SECONDS).toBeLessThanOrEqual(15);
|
||||
});
|
||||
|
||||
it('staging stays inside the low-tier caps and never with the engine off', () => {
|
||||
const model = createTrafficModel({ pool: 6, seed: 6, lite: true });
|
||||
const step = input({ chapter: 'infrastructure' });
|
||||
let maxRight = 0;
|
||||
for (let t = 0; t < 300; t += 1 / 60) {
|
||||
step.cue = Math.floor(t * 60) % 600 === 0;
|
||||
const f = model.step(step);
|
||||
let right = 0;
|
||||
for (let i = 0; i < f.count; i += 1) if (f.active[i] === TRAFFIC_VISIBLE && f.x[i]! > 0) right += 1;
|
||||
maxRight = Math.max(maxRight, right);
|
||||
}
|
||||
expect(maxRight).toBeLessThanOrEqual(5);
|
||||
const parked = createTrafficModel({ pool: 6, seed: 6, lite: true });
|
||||
for (let t = 0; t < 60; t += 1 / 60) parked.step(input({ chapter: 'destination', engineOff: 1, heroSpeed: 0, cue: t < 0.02 }));
|
||||
expect(parked.events().laneChanges).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user