mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-22 21:01:40 +00:00
main
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5aa336961c |
feat(highways): per-splitscreen-panel Camera Director cameras
Make the three 3D highways read the Camera Director bridge per panel so each splitscreen panel renders its own camera (independent orbit/height/zoom/tilt/pan), instead of all panels sharing the focused camera. - Add a shared `_freeCamFor(canvas)` resolver to each highway: prefer this panel's entry in `window.__h3dCamCtlPanels[panelIndexFor(canvas)]`, fall back to the global `window.__h3dCamCtl`, else null (100% stock). Defensive on the splitscreen global name (feedBackSplitscreen || slopsmithSplitscreen), NaN-safe, allocation-free. - highway_3d (guitar): source `_freeCam` from the resolver (was global-only). - keys_highway_3d: adopt the bridge for the first time — layer dolly/height/orbit + pan/pitch offsets onto the pan/zoom follow rig at the camera write. - drum_highway_3d: adopt the bridge — new per-frame `applyCamera()` folds the static base pose + kick-pulse dip + free-cam offsets. - In a follower (popped-out) window there is one panel, so the resolver yields whatever camera the plugin set in that window; no highway change needed for pop-out. Camera Director absent → resolver returns null → renderers behave exactly as before. Bump each plugin patch version. Existing plugin tests pass (drum 15, keys 30); the keys "default look unchanged" test confirms the stock path is byte-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Kris Anderson <topkoa@gmail.com> |
||
|
|
e446b05a99
|
feat(keys_highway_3d): key layout modes, lane-color opacity & octave lines (#803)
* feat(keys_highway_3d): sharp-layout modes, lane-color opacity, octave lines Add a Highway layout section to the settings with a rebuilt way to draw sharps/flats and lanes on the 3D piano highway: - Sharps & flats layout (keys3d_bg_sharpMode): floating (original raised plane), flat (one plane, zero-overlap piano-shaped tiled lanes with the naturals evened out), and realistic (one plane, bars sized like the physical keys). All geometry lives in pure laneSpanFlat()/laneSpanReal() helpers. Default: realistic. - Lane color opacity (keys3d_bg_laneOpacity, 0-1): fades the pitch-class lane tint; at 0 it is a dark floor with guide lines only at the key-block boundaries (E-F and each octave), toward 1 full vivid colored lanes. The strips, per-lane separators and block lines crossfade with the value. Default: 0. - Octave separators (keys3d_bg_octaveGaps, default on) and Octave line contrast (keys3d_bg_octaveContrast, 0-1): the B->C octave line is a dark layer scaled by lane opacity plus a bright layer scaled by its inverse, so it auto-shifts dark->bright as the lanes fade. Settings re-read on init() so they apply on the next chart build. All other behavior (MIDI scoring, palettes, camera, themes, hit feedback) is unchanged. Unit tests cover the new defaults, the sharp-mode setting, and the lane geometry (tiling/evening for flat, uniform/overlap for realistic). Signed-off-by: LegionaryLeader <legionaryleader@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update plugins/keys_highway_3d/settings.html Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update plugins/keys_highway_3d/screen.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix(keys_highway_3d): don't trim active-range boundary lane when neighbor sharp is out of range laneSpanFlat() trimmed a white key's edge for its neighboring black key's lane even when that neighbor midi fell outside range.activeLow..range.activeHigh — the neighbor's lane is never drawn (see the activeLow/activeHigh skip around the lane-strip loop), so the trim left a dark, unfilled sliver at the active-range boundary with no sharp lane to fill it. Gate the trim on the neighbor being in-range; callers that don't pass a range (e.g. the raw-tiling unit tests) keep the prior unconditional-trim behavior. Also add the CHANGELOG entry for this PR's feature set, following the existing keys_highway_3d wording convention (no plugin-local CHANGELOG exists; plugin.json was already bumped 0.1.2 -> 0.2.0 by the original commits). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Signed-off-by: LegionaryLeader <legionaryleader@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: byrongamatos <xasiklas@gmail.com> |
||
|
|
a20dca21bb
|
feat(keys_highway_3d): add note-colour palettes and selectable camera angles (#794)
Some checks are pending
ship-ci / ci (push) Waiting to run
* feat(keys_highway_3d): add note-colour palettes and selectable camera angles
Give the 3D keys highway player-facing view options and a tuned default
look, so the piano highway is readable out of the box and customisable
from the settings panel without touching code.
Note colours (settings -> Note colours, `keys3d_bg_palette`):
- Octaves (new default): each octave its own hue climbing the rainbow,
darker sharps, so pitch height reads at a glance on any note range.
- Rainbow: the original per-pitch table (colours unchanged).
- Vivid / Pastel: per-pitch variants.
- Emerald / Ice: single-hue two-tone (uniform naturals, darker sharps).
The pick drives the notes, key glow, lane guides and hit flames, live.
Camera (settings -> Camera angle, `keys3d_bg_camera`):
- Classic (the original low rig) / Elevated / Overhead (new default).
- Height, distance and tilt fine-tune sliders nudge the base vantage the
auto-pan/zoom follow-motion orbits; presets apply live.
The new defaults are opinionated for plug-and-play (octaves palette,
overhead camera, tilt -0.6); anyone who prefers the original look can
pick Rainbow + Classic. Settings changes are re-read on init() so they
apply on return from the settings screen, not only after a relaunch.
Scoring, hit-timing and MIDI handling are untouched -- these are purely
visual. Numeric FX keys clamp to declared ranges (FX_RANGES); the pure
colour/camera helpers are covered by unit tests (node --test).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: LegionaryLeader <legionaryleader@gmail.com>
* fix(keys_highway_3d): make Classic camera reproduce the original rig + drop per-frame camera alloc
Review follow-ups on the palettes/camera feature:
- "Classic" preset now reproduces the historical rig exactly. The tuned
plug-and-play downward aim (camTilt -0.6 x CAM_TILT_UNITS = -33) is baked
into CAM_PRESETS.overhead.lookY, and camTilt now defaults to 0 (neutral).
The default overhead look is byte-identical (effective lookY still -33),
but "pick Classic for the original look" is now actually true instead of
leaving a -33 down-tilt applied. settings.html tilt slider defaults to 0.
- _rig() writes into a hoisted reusable object instead of allocating a fresh
{y,z,lookY,lookZ} literal every frame, honoring the module's documented
"no per-frame allocations in draw()" discipline. Callers read it
synchronously and never retain it, so one shared instance is safe.
Tests updated for the neutral camTilt default; adds an invariant test that
the default overhead framing is unchanged and Classic + neutral tilt == the
historical LOOK_Y. Full JS suite green (1069 pass).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Signed-off-by: LegionaryLeader <legionaryleader@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
|
||
|
|
4b6cbe8b11
|
Fix 3D drum/keys highways not resizing on fullscreen under splitscreen (#723)
The guitar/bass highway_3d renderer self-detects panel-canvas size changes in its draw() loop and re-runs applySize() every frame, because the splitscreen host overrides hw.resize and never calls renderer.resize(). The drum and keys highways lacked that fallback — they only re-framed when the host explicitly called resize(w, h) — so their panels stayed framed for the pre-fullscreen size while the guitar/bass panels adapted. Symptom: a too-small, off-center highway in the drum/keys panels after maximizing a split-screen session. Port highway_3d's per-frame drift check into both draw() loops: re-apply on backing-store change (canvas.width/height) AND on CSS-box drift (clientWidth/clientHeight vs the last applied logical size, throttled to every 10th frame). Track _lastHwW/_lastHwH + _appliedW/_appliedH per instance and reset them in destroy() so a reused instance re-frames on the next song. plugins/drum_highway_3d -> 0.3.1, plugins/keys_highway_3d -> 0.1.1. Tests: tests/js/drum_keys_highway_3d_resize_reframe.test.js. Signed-off-by: Kris Anderson <topkoa@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
beefd7bc09
|
feat(keys_highway_3d): foundation for guitar-highway visual parity (K1) (#696)
- setPixelRatio at last: DPR (cap 2; 1.25 when >1 instance) x host adaptive bundle.renderScale — HiDPI displays were rendering at CSS resolution and upscaling (soft/aliased) - Bloom: port _bloomEnsure/_bloomDispose (UnrealBloomPass 0.65/0.5/0.82, MSAA HalfFloat target, ACES<->None switch); hit-line, flames and consume-glow benefit immediately; direct render is the degrade path - First settings panel: settings.html (graphics category) with a live Glow (bloom) toggle; FX scaffold (FX_DEFAULTS/readFxSettings/ window.keys3dSetFx, keys3d_bg_* keys, keys3d:settings event) - Combo/accuracy/best-streak DOM HUD (drum_highway_3d pattern), gated on a live MIDI session - tests/fx_settings.test.js (3 tests; vm harness) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1e2d5a6162
|
Bundle drum_highway_3d + keys_highway_3d as in-tree core plugins (#693)
* Initial commit — clean relaunch * Initial commit — clean relaunch * Remove external game/format terminology from docs and code Reword references to the external game and its proprietary file formats in comments, docstrings, UI text and identifiers; no behaviour change. * Repoint dead slopsmith URLs -> got-feedback Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(midi): consume core midi-input domain + session robustness fixes (#2) * feat(midi): consume the core midi-input domain instead of private requestMIDIAccess (#881) Route Web-MIDI device access through window.slopsmith.midiInput (discover/list/select/open/close) rather than a private navigator.requestMIDIAccess(). One shared device-access boundary with piano/drums/onboarding; retires this plugin's private Web-MIDI. The note-detection 'midi' exact-verdict provider role and the audio-input source export are preserved (now backed by midi-input source data). Saved pick ({id,name}) stays compatible — domain sourceId == the old MIDIInput.id. Live listener is the domain handle's addListener/removeListener; the async init/resume/pause gate and device-vanish handling are kept. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): stop exporting MIDI into the audio-input domain Keys MIDI now lives in the dedicated midi-input domain (#881). Continuing to register pseudonymized 'midi-input-N' sources into audio-input polluted audio device pickers — notably the onboarding guitar audio-input dropdown showed MIDI devices under cryptic names. Unregister any leftovers and register none. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): guard async connect races, denied-discovery latch, key consistency Codex preflight findings on the midi-input consumption: - Don't latch `_midiReady` when discover() resolves denied/unavailable — only on a handled outcome — so reopening retries the permission prompt. - Add a generation guard to async `_midiConnect`: a slower open() from an earlier selection can resolve after a device/None switch and install a stale handle/listener. Discard (and close) superseded opens. - Carry the domain `logicalSourceKey` on the source/selection descriptor and use it for select/open/close instead of synthesizing `web-midi::<id>`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): invalidate pending opens on detach; clear phantom selection; manifest Codex re-review (round 2): - A detach driven by device removal (sources-changed) didn't advance _midiConnectSeq, so a pending _midiConnect open could resume and install a handle/listener for a now-gone source. Bump the generation inside _midiDetach and capture myGen after it, so any later detach supersedes an in-flight open. - If mi.open() yields no handle, _midiInput stayed set — a phantom connected device the render loop's miss-sweeping would penalize. Clear it on the no-handle and catch paths. - plugin.json: replace the stale audio-input provider role (the removed MIDI-into-audio-input export bridge) with the midi-input requester it now uses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): make _midiResume idempotent under multiple live instances Codex re-review (round 3): with the domain addListener API, a second live renderer instance (splitscreen/overlapping lifetimes) calling _midiResume() while already active could register the same listener again and double-deliver a MIDI note to the focused instance (hit + duplicate misses). Return early when already active rather than relying on the provider's Set-backed de-dup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): release domain session on teardown; counter-free legacy source cleanup Codex re-review (round 4): - [P2] Last-instance teardown only detached the listener and never released the midi-input domain session, leaking the requester ref. Add _midiReleaseSession() (delegates to _midiDetach: close + null + generation bump) at the destroy site; re-mount's _midiInit auto-connects from the saved pick. - [P3] The legacy audio-input MIDI-source cleanup looped over the module-local _aiRegisteredCount, which resets to 0 on an in-page upgrade so the prior build's 'keys-midi:input-N' entries were never unregistered. Iterate a fixed bound over the known sourceId pattern instead (unregister of an absent source is a no-op). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): gate miss-sweep on live handle; don't open without a live renderer Codex re-review (round 5): - Miss-sweeping was gated on _midiInput, set as soon as a device is picked, but the async mi.open() may still be pending (slow / permission prompt) — notes passing during that window banked false misses. Gate on _midiHandle, truthy only after a handle is opened and wired. - A settings-only ensure-init (or a discover resolving after the last instance was torn down) could open a midi-input session with no renderer to release it. Gate mi.open() on _instances.size > 0; the pick is saved and a later mount re-runs auto-connect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): guard open-failure clobber + don't clobber saved device on unplug Bringing keys-highway-3d in line with the piano/drums hardening: - The _midiConnect catch cleared _midiInput unconditionally; a stale older open's rejection could wipe a newer connect's _midiInput/_midiHandle (and leak the handle). Only clear when myGen === _midiConnectSeq. - The sources-changed recovery called _midiAutoConnect(), whose fallback persists a substitute device — overwriting the user's saved pick on a transient multi-device unplug. Parameterize _midiAutoConnect(allowFallback); recovery passes false (reconnect the saved device only, never a persisted fallback). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): keep first-hotplug working; don't tear down a live session on re-init Codex round-11: - The unplug-recovery _midiAutoConnect(false) skipped ALL fallbacks, which broke first-hotplug (open visualizer with no keyboard, plug one in → never picked → no connect). Skip the substitute only when a saved pick exists but is absent (preserve it); allow the fallback when nothing was ever picked. - _midiInit re-ran _midiAutoConnect on every ready re-init (settings/splitscreen), tearing down the live handle + releasing held keys. Only re-connect when there's no live handle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(midi): consume core midi-input domain + session robustness fixes (#2) * feat(midi): consume the core midi-input domain instead of private requestMIDIAccess (#881) Route Web-MIDI device access through window.slopsmith.midiInput (discover/list/select/open/close) rather than a private navigator.requestMIDIAccess(). One shared device-access boundary with piano/drums/keys/onboarding. Saved pick ({id,name}, with legacy id-only fallback) stays compatible — domain sourceId == the old MIDIInput.id. The async in-flight init guard, _midiActive teardown gate, device-vanish handling, and settings device APIs are preserved; the live listener is the domain handle's addListener/removeListener. Degrades to no-MIDI when the domain is absent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): guard async connect races, denied-discovery latch, key consistency Codex preflight findings on the midi-input consumption: - Don't latch `_midiReady` when discover() resolves denied/unavailable — only on a handled outcome — so reopening retries the permission prompt. - Add a generation guard to async `_midiConnect`: a slower open() from an earlier selection can resolve after a device/None switch and install a stale handle/listener. Discard (and close) superseded opens. - Carry the domain `logicalSourceKey` on the source/selection descriptor and use it for select/open/close instead of synthesizing `web-midi::<id>`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): invalidate pending opens on detach; clear phantom selection; manifest Codex re-review (round 2): - A detach driven by device removal (sources-changed) didn't advance _midiConnectSeq, so a pending _midiConnect open could resume and install a handle/listener for a now-gone source. Bump the generation inside _midiDetach and capture myGen after it, so any later detach supersedes an in-flight open. - If mi.open() yields no handle, _midiInput stayed set — a phantom connected device the render loop's miss-sweeping would penalize. Clear it on the no-handle and catch paths. - Declare the midi-input requester capability (degrade-noop) in plugin.json. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): make _midiResume idempotent under multiple live instances Proactively mirror the keys-highway-3d round-3 fix (same multi-instance lifecycle and identical code): a second live renderer instance calling _midiResume() while already active could double-register the listener and double-deliver a MIDI hit. Return early when already active. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): release the domain session on final teardown Mirror the keys-highway-3d round-4 fix: last-instance teardown only detached the listener and never released the midi-input domain session, leaking the requester ref. Add _midiReleaseSession() (delegates to _midiDetach: close + null + generation bump) at the destroy site; re-mount's _midiInit auto-connects. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): gate miss-sweep on live handle; don't open without a live renderer Codex re-review (round 5): - [P2] drumH3dEnsureMidiInit (settings-only, no renderer) auto-connected and opened a midi-input session that destroy()/_midiReleaseSession would never run to release — held until reload. Gate mi.open() on _instances.size > 0; the pick is saved and a later renderer mount re-runs auto-connect. - Mirror the keys-highway-3d miss-sweep fix: gate accumulation on _midiHandle (live wired session), not _midiInput (set before the async open resolves), so notes during a pending open don't bank false misses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): guard open-failure clobber + reconnect saved device on replug Bringing drum-highway-3d in line with the piano/drums hardening: - The _midiConnect catch cleared _midiInput unconditionally; a stale older open's rejection could wipe a newer connect's _midiInput/_midiHandle. Only clear when myGen === _midiConnectSeq. - The sources-changed handler only refreshed the list (never reconnected), so a replug of the saved device didn't reattach. Reconnect on sources-changed via _midiAutoConnect(false) — saved device only, no fallback, so a transient unplug can't switch to / persist another input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): keep first-hotplug working; don't tear down a live session on re-init Same two round-11 fixes as keys-highway-3d (shared structure): the recovery _midiAutoConnect(false) now allows a fallback when nothing was ever picked (first-hotplug) while still preserving a saved-but-absent pick; and _midiInit only re-connects when there's no live handle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): select by logicalSourceKey, not provider-local sourceId (#4) Codex review of the midi-input migration: resolving a selection with find(s => s.id === id) treats the provider-local sourceId as globally unique, so when the core midi-input domain aggregates multiple providers two devices sharing a sourceId can't be distinguished — the wrong device opens and unplug detection can miss that the selected key vanished. Thread the globally-unique logicalSourceKey through save/read, _midiConnect, auto-connect reconnect, the public keysH3dSetMidiInput entry, and the capability _aiOpen path, preferring the key and keeping the bare sourceId / name only as a legacy fallback. Mirrors the already-merged drums/piano migration (_midiResolveSaved). Self-reviewed against that reference. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi): select by logicalSourceKey, not provider-local sourceId (#3) Codex review of the midi-input migration (same issue as keys-highway-3d): resolving a selection with find(s => s.id === id) treats the provider-local sourceId as globally unique, so when the core midi-input domain aggregates multiple providers two devices sharing a sourceId can't be distinguished — the wrong device opens and unplug detection can miss the vanished key. Thread the globally-unique logicalSourceKey through save/read, _midiConnect, the auto-connect reconnect, and the public drumH3dSetMidiInput entry, preferring the key with the bare sourceId / name only as a legacy fallback. Mirrors the already-merged drums/piano migration. Self-reviewed. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: add plugin metadata (category, description, thumbnail) (#5) * chore: add plugin metadata (category/description/thumbnail) Refs got-feedback/feedBack#571 * chore: add placeholder thumbnail Refs got-feedback/feedBack#571 * chore: add plugin metadata (category, description, thumbnail) (#4) * chore: add plugin metadata (category/description/thumbnail) Refs got-feedback/feedBack#571 * chore: add placeholder thumbnail Refs got-feedback/feedBack#571 * fix(viz): register feedBackViz_<id> so the player viz picker lists this plugin (#5) After the slopsmith->feedBack rename the factory was still registered as window.slopsmithViz_<id>; the host viz picker only looks up window.feedBackViz_<id> (no slopsmithViz alias exists) and silently skipped this plugin. Add the feedBackViz_ global (aliased to the existing slopsmithViz_ one for back-compat). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(viz): register feedBackViz_<id> so the player viz picker lists this plugin (#6) After the slopsmith->feedBack rename the factory was still registered as window.slopsmithViz_<id>; the host viz picker only looks up window.feedBackViz_<id> (no slopsmithViz alias exists) and silently skipped this plugin. Add the feedBackViz_ global (aliased to the existing slopsmithViz_ one for back-compat). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(plugins): bundle drum_highway_3d + keys_highway_3d as in-tree core plugins Import both 3D highway plugins from their standalone repos via git subtree (history preserved), ahead of the visual-parity epic that ports the guitar highway's polish to them. - .gitignore: !plugins/drum_highway_3d/ + !plugins/keys_highway_3d/ exceptions - keys plugin.json: "bundled": true (drum already had it) - CI: JS test step gains 'plugins/*/tests/*.test.js' (+20 keys tests) - static/tailwind.min.css regenerated (core build scans plugins/**) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(drum_highway_3d): narrow Auto-mode predicate so bundling can't steal guitar arrangements has_drum_tab is pack-level; first-match-wins Auto order sorts this plugin before highway_3d. Claim only drum arrangements, or packs nothing more specific can render (Codex preflight P2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: CHANGELOG — note the deliberate Auto-predicate narrowing Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(drum_highway_3d): MIDI lifecycle — no false misses on mid-song connect, promote survivor on destroy - _missSweepFloor exempts notes that passed before the device wired up from the miss sweep (lowered on seek-back; cleared with scoring resets) - destroy() promotes a surviving instance to _activeInstance so splitscreen panel teardown doesn't drop all MIDI routing (Codex preflight round 2, both pre-existing in the imported code) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Sin <deathlysin@outlook.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |