From 6fa5aabba82429d794f9db500f9e6b046a68b39e Mon Sep 17 00:00:00 2001 From: Byron Gamatos Date: Fri, 26 Jun 2026 14:08:13 +0200 Subject: [PATCH] fix(highway_3d): re-home Butterchurn panel to a surviving highway (splitscreen) (#599) The Butterchurn control panel is a singleton, created only when a controller is created and parented to that controller's wrap. In splitscreen the panel followed the last-created controller; when that controller was torn down, destroy() only removed the panel DOM if it was the LAST controller, so with another highway still alive the panel stayed orphaned on the destroyed wrap and the surviving highway was left with no visualizer controls. Track each controller's wrap (ctrl.wrap) and, on destroy with another controller still alive, re-home the panel+pane onto the surviving primary's wrap via _bcEnsurePanel (which moves them when connected, or rebuilds them on the survivor if the old wrap was already detached). Co-authored-by: Claude Opus 4.8 (1M context) --- plugins/highway_3d/screen.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/highway_3d/screen.js b/plugins/highway_3d/screen.js index 91c534b..622d173 100644 --- a/plugins/highway_3d/screen.js +++ b/plugins/highway_3d/screen.js @@ -459,7 +459,7 @@ // Create a Butterchurn background controller bound to a wrap element. function _bcCreateController(wrap, sizeProvider, audioProvider) { - const ctrl = { viz: null, actx: null, guitar: null, map: null, keys: [], cycle: 0, dead: false, lastW: -1, lastH: -1, canvas: null, backdrop: null, scrim: null, tint: null }; + const ctrl = { viz: null, actx: null, guitar: null, map: null, keys: [], cycle: 0, dead: false, lastW: -1, lastH: -1, canvas: null, backdrop: null, scrim: null, tint: null, wrap: wrap }; // Layered DOM in the wrap, all BEHIND the transparent 3D highway: // backdrop(z-4 dark) → bc canvas(z-3) → tint(z-2 instrument color) → scrim(z-1 lane dim) const mkLayer = (cls, css) => { const d = document.createElement('div'); d.className = cls; d.style.cssText = css; wrap.appendChild(d); return d; }; @@ -661,6 +661,16 @@ if (_bcPanel && _bcPanel.parentNode) _bcPanel.parentNode.removeChild(_bcPanel); if (_bcPane && _bcPane.parentNode) _bcPane.parentNode.removeChild(_bcPane); _bcPanel = null; _bcPane = null; _bcListEl = null; _bcFilterEl = null; _bcPaneOpen = false; + } else if (_bcPrimary && _bcPrimary.wrap) { + // Splitscreen: a controller other than this one is still + // alive. The singleton panel was parented to THIS (now + // destroyed) wrap, so re-home it onto the surviving primary's + // wrap — otherwise the panel is orphaned on the dead wrap and + // the surviving highway is left with no visualizer controls + // (_bcEnsurePanel only runs at controller creation). It moves + // the existing panel+pane when connected, or rebuilds them on + // the survivor if this wrap was already detached. + try { _bcEnsurePanel(_bcPrimary.wrap); _bcUpdatePanelPreset(); } catch (e) {} } }, };