Files
Portfolio/docs/reviews/evidence/nightdrive-iteration/sign-passing-refinement/README.md

119 lines
18 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 passing and navigator evidence
Status date: 2026-09-03
This directory records the final bounded Three.js roadside-sign continuation. It supersedes the earlier sign-pass evidence for current navigator triggering, route corridors, live resize behavior, and departure motion. The approved portfolio content, navigation presentation, HUD, controls, car, road, terrain, sun, atmosphere, camera, and forward-travel direction were not redesigned.
## Shared causes found
The remaining defects had related state, projection, and rendering causes:
1. The navigation controller described every link jump only as generic explicit navigation. That correctly suppressed skip links, direct fragments, restoration, and the hero shortcut, but gave the sign controller no way to distinguish an intentional navigator destination.
2. A navigation source persists for several render frames. Consuming that source directly would duplicate a sign, so navigator intent needed a monotonic epoch and a single canonical destination.
3. Passive trigger distance was shared across very different document layouts. In portrait and short landscape, the usable world corridor occurs in a narrower inter-panel interval, so the old threshold could latch while an opaque panel still occupied the sign's dwell region.
4. Placement considered the destination panel but not every opaque panel still visible during a transition. Experience, Additional work, and Contact could therefore clear their incoming card while touching the outgoing Profile, Selected builds, or Toolkit surface.
5. A quality-tier rebuild could reconstruct the renderer with the lifecycle's older responsive profile. The next update then moved a visible sign abruptly. Projection must be owned by the current renderer viewport, with a bounded live reflow.
6. The sign face needed more than a larger bitmap: the source aspect, UVs, font readiness, color space, mipmap path, anisotropy, tone mapping, bloom exposure, and projected dwell size all affected perceived sharpness.
7. Departure changed opacity more than physical pose. It read as a slide-deck fade because the sign neither grew in perspective nor crossed the roadside edge as the car passed it.
8. A full-resolution evidence review exposed one final route-specific issue: the rapid Selected builds reconciliation used the outer-right pose and clipped 13px beyond the viewport in the mobile browser profile. Moving every outer-right sign inward then touched Toolkit before Contact. The final fix assigns Selected builds to the inner-right corridor while retaining Contact's genuinely necessary outer-right placement.
9. Traversal state could race a still-departing sign: a stale completion after returning to Start could disqualify the next Profile traversal, while the new same-label Profile candidate could be dropped behind the old presentation. Visible signs now carry their traversal generation, and only passive same-label work from a newer traversal may become the single pending target. A separate destination-idempotency guard makes another click on the already-active navigator target a no-op even after its first sign has completed.
10. Reverse re-arming originally removed only the immediate candidate from the completion ledger. A multi-section retreat therefore left later routes ineligible, and a still-departing old sign could write its route back after the retreat. A qualifying retreat now advances the traversal generation and re-arms the candidate plus every later route; cooldown state retains its generation so a quick forward return cannot be discarded.
11. A passive portrait sign could already be readable when its matching navigator button was clicked. Keeping the passive pose would place it behind the newly aligned full-width card, while relatching it would duplicate the label. The controller now promotes the existing presentation to a navigator arrival, and the renderer moves it to the portrait-safe pose over the same bounded 320ms pose transition used for resize.
12. Promotion initially preserved the passive dwell clock. Under a slow navigation alignment, part of the guaranteed readable interval could therefore elapse while the sign was still moving into the navigator-safe corridor. Promotion now starts a fresh 1,400ms dwell from the navigator sample while retaining the same object and presentation count.
## Canonical candidacy and placement map
Passive candidates require motion to be enabled, a forward document direction, positive scroll delta, and the next eligible route. The ordinary trigger is at most 0.55 viewport heights from the activation line on wide layouts, 0.23vh on portrait layouts, and 0.38vh on short landscape. Crossing a real section boundary remains authoritative and reconciles directly to the eligible route reached, including large and multi-section jumps.
Navigator buttons add one explicit sign intent to their navigation epoch. That intent selects the canonical destination directly rather than waiting for a passive threshold. Repeated render frames and another click on the same already-active destination cannot duplicate it; after the visitor moves elsewhere, a later navigator return is a new eligible arrival. A navigator target queued behind a readable sign bypasses the passive completed-route filter, and returning to the currently visible label cancels any obsolete queued destination. Start remains ineligible.
| Route target | Face | Marker | Readable corridor | Safe-placement intent |
| --- | --- | ---: | --- | --- |
| `start` | Start | 01 | none | deliberately ineligible |
| `profile` | Profile | 02 | left | opposite the right Profile card |
| `experience` | Experience | 03 | left | clears both the outgoing Profile card and incoming Career surface |
| `infrastructure` | Infrastructure | 04 | outer-right | outside the broad systems-content corridor, with a route-owned 1.6-world-unit inward dwell bias to preserve the viewport edge |
| `selected-builds` | Selected builds | 05 | right | inside the visible right roadside corridor, clear of the large left project card |
| `additional-work` | Additional work | 06 | outer-left | outside the outgoing centered Selected builds surface |
| `toolkit` | Toolkit | 07 | outer-left | outside the wide toolkit content corridor |
| `contact` | Contact | 08 | outer-right | clears the still-visible wide Toolkit surface |
Every eligible route approaches from its corridor's horizon pose, settles at the responsive dwell pose, then travels toward and past the chase camera on the same roadside. Wide, tall, and short pose families are derived from the current renderer viewport. Short landscape receives aspect-bounded lateral relief. A navigator arrival on a narrow portrait viewport uses a compact, terrain-planted near-car pose in the stable gap between the destination and following panels. If navigation promotes an already-readable passive sign, the same object transitions to that safe pose without a label redraw, a second presentation, or a one-frame teleport.
Browser projection tests exercise all seven passive transitions at 1440×900, 390×844, and 844×390. During dwell, the complete face must remain at least 8px inside the viewport, 16px clear of every visible opaque content surface, and 8px clear of navigation, HUD, and controls. Navigator-triggered Profile placement is separately measured at 780×493 and 390×844.
## Lifecycle and retarget contract
| Phase | Duration | Contract |
| --- | ---: | --- |
| Anticipate | 300ms | latch one canonical target; remain concealed at the route horizon |
| Approach | 650–900ms | elapsed-time, frame-rate-independent horizon-to-dwell travel; acceleration can shorten by at most 250ms |
| Dwell | at least 1,400ms | readable from the first actual dwell frame; reverse, target passing, delayed frames, and newer candidates cannot shorten it |
| Depart | 700ms | constant world scale; positive-z movement toward the chase camera plus a roadside sweep; opacity stays full through progress 0.58 and falls off only during the edge pass |
| Cooldown | 350ms | suppress immediate replay while retaining only the latest valid pending destination |
The controller owns one visible sign and one optional pending target. A concealed sign may retarget immediately. A visible sign never changes label. A readable stale sign completes its minimum dwell and departs cleanly; only the latest relevant pending destination follows. If navigation returns to the currently visible target, that intent cancels an obsolete queued destination instead of replaying either label. A presentation may complete only into the traversal generation in which it was latched, so an older departure cannot poison the new traversal's completed-route ledger. Large jumps do not flash or queue obsolete intermediate labels. Reverse document input never reverses physical travel through the car. Live lifecycle sampling continues without new input. Delayed frames enter dwell and start a fresh minimum dwell clock rather than skipping the readable state.
Completed passive routes stay armed-off through small oscillations. Retreat beyond the responsive trigger plus 0.20vh rearm margin starts a new traversal and re-arms that candidate and every later route. A navigator jump to Start, an unrequested explicit jump, or history restoration starts a fresh passive traversal without displaying a suppressed sign or allowing an older departure to re-complete the new ledger.
Initialization, direct fragments, history restoration, skip links, the non-navigator “Explore work” jump, Static mode, reduced motion, and hidden-document resets remain sign-suppressed. Navigator buttons are the sole explicit-navigation path that requests a destination sign.
## Physical departure evidence
At 1440×900, the Profile dwell face measured 211.09px wide. During departure it remained fully opaque while growing to 410.98px at progress 0.500 and sweeping left from 402.91px to 71.74px. At progress 0.643 it crossed the roadside edge (left −342.88px, width 675.49px) and only then began its restrained falloff (opacity 0.920). This produces the visual event of the car overtaking a fixed roadside object instead of fading a card in place.
## Text and runtime-owned geometry
The face is an exact 4:1 plane backed by a 2048×512 sRGB canvas texture, trilinear mipmaps, linear magnification, premultiplied alpha, and up to 8× renderer-supported anisotropy. The bundled Geist Mono marker and label faces are awaited before the first draw. The face bypasses tone mapping and is inset ahead of a separate panel to avoid bloom contamination and z-fighting. Labels redraw and upload only while concealed; there is no per-frame canvas work or texture replacement.
The refined housing uses a clipped beveled body, inset backplate and face panel, rear rails, restrained edge and route accents, two narrow posts, paired braces, fasteners, and separate planted footings. The sign owns 20 renderable parts through 13 geometries, 7 materials, and 1 texture. Those objects are created once per world generation and disposed through the scene resource bag; lifecycle updates do not rebuild the hierarchy or traverse it.
## Measured browser matrix
All recorded dwell frames reported a 2048×512 texture, 8× anisotropy, a browser maximum texture size of 8192, and `fontReadyWhenDrawn: true`.
| Scenario | CSS face width | Renderer ratio | Source texels per rendered face pixel |
| --- | ---: | ---: | ---: |
| 390×844 portrait, device DPR 3 / Low | 162.10px | 1.25 | 10.11 |
| 844×390 short landscape, DPR 1 / High | 151.31px | 1 | 13.54 |
| 768×1024 tablet, DPR 1 / High | 196.67px | 1 | 10.41 |
| 1440×900 desktop, DPR 1 / High | 211.09px | 1 | 9.70 |
| 1440×900 desktop, device DPR 2 / High | 211.07px | 1.5 | 6.47 |
| 1440×900 desktop, DPR 1 / Low | 211.09px | 1 | 9.70 |
| 2560×1440 ultrawide, DPR 1 / High | 337.72px | 1 | 6.06 |
The ratio is the 2,048 source width divided by CSS face width × renderer pixel ratio. It records sampling headroom rather than claiming an unavailable GPU-inspector metric. Low and High retain identical sign typography and geometry. The renderer ratio remains tier-capped independently of the source texture, including device DPR 3.
## Evidence index
- Supplied overlap baseline: [`before/profile-overlap-1280x720-dpr1.25.png`](before/profile-overlap-1280x720-dpr1.25.png)
- Corrected desktop sides: [`after/profile-desktop-1440x900-dpr1-high.png`](after/profile-desktop-1440x900-dpr1-high.png), [`after/experience-desktop-1440x900-dpr1-high.png`](after/experience-desktop-1440x900-dpr1-high.png)
- Navigator arrival: [`after/navigator-profile-780x493-dwell.png`](after/navigator-profile-780x493-dwell.png), [`after/navigator-profile-portrait-390x844-dpr3-dwell.png`](after/navigator-profile-portrait-390x844-dpr3-dwell.png)
- Responsive families: [`after/profile-portrait-390x844-dpr3-low.png`](after/profile-portrait-390x844-dpr3-low.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 and quality: [`after/profile-desktop-1440x900-dpr2-high.png`](after/profile-desktop-1440x900-dpr2-high.png), [`after/profile-desktop-1440x900-dpr1-low.png`](after/profile-desktop-1440x900-dpr1-low.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-early.png`](lifecycle/profile-depart-early.png), [`lifecycle/profile-depart-passing.png`](lifecycle/profile-depart-passing.png), [`lifecycle/profile-depart-edge.png`](lifecycle/profile-depart-edge.png)
- Adverse input: [`lifecycle/slow-scroll-dwell.png`](lifecycle/slow-scroll-dwell.png), [`lifecycle/repeated-tiny-input-dwell.png`](lifecycle/repeated-tiny-input-dwell.png), [`lifecycle/reverse-during-approach.png`](lifecycle/reverse-during-approach.png), [`lifecycle/reverse-during-dwell.png`](lifecycle/reverse-during-dwell.png), [`lifecycle/rapid-multi-section-reconciliation.png`](lifecycle/rapid-multi-section-reconciliation.png), [`lifecycle/delayed-frame-readable-dwell.png`](lifecycle/delayed-frame-readable-dwell.png), [`lifecycle/keyboard-scroll-dwell.png`](lifecycle/keyboard-scroll-dwell.png)
- Visible resize: [`lifecycle/resize-visible-before-1440x900.png`](lifecycle/resize-visible-before-1440x900.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 results: [`browser-matrix.json`](browser-matrix.json), [`lifecycle/trace.json`](lifecycle/trace.json), [`browser-console.json`](browser-console.json)
The lifecycle trace records candidate/latched target outcomes, phase, opacity, projected bounds, visible duration, active section, direction, navigator intent, multi-section reconciliation, and resize state. The navigator scenario presents Profile exactly once and draws only `Profile`; the rapid scenario reconciles directly to Selected builds in the right corridor, with final bounds 857.27–1056.10px; no visible label mutates.
## Fresh verification record
- `npm run build:preview`: passed asset verification, type checking, 43 unit files / 566 tests, client and SSR builds, prerendering, artifact validation, and the transfer gate.
- Focused controller/navigation/drive/world verification: 4 files / 117 tests passed.
- Focused browser reflow verification: the already-readable portrait navigator promotion passed in 4.5s, and the responsive world-safe-corridor test passed in 16.8s, unchanged and with one worker.
- Complete browser matrix: the first frozen run produced 189 passes, 48 intentional project skips, and one parallel-load Toolkit→Contact margin sample; the second produced 188 passes, the same 48 skips, a different responsive-sampling timeout, and an unrelated hydration sample that caught the transient `Motion: preparing` status. The all-seven-transition test passed in the second parallel run and also passed unchanged alone in 39.9s. The responsive sign test passed unchanged alone in 16.8s, and hydration passed unchanged alone in 1.8s. No assertion or timeout was changed.
- Authenticated runtime gate: passed at High, renderer ratio 1.5, with 5,608,790 texture bytes, 755,916 geometry bytes, 161,344,784 render-target bytes, 33,554,432 runtime-allowance bytes, and 201,263,922 total accounted bytes.
- Transfer gate: passed with 21,499 bytes initial HTML/critical CSS, 80,974 bytes initial application JavaScript, 69,040 bytes initial fonts, and 223,856 bytes first live scene; zero violations.
- Deployment suite: 7/9 passed. Its two failures are the checked-in execution-security evidence expiring before the run date, before sign or portfolio behavior is evaluated.
- Long-drive performance harness: no valid measurement. Desktop reached its fixed 75s timeout while locating the visible Toolkit navigation target; mobile did not leave scene loading before its five-second deadline and correctly selected fallback. No metric is claimed and no harness threshold was changed.
- [`browser-console.json`](browser-console.json): empty error list.
## Blender decision and ownership
Another GLB is not justified. The sign's finite construction is lightweight, while its label, candidacy, lifecycle, projection, responsive pose, and quality behavior must remain runtime-owned. Keeping the whole sign in stable Three.js geometry avoids another asset transfer and keeps dynamic text separate by construction.
The finite authored grand-tourer remains Blender-owned and unchanged. Dynamic text, sign behavior, responsive placement, road, terrain, atmosphere, sun, camera, and motion remain Three.js-owned.