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

89 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 journey 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.
## 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
- Before: [`before/profile-overlap-1280x720-dpr1.25.png`](before/profile-overlap-1280x720-dpr1.25.png)
- Desktop Profile/right-card separation: [`after/profile-desktop-1440x900-dpr1-high.png`](after/profile-desktop-1440x900-dpr1-high.png)
- Desktop Experience/left-card separation: [`after/experience-desktop-1440x900-dpr1-high.png`](after/experience-desktop-1440x900-dpr1-high.png)
- Responsive: [`after/profile-portrait-390x844-dpr3-high.png`](after/profile-portrait-390x844-dpr3-high.png), [`after/profile-short-844x390-dpr1-high.png`](after/profile-short-844x390-dpr1-high.png), [`after/profile-tablet-768x1024-dpr1-high.png`](after/profile-tablet-768x1024-dpr1-high.png), [`after/profile-ultrawide-2560x1440-dpr1-high.png`](after/profile-ultrawide-2560x1440-dpr1-high.png)
- DPR/quality: [`after/profile-desktop-1440x900-dpr2-high.png`](after/profile-desktop-1440x900-dpr2-high.png), [`after/profile-desktop-1440x900-dpr1-low-quality.png`](after/profile-desktop-1440x900-dpr1-low-quality.png), [`after/profile-desktop-1440x900-dpr1-high-quality.png`](after/profile-desktop-1440x900-dpr1-high-quality.png)
- Lifecycle: [`lifecycle/profile-anticipate.png`](lifecycle/profile-anticipate.png), [`lifecycle/profile-approach.png`](lifecycle/profile-approach.png), [`lifecycle/profile-dwell.png`](lifecycle/profile-dwell.png), [`lifecycle/profile-depart.png`](lifecycle/profile-depart.png)
- Adverse input: [`lifecycle/profile-reverse-during-approach.png`](lifecycle/profile-reverse-during-approach.png), [`lifecycle/profile-dwell-after-reverse.png`](lifecycle/profile-dwell-after-reverse.png), [`lifecycle/repeated-tiny-input-dwell.png`](lifecycle/repeated-tiny-input-dwell.png), [`lifecycle/rapid-multi-section-reconciliation.png`](lifecycle/rapid-multi-section-reconciliation.png)
- Visible resize: [`lifecycle/resize-visible-before.png`](lifecycle/resize-visible-before.png), [`lifecycle/resize-visible-after-844x390.png`](lifecycle/resize-visible-after-844x390.png)
- Suppressed modes: [`after/static-mode-1440x900.png`](after/static-mode-1440x900.png), [`after/reduced-motion-390x844-dpr3.png`](after/reduced-motion-390x844-dpr3.png)
- Machine-readable measurements: [`browser-matrix.json`](browser-matrix.json), [`lifecycle/trace.json`](lifecycle/trace.json), [`browser-console.json`](browser-console.json)
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.