Files
Portfolio/docs/reviews/evidence/nightdrive-iteration/sign-reliability-pass/README.md

11 KiB
Raw Blame History

Nightdrive sign reliability and legibility evidence

Status date: 2026-09-03

This directory records the bounded Three.js roadside-sign pass. The portfolio content, panels, navigation, HUD, controls, car, road, terrain, sun, atmosphere, camera, and forward-travel direction were not redesigned.

Shared causes found

The supplied Profile failure was not one isolated coordinate defect:

  1. The former candidate gate discarded large deltas and multi-boundary samples before lifecycle reconciliation. Slow threshold crossings and rapid arrival therefore followed different paths, and the latter could skip the relevant destination.
  2. Dwell time was derived from the original latch time. A delayed frame could consume the readable interval before the first dwell frame, while no further input could leave another sign frozen indefinitely.
  3. Every desktop label inherited one right-side pose. Profile therefore competed directly with its right-side content card, while one immediate responsive remap could move a live sign to a different pose family.
  4. The 1024×288 source was stretched onto a 3.93:1 face, could be drawn before the intended font was ready, and was small enough relative to the projected dwell face to look soft after perspective, mip selection, and post-processing.
  5. The former departure could advance toward and through the camera plane. A late departure frame consequently expanded instead of receding.
  6. A scene rebuild copied the lifecycle fields but omitted the latched responsive profile, so a quality/resize rebuild could silently replace a live composition.

Canonical route contract

Only forward passive-scroll samples with motion enabled are eligible. The opening route is deliberately ineligible. A normal sample selects the next route once its heading is within 0.55 viewport heights of the activation line. A sample that crosses a section boundary selects the eligible section actually reached. Large and multi-section jumps coalesce to that latest relevant destination rather than queueing intermediate labels.

Route target Face label Marker Corridor Trigger Dwell placement intent
start Start 01 none ineligible no sign
profile Profile 02 left ≤0.55vh or actual arrival opposite the right Profile card
experience Experience 03 right ≤0.55vh or actual arrival opposite the left Career card
infrastructure Infrastructure 04 outer-right ≤0.55vh or actual arrival outside the broad systems content corridor
selected-builds Selected builds 05 outer-right ≤0.55vh or actual arrival outside the projects content corridor
additional-work Additional work 06 left ≤0.55vh or actual arrival away from the dominant content surface
toolkit Toolkit 07 outer-left ≤0.55vh or actual arrival outside the toolkit content corridor
contact Contact 08 right ≤0.55vh or actual arrival opposite the Contact destination content

The controller owns exactly one visible target. A concealed sign can retarget immediately. A visible label is immutable; a newer candidate is coalesced as the pending target. Completed targets are suppressed for the current passive traversal so small oscillations cannot spam them. A stale readable target completes its minimum dwell before departing. Initialization, explicit navigation (including skip links and direct fragments), history restoration, Static mode, and reduced motion remain intentionally suppressed. Visibility loss resets transient lifecycle state.

The navigator-recovery follow-up corrected the traversal boundary itself. Suppressed explicit/history/initial samples previously marked whichever active section they observed as completed, including transient sections crossed by a navigator jump. That completion ledger then survived the return to Start and could suppress the correct next sign indefinitely. Non-passive navigation now clears the passive-traversal completion ledger while still rendering no sign. The next real wheel, touch, or scroll-key epoch therefore starts eligible and deterministic; completion remains sticky only inside uninterrupted passive travel, preserving the small-oscillation anti-spam rule. Unit coverage drives a completed Profile lifecycle through Contact and back to Start, while the browser regression clicks Profile, returns through the navigator, and proves the next passive Profile sign renders on both desktop and mobile.

Lifecycle contract

Phase Duration Behavior
Anticipate 300ms target/profile latch; sign remains concealed at its horizon pose
Approach 900ms, reduced by at most 250ms with acceleration elapsed-time interpolation from horizon to dwell; opacity reaches full early in approach
Dwell at least 1,400ms from the first actual dwell sample fixed readable pose; reverse/passed/stale input cannot shorten the minimum
Depart 700ms exits laterally and recedes 32 world units; never reverses through the car or crosses the camera
Cooldown 350ms prevents immediate replay; retains only the latest valid pending target

The controller continues sampling without input while a sign is live, so release cannot freeze it. A delayed first dwell frame starts the dwell clock at that observed frame rather than skipping readability. Resize/orientation and scene-quality rebuilds preserve the latched wide, tall, or short profile until the lifecycle is concealed; the newly measured family applies to the next sign.

Responsive pose and texture contract

short applies below 560px height in landscape, tall below a 1.05 aspect ratio, and wide otherwise. Each family has inner left/right and outer left/right roadside poses. Dwell face-width assertions are 120px at 390×844, 130px at 844×390, 150px at 768×1024, 180px at 1440×900, and 240px at 2560×1440. Every readable face must stay at least 8px inside the viewport, 16px clear of active and adjacent opaque content, and 8px clear of fixed navigation, controls, and HUD.

The face is an exact 4:1 plane backed by a 2048×512 4:1 canvas. It uses sRGB, trilinear mipmaps, linear magnification, up to 8× renderer-supported anisotropy, and a non-tone-mapped face material. The bundled Geist Mono marker and label weights are awaited before the first draw. A target change redraws only while concealed; there is no per-frame canvas redraw or texture replacement. The housing, clipped bevel, backplate, bracket, brace, narrow post, and dynamically compensated footing remain stable render-owned Three.js objects.

Measured browser matrix

All recorded dwell frames reported a 2048×512 texture, 8× anisotropy, and fontReadyWhenDrawn: true.

Scenario CSS face width Renderer ratio Source texels per rendered face pixel Content separation
390×844 portrait, device DPR 3 / High 150.64px 1.25 10.87 pass
844×390 short landscape, DPR 1 / High 134.49px 1 15.23 active + adjacent pass
768×1024 tablet, DPR 1 / High 182.76px 1 11.21 pass
1440×900 desktop, DPR 1 / High 195.05px 1 10.50 pass
1440×900 desktop, device DPR 2 / High 195.06px 1.5 7.00 pass
1440×900 desktop, DPR 1 / Low 195.06px 1 10.50 pass
2560×1440 ultrawide, DPR 1 / High 312.09px 1 6.56 pass

The ratios above divide the 2,048 source texels by CSS face width × renderer ratio. They describe source sampling headroom; they are not an invented GPU-inspector measurement.

Evidence index

The lifecycle trace records target, phase, progress, opacity, projected face bounds, active section, direction, responsive profile, texture state, renderer ratio, reverse handling, repeated-small-input dwell, rapid multi-section reconciliation, visible resize, and the captured departure frame. Its rapid reconciliation contains only Selected builds; the visible Profile label never mutates.

Three.js DevTools was unavailable for the final local preview (No Three.js app connected), so no replacement draw-call or inspector-memory result is claimed here. Allocation and transfer impact are verified by the repository's authenticated runtime-budget and production-build harnesses instead.

Asset ownership

No additional Blender asset was justified. The sign needs dynamic labels, route candidacy, lifecycle motion, responsive placement, and quality-tier integration; lightweight stable Three.js geometry preserves those controls without another GLB or asset transfer. The finite authored car remains Blender-owned and unchanged. Dynamic sign text and behavior, responsive placement, road, terrain, atmosphere, sun, camera, and motion remain Three.js-owned.