fix: make nightdrive signage reliable and legible
This commit is contained in:
@@ -141,6 +141,28 @@ The later navigator failure shared one state-ownership cause: explicit, initial,
|
||||
|
||||
The focused controller regression failed against the previous implementation because Profile remained `null` after a completed lifecycle and an explicit Contact-to-Start round trip, then passed after the ownership correction. The real-browser regression clicks a section, returns to Start, and verifies that the Profile sign is emitted on the next passive scroll. It passed in both desktop and mobile projects. The final four-worker browser matrix passed 180 checks with 44 deliberate project/capability skips and no failures, including navigation, history, keyboard, accessibility, responsive, Static, reduced-motion, fallback, visual, and flash-safety coverage.
|
||||
|
||||
### Sign passing and navigator-trigger continuation
|
||||
|
||||
The final 2026-09-03 signs-only continuation supersedes the earlier sign-pass route, navigator, resize, texture, and departure details above. Durable evidence is retained under `docs/reviews/evidence/nightdrive-iteration/sign-passing-refinement/`; its [`README.md`](evidence/nightdrive-iteration/sign-passing-refinement/README.md) is the canonical evidence index and records the complete route/trigger/pose contract. The supplied Profile-overlap baseline remains separate from the corrected viewport, DPR, quality, lifecycle, adverse-input, navigator, Static, reduced-motion, and empty-console evidence.
|
||||
|
||||
The investigation found six shared causes. Navigator actions exposed only a generic explicit-navigation source, so preserving skip/direct/history suppression also concealed intentional navigator arrivals. Completed-route state was not generation-bound, allowing an older departure to poison a traversal restarted at Start; reverse hysteresis re-armed only one route; and cooldown discarded legitimate same-label work from a newer traversal. The old desktop placement model did not account for the outgoing opaque panel or route-specific viewport-edge pressure. Perceived face softness came from the combined source aspect, font-readiness, UV, filtering, color-space, tone-mapping, bloom, projection, and DPR path rather than texture dimensions alone. Finally, departure mostly reduced opacity in place, which produced the reported slide-transition effect instead of a physical roadside pass.
|
||||
|
||||
The controller now consumes a navigator request exactly once through a monotonic navigation epoch and canonical destination. Start is ineligible; navigator Profile through Contact are eligible. Another click on the same already-active destination is idempotent, including after the first lifecycle completes. A different destination may replace a concealed label immediately or become the sole pending label behind a readable sign; returning to the visible target cancels the obsolete pending label. If a passive portrait sign is already readable when its matching navigator link is activated, the same object is promoted to the safe navigator pose over 320ms—without a redraw, replay, or teleport. Initial positioning, direct fragments, history restoration, skip links, the non-navigator Explore shortcut, Static, reduced motion, and hidden-document reset remain sign-suppressed; the next passive traversal is re-armed where appropriate.
|
||||
|
||||
Passive candidacy requires enabled motion, forward document direction, positive input, and the canonical upcoming route. The trigger maxima are 0.55vh for wide layouts, 0.23vh for portrait, and 0.38vh for short landscape; an actual boundary crossing wins and reconciles directly to the route reached. A qualifying reverse retreat beyond the trigger plus 0.20vh starts a new traversal and re-arms that route and every later route. Visible/cooldown state carries the traversal generation, preventing stale completion or same-label cooldown races. The clock contract is 300ms anticipate, 650–900ms approach, at least 1,400ms dwell measured from the first actual readable sample, 700ms pass-by departure, and 350ms cooldown. Delayed frames cannot skip dwell, stopping input cannot freeze a live sign, reverse input cannot move it physically backward, and a visible label never mutates.
|
||||
|
||||
The final route corridors are Profile left, Experience left, Infrastructure outer-right with a route-owned 1.6-world-unit inward dwell bias, Selected builds right, Additional work outer-left, Toolkit outer-left, and Contact outer-right. Projection assertions cover all seven passive transitions and require the complete dwell face to stay at least 8px inside the viewport, 16px outside visible opaque content, and 8px outside fixed controls. The responsive renderer—not stale lifecycle metadata—owns the current wide/tall/short pose. Visible resize and passive-to-navigator changes share one bounded pose transition and do not revive, duplicate, or strand the object.
|
||||
|
||||
The refined sign remains stable Three.js-owned geometry: one clipped/beveled housing, inset face panel, backplate, rear rails, paired narrow posts and braces, fasteners, and planted footings. It uses 20 renderable parts, 13 geometries, 7 materials, and one texture. The 4:1 face is a 2048×512 sRGB canvas texture with premultiplied alpha, trilinear mipmaps, linear magnification, up to 8× supported anisotropy, awaited Geist Mono fonts, and a non-tone-mapped face material. It redraws/uploads only when a concealed label changes. Recorded source headroom ranged from 6.06 to 13.54 texels per rendered face pixel across 390×844/DPR 3 Low through 2560×1440 High, and every recorded draw reported `fontReadyWhenDrawn: true`.
|
||||
|
||||
The new departure keeps constant world scale and moves the planted sign toward the chase camera while sweeping it out through its authored roadside edge. At 1440×900 the Profile face grew in projection from 211.09px at dwell to 410.98px at progress 0.500 while remaining fully opaque and moving left from 402.91px to 71.74px. At progress 0.643 it was already crossing the edge (left −342.88px, width 675.49px) before the restrained opacity falloff began at 0.920. The result reads as the car overtaking a fixed roadside object rather than a card fading in place.
|
||||
|
||||
Fresh verification on 2026-09-03 passed type checking, the production preview build, all 43 unit files / 566 tests, and the focused sign/navigation/world set (4 files / 117 tests). The first complete four-worker browser matrix completed 189 passes and 48 intentional project skips; its sole failure was the 1440×900 Toolkit→Contact safety-margin sample under parallel load. A second frozen repeat completed 188 passes and the same 48 skips: the seven-transition assertion passed that time, while a different responsive sign sample timed out during `page.evaluate` and an unrelated hydration test sampled the transient `Motion: preparing` label. The exact unchanged seven-transition test passed with one worker in 39.9s, the responsive sign and navigator-promotion checks passed unchanged with one worker in 16.8s and 4.5s, and the hydration check passed unchanged alone in 1.8s. These are retained as observed load-only matrix flakes rather than hidden by timeout or assertion changes. The authenticated High-tier allocation gate passed at a renderer ratio of 1.5 with 5,608,790 texture bytes, 755,916 geometry bytes, 161,344,784 render-target bytes, a 33,554,432-byte runtime allowance, and 201,263,922 total accounted bytes. The transfer gate passed with 80,974 bytes of initial application JavaScript and 223,856 bytes for the first live scene; it reported no violations. The saved console capture contains no browser errors.
|
||||
|
||||
Two pre-existing harness limitations remain explicit. Deployment verification passed 7/9 tests; both failures stop at the repository's expired execution-security-evidence timestamp before portfolio behavior is exercised. The long-drive performance harness did not produce a valid trace: desktop exhausted its fixed 75s timeout while resolving the visible Toolkit navigation target, and mobile stayed in scene loading until the five-second live-scene deadline selected the static fallback. No performance number is inferred from either invalid run, and no timeout, lifecycle assertion, or budget was weakened.
|
||||
|
||||
No additional Blender asset was justified. The finite authored car remains Blender-owned and unchanged. Dynamic sign text, candidacy, lifecycle, responsive placement, road, terrain, atmosphere, sun, camera, and motion remain Three.js-owned.
|
||||
|
||||
## Direction review checklist
|
||||
|
||||
- [x] Information uses near-black opaque panels rather than neon glass.
|
||||
|
||||
Reference in New Issue
Block a user