You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Restored from slopsmith/slopsmith#819 — original issue, opened by @byrongamatos on 2026-06-12. [restored-from: slopsmith/slopsmith#819]
Summary
The per-frame draw gate in static/highway.js pauses the active custom renderer'sdraw(bundle) whenever the highway is marked not-visible — including when a renderer set that hidden state itself via setVisible(false). That starves any custom renderer that paints its own surface while occluding the highway canvas.
Override-hide (_visibleOverride === false) — a renderer called setVisible(false) because an opaque overlay covers the canvas. Its purpose is to fire the highway:visibility event so sibling overlay renderers (e.g. the 3D Highway .h3d-wrap) pause their own loops. But the active_renderer that triggered it is still painting its own visible surface (e.g. Tab View renders an alphaTab DOM tab over the hidden canvas) — it must keep getting draw().
Case 2 wrongly trips the same gate, so the active custom renderer's draw() stops the instant it hides the canvas.
Observed impact
Tab View (slopsmith-plugin-tabview) hides the highway via setVisible(false) (#499, to stop the occluded renderer wasting GPU). Its playback cursor — positioned per-frame from draw(bundle) — silently froze / never appeared in single-player on shipped builds (slopsmith#734). It only worked in splitscreen, where setVisible(false) is skipped. This is core-side and not Electron-specific (reproduces in a browser).
Worked around plugin-side in slopsmith-plugin-tabview#25 (the plugin now self-drives its cursor from its own rAF + highway.getTime()), but the underlying core trap remains for any future custom renderer that paints its own surface.
Proposed fix
Exempt the active custom renderer from the override-hide branch of the gate, while keeping genuine off-screen pausing everything and keeping the default 2D renderer paused on an override-hide (its canvas really is the occluded surface). The highway:visibility event still fires (it's emitted before the gate), so sibling overlays still pause.
if(!_lastVisible){constoverrideHide=_visibleOverride===false;constcustomActive=_renderer!==_defaultRenderer;if(!(overrideHide&&customActive))return;// genuine off-screen, or default renderer → still pause}
This preserves setVisible(true) (force-visible), #418 (off-screen), and #499 (default-renderer GPU saving), and only lets an active custom renderer keep painting through its own override-hide. The existing paused-frame throttle still applies afterward.
Summary
The per-frame draw gate in
static/highway.jspauses the active custom renderer'sdraw(bundle)whenever the highway is marked not-visible — including when a renderer set that hidden state itself viasetVisible(false). That starves any custom renderer that paints its own surface while occluding the highway canvas.Where
static/highway.js, indraw():_lastVisiblecomes from_isHighwayVisible():The conflation
_lastVisiblegoes false for two different reasons that should be treated differently for an active custom renderer:_visibleOverride === null && canvas.offsetParent === null) — the player screen is navigated away / a splitscreen panel isdisplay:none. Nothing is on screen → pausing everything is correct (Highway viz renderers keep rendering full-screen when #highway is display:none (leaks behind splitscreen + wasted GPU) #418)._visibleOverride === false) — a renderer calledsetVisible(false)because an opaque overlay covers the canvas. Its purpose is to fire thehighway:visibilityevent so sibling overlay renderers (e.g. the 3D Highway.h3d-wrap) pause their own loops. But the active_rendererthat triggered it is still painting its own visible surface (e.g. Tab View renders an alphaTab DOM tab over the hidden canvas) — it must keep gettingdraw().Case 2 wrongly trips the same gate, so the active custom renderer's
draw()stops the instant it hides the canvas.Observed impact
Tab View (slopsmith-plugin-tabview) hides the highway via
setVisible(false)(#499, to stop the occluded renderer wasting GPU). Its playback cursor — positioned per-frame fromdraw(bundle)— silently froze / never appeared in single-player on shipped builds (slopsmith#734). It only worked in splitscreen, wheresetVisible(false)is skipped. This is core-side and not Electron-specific (reproduces in a browser).Worked around plugin-side in slopsmith-plugin-tabview#25 (the plugin now self-drives its cursor from its own rAF +
highway.getTime()), but the underlying core trap remains for any future custom renderer that paints its own surface.Proposed fix
Exempt the active custom renderer from the override-hide branch of the gate, while keeping genuine off-screen pausing everything and keeping the default 2D renderer paused on an override-hide (its canvas really is the occluded surface). The
highway:visibilityevent still fires (it's emitted before the gate), so sibling overlays still pause.This preserves
setVisible(true)(force-visible), #418 (off-screen), and #499 (default-renderer GPU saving), and only lets an active custom renderer keep painting through its own override-hide. The existing paused-frame throttle still applies afterward.I'll open a PR with this change.