Skip to content

fix(highway): a superseded renderer init is not a failed one (gig fell back to the 2D highway)#970

Merged
byrongamatos merged 1 commit into
mainfrom
fix/viz-superseded-init
Jul 14, 2026
Merged

fix(highway): a superseded renderer init is not a failed one (gig fell back to the 2D highway)#970
byrongamatos merged 1 commit into
mainfrom
fix/viz-superseded-init

Conversation

@byrongamatos

@byrongamatos byrongamatos commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Starting a gig dropped the player onto the fallback 2D highway with no venue.

startGig() calls setViz('venue'), which installs the 3D renderer — whose init is async — and then immediately starts its play queue. playSong() re-initialises that same renderer a tick later. A renderer mints a fresh readyPromise per init() and rejects the previous one with "superseded". highway.js only checked that the renderer object was unchanged — which it is — so it treated a healthy, re-initialising renderer as a failed one:

renderer async init failure: Error: superseded
viz picker: reverted to default renderer (async-init-failure)

The guard now also checks the promise identity. The renderer-identity guard stays, and a genuine failure of the current cycle still reverts — both init() call sites go through _setRenderer, which re-wires the handler every time.

Reproduced and fixed against the real build:

vizSelection viz-picker venue scene events
before default default inactive viz:reverted
after venue venue active none

Also: the paused-frame throttle's opt-out was too narrow

The throttle fires whenever the chart clock is stalled — not only on a pause, but through a count-in and the credits/author overlay too. Its opt-out only asked "is a crowd video rolling", but the venue scene animates on a clock of its own with no pack at all (backdrop breathe, parallax, haze drift, warmth pulse — Math.sin(t) in the draw loop). It now claims frames for both sources; a plain 3D highway reads motion mode off and keeps the #654 GPU saving.

Honest caveat on that second part: I could not get the throttle to fire in a reproduction. A control run on the shipped code showed 100 draws/sec while paused, not the ~10/sec a firing throttle would give. So the change is defensible on its own terms (a stalled clock is genuinely not a static picture) but it does not have a demonstrated symptom behind it. The viz fix above does. Happy to drop it if you'd rather not ship on a theory.

Tests fail against the pre-fix source. eslint 0 errors; JS 1207/1207.

Summary by CodeRabbit

  • Bug Fixes
    • 3D highway scenes now continue rendering when venue motion is enabled, even without an actively playing crowd video.
    • Prevented outdated renderer initialization failures from replacing a healthy visualization with the default view.
    • Genuine renderer initialization failures continue to trigger the expected fallback behavior.

Starting a gig dropped the player onto the fallback 2D highway with no venue.

startGig() calls setViz('venue'), which installs the 3D renderer — whose init is
async — and then immediately starts its play queue. playSong() re-initialises
that same renderer a tick later. A renderer mints a fresh readyPromise per
init() and rejects the previous one with "superseded"; highway.js only checked
that the RENDERER OBJECT was unchanged, which it is. So it treated a healthy,
re-initialising renderer as a failed one, tore it down, and reverted to 2D:

    renderer async init failure: Error: superseded
    viz picker: reverted to default renderer (async-init-failure)

The guard now also checks the PROMISE identity: a rejection from an init cycle
the renderer has already moved on from is ignored. The renderer-identity guard
stays (a rejection for a renderer since REPLACED is also not ours), and a
genuine failure of the CURRENT cycle still reverts — both init() call sites go
through _setRenderer, which re-wires the handler every time, so the new cycle is
always watched.

Reproduced and fixed against the real build:

    before:  vizSelection=default  viz-picker=default  venue=inactive  viz:reverted
    after:   vizSelection=venue    viz-picker=venue    venue=ACTIVE    (no revert)

Also widens the paused-frame throttle's opt-out. The throttle fires whenever the
CHART CLOCK is stalled — not only on a pause, but through a count-in and the
credits/author overlay too. Its opt-out only asked "is a crowd video rolling",
but the venue scene animates on a clock of its own with no pack at all (backdrop
breathe, parallax, haze drift, warmth pulse — Math.sin(t) in the draw loop), so
that motion was still being throttled. It now claims frames for both sources; a
plain 3D highway with no venue reads motion mode 'off' and keeps the #654 GPU
saving.

HONEST CAVEAT on that second part: I could not get the throttle to fire in a
reproduction. A control run on the shipped code showed 100 draws/sec while
paused, not the ~10/sec a firing throttle would give — so the change is
defensible on its own terms (a stalled clock is genuinely not a static picture)
but it does NOT have a demonstrated symptom behind it. The viz fix above does.

Tests: the superseded guard, and that the throttle opt-out covers both motion
sources. All fail against the pre-fix source. eslint 0 errors; JS 1207/1207.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Highway continuous-frame detection now accounts for venue motion modes in addition to rolling crowd videos. Renderer initialization failure handling now ignores rejections from superseded promises while preserving current-cycle failure recovery, with tests covering both behaviors.

Changes

Highway rendering behavior

Layer / File(s) Summary
Continuous-frame motion detection
plugins/highway_3d/screen.js
needsContinuousFrames() keeps continuous frames enabled for rolling crowd videos and now also enables them when effective venue motion is not 'off', falling back to false if motion-mode lookup fails.
Superseded renderer initialization handling and regression coverage
static/highway.js, tests/js/highway_pause_throttle.test.js
Async initialization failures from outdated readyPromise instances are ignored, while current-cycle failures still revert to the default renderer; tests cover both renderer failure behavior and continuous-frame sources.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: chrisbewithyou, omikronapex

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the change, but it omits the required template sections What, feedpak surface, and Checklist. Rewrite it to match the template, adding a What section, the feedpak surface note or deletion rationale, and the checklist items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the stale renderer init rejection fix and the 2D fallback regression.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/viz-superseded-init

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
static/highway.js (1)

1004-1004: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Resolve-path lacks the same staleness guard.

The reject handler now ignores a rejection when _installedRenderer.readyPromise !== rp (Line 1004), but the adjacent resolve handler (Lines 1035-1038) only checks hwState._renderer === _installedRenderer — not whether rp is still the renderer's current readyPromise. This currently works only because the documented contract says a superseded promise always rejects, never resolves. If that invariant is ever violated (e.g. a future renderer change resolves an old promise instead of rejecting it), _emitVizReady() could fire for a stale init cycle. Mirroring the same identity check in the resolve branch would close this gap defensively.

♻️ Proposed symmetry fix
                 rp.then(
-                    () => { if (hwState._renderer === _installedRenderer) _emitVizReady(); },
+                    () => { if (hwState._renderer === _installedRenderer && _installedRenderer.readyPromise === rp) _emitVizReady(); },
                     _handleAsyncInitFailure
                 );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@static/highway.js` at line 1004, Update the adjacent resolve handler for the
renderer ready promise to also require _installedRenderer.readyPromise === rp,
matching the existing rejection guard. Preserve the hwState._renderer ===
_installedRenderer check and prevent _emitVizReady() from running when the
promise belongs to a superseded initialization cycle.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@static/highway.js`:
- Line 1004: Update the adjacent resolve handler for the renderer ready promise
to also require _installedRenderer.readyPromise === rp, matching the existing
rejection guard. Preserve the hwState._renderer === _installedRenderer check and
prevent _emitVizReady() from running when the promise belongs to a superseded
initialization cycle.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e4c815f-ba3d-4818-8832-d631cd7502c8

📥 Commits

Reviewing files that changed from the base of the PR and between 4e0e3c5 and 73159a7.

📒 Files selected for processing (3)
  • plugins/highway_3d/screen.js
  • static/highway.js
  • tests/js/highway_pause_throttle.test.js

@byrongamatos
byrongamatos merged commit 917d81c into main Jul 14, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant