feat: interactive traffic with car-following, overtakes and flash-to-pass
This commit is contained in:
@@ -110,3 +110,21 @@ Run: `npm run build:preview` then serve `dist` (dev server shows a blank page).
|
||||
- **Review (Sonnet, hardware GL):** per-frame allocations removed, non-finite distance guarded; featured plates now hang on a 4.2 m arm over the road (clears traffic at ~6.3 m) so the Experience plate is fully in frame at 1440.
|
||||
- **Fixed after (opening entry):** a chapter's first entry used to arrive up to two entries late when the reader started at the heading, because only posts in the haze (~150 m out) could take an entry. Now, for 0.75 s after a chapter change, the posts already on the road stay dark; once the reading pace is known, the one that will pass closest to the first entry's activation takes it (the nearest when the reader is still), unless a post from the haze would be on time anyway. Haze assignments wait for that, and there is no glint in the 0.3 s after a change or on a post leaving its chapter. Unit tests show entry 0 now passes within 0.4 of an entry's share of its line (it used to miss by up to about 2 shares), and within 6.5 s when the reader stays at the heading.
|
||||
- Screenshots: docs/overnight/polish/posts-*.webp (before/after, idle contact strips before and after, feature plate zoom).
|
||||
|
||||
## Polish: interactive traffic (owner feedback)
|
||||
- **What:** traffic was scenery that clipped through the hero car. It is now a small deterministic simulation (scene/world/traffic-model.ts, typed arrays, a seeded LCG, a preallocated frame, no per-frame allocation). Vehicles keep their distance with IDM-lite car-following, treat the hero as a virtual leader, and a hard front-to-back constraint means no two bodies (hero included) ever overlap. The road is three lanes with dashed dividers: fast cars on the left (about 40 u/s, they overtake the hero alongside and pull away), slow cars and box trucks on the right (the hero passes them), and the centre lane is used for overtakes only. An overtaking car signals for 1.4 s, at 1.5 Hz with a 0.4 duty, before it changes lane. It plans its return before it enters and yields early, or sprints, when the hero closes in. Brake lamps and the CHMSL light on real deceleration, with hysteresis and minimum on-time, rippling back through a queue and capped at 3 onsets per second. **Flash to pass:** a high-beam flash makes a centre-lane car indicate and move out of the way, and tail lamps and truck under-run bars flare as retro-reflectors in the beam cone (new `headlightCone()` in BEAM_PARS). **Scroll:** while the drive sweeps, or on a navigator arrival, nobody changes lane. Density follows the chapter (busy in Infrastructure, empty at the destination and with the engine off). Vehicles enter and leave only in the haze or behind the camera, never on screen. A vehicle passing under an off-centre chase camera dissolves out instead of becoming a dark slab. On medium and high tiers (not lite, not Save-Data), a short band-passed whoosh panned to the passing side plays through the soundtrack's switchboard; it lives in a lazy 0.5 KB chunk.
|
||||
- **Build:** traffic-model.ts (new, pure), traffic.ts (rewritten renderer: instanced bodies with aBody near/dither/brake/camera-side, six lamps per vehicle, trails, rear pools, warm front pools on medium and high), environment.ts (sweep, flash and chapter inputs, lazy swell), headlights.ts (`headlightCone`), road.ts (centre line becomes two lane dividers), milestones.ts (comment: the arm clears 5.67 m trucks), audio/traffic-pass.ts (new), bundle-budgets.mjs (`traffic-pass-` deferred-non-video). 963 tests (81 files, +31; 20 seeds × 10 simulated minutes with zero overlaps, adversarial speeds and frame steps, flash, sweep, density, brake and blink timing). violations [].
|
||||
- **Budgets:** initial JS unchanged (339354 B raw; the gz figure moves by 1 B of hash noise), scene chunk 222.09 -> 227.39 KB gz (+5.3, cap 8), traffic-pass chunk 494 B (deferred-non-video).
|
||||
- **Perf (hardware GL 1440x900, r6 flags, same runs as the baseline):** rAF p50 16.7 / p95 16.7-16.8 ms on idle Start, brisk scroll, idle Infrastructure, wheel, nav jump and reverse, identical to the baseline. 0 frames over 34 ms; the wheel run's single 33.4 ms max is also in the baseline. No console errors.
|
||||
- **Deviations from the spec:** speeds retuned against the real idle cruise (18 + 0.18·54 = 27.7 u/s, not 18); vehicles leave by their nose at z 20 into a behind-camera buffer instead of by their rear at +18; cars never near-fade (only trucks latch a camera-side near limit at -30); the camera-side dissolve applies to every kind; sweep = smoothed |Δroute|/dt > 0.12 or a navigator arrival; added yield heuristics (forced 0.35 s headway, a 6 s "soon" horizon, a sprint boost, a latch after a flash, a return-room check), without which the centre car could be boxed in, which produced emergency exits.
|
||||
- **Known weaknesses:** overtakes and their indicators happen 200-320 m ahead, where a vehicle is a few pixels wide, so the blink is barely legible at 1440 (the strips show the manoeuvre, not a readable amber). The reflector flare is subtle. Right after a scroll the road ahead is sparse until new vehicles come out of the haze at -640. The intro sweep can register as one flash. A hardware-GL screenshot run cannot tell an indicator from the yellow lane dashes, so indicator timing is proven in unit tests, not on screen.
|
||||
- Screenshots: docs/overnight/polish/traffic-*.webp (before/after idle at Start; alongside pass, a left-lane car coming up beside the hero and pulling away; overtake, left-lane overtakes ahead; flash-to-pass, a truck's lamps flaring under the flash and passing on the right; scroll).
|
||||
- **Review (Sonnet, hardware GL) of the weaknesses above:**
|
||||
- *Overtakes in frame.* The centre-lane window moved from -280..-150 to -210..-150 (entry at about -192, exit at -115..-150, i.e. 150-215 m from the camera on every tier), with the entry gate relaxed (closing speed on the hero <= 12 u/s, a 90 m leader gap, reach checked with the 8 u/s sprint boost). A **convoy spawn** rule makes the manoeuvre happen at all on the lite pool: a right-lane car fades in queued 30-50 m behind an unfollowed truck in the haze window (-600..-450), or waits a few seconds for a truck to reach it, so a car is behind a truck inside the band instead of only when two spawns happened to line up. Seed 5 lite went from 0 lane changes in 300 s to 6-10; the full pool makes 14-26. If the hero closes before a car reaches the deadline it yields with a forced 0.8 s swerve, which clears the hero corridor by -10 even when the drive steps straight to 120 u/s. The indicator now reads at 1440: the lamp quad swells 1.6x, the gain is 2.4x, and an amber spill (aBlink) paints the signalling corner of the body.
|
||||
- *Flash.* The flare is a near cutoff (18-50 m) with a warm-white mix (0.7) and a 1.3x lamp swell under the beam lift, plus a faint warm wash on rear faces within 320 m. A brighter version (3.2x, 0.8 swell) was tried and rejected: the bloom pass turned every tail lamp within 100 m into a white patch (road luminance 110 -> 166). At the final level the clip's mean luminance rises 82 -> 102 at most on a flash with vehicles at 100 m, and does not move on an empty road.
|
||||
- *Sparse after a scroll.* When a lane's band -560..-270 is empty a vehicle enters at -420..-510 with a 0.6 s screen-door fade-in (the dither channel) in the haze instead of at -640.
|
||||
- *Intro sweep.* `createFlashDetector()` (pure, in traffic-model.ts) arms only on a reading that is below 0.85 and not rising, so the 0 -> 1 sweep never fires; a flash fires once per crossing of 0.95.
|
||||
- *Click to flash.* Any click that is not on a control, a label, the dashcam frame or a text selection flashes (journey/flash-target.ts, passive listener, nothing prevented), not only `.hero-journey` and `.gantry-window`. Initial JS +120 B raw (339474) for the predicate.
|
||||
- Tests: 970 (82 files): in-frame overtakes for four pool/seed configurations, near-spawn fade-in, the forced swerve against instant 120 u/s, the detector against the sweep and two flashes, and the click predicate.
|
||||
- Budgets: scene chunk 228.15 KB gz9 (+6.06 vs 222.09, cap +8). Perf unchanged: rAF p50 16.7 / p95 16.7-16.8 ms on the six runs, 0 frames over 34 ms, no console errors.
|
||||
- Still weak: there is no visual hint that a click flashes; on lite the overtake comes every ~50 s; the flash is still clearly bright through bloom on vehicles within 100 m (within the 3 Hz, 600 ms interval rule); `traffic-overtake-indicator.webp` is a 120 s capture crop at Infrastructure (centre-lane car at about 200 m with the amber on), `traffic-flash-to-pass.webp` is a before / +1 / +2 frame strip of one flash.
|
||||
|
||||
Reference in New Issue
Block a user