* feat(highway_3d): add Butterchurn visualizer background style
Adds an opt-in "Butterchurn (visualizer)" option to the 3D Highway plugin's
Background-style dropdown. When selected, the highway renders over a WebGL
MilkDrop (Butterchurn) canvas that reacts to your playing (guitar input on
desktop, the song <audio> spectrum in the browser) and the chart (beat/note/
chord accents + instrument-color tint). The default stays 'particles', so
existing users see no change until they pick it.
Integrates the standalone "3D Highway + Butterchurn" mod into the bundled
renderer as the 'butterchurn' bg-style (not a fork):
- a self-contained _bc* controller that lazy-loads the vendored butterchurn
libs only when the style is selected; mount/unmount is driven idempotently
by the existing bg-style lifecycle (_bcSyncMode in _bgMountStyle) plus an
explicit teardown in destroy()
- the renderer uses alpha:true with the transparent clear gated on the mode,
so every other bg style stays byte-identical (opaque clear)
- the fog-scenery <audio> tap is disabled while active to avoid a double
createMediaElementSource on #audio
- the mod's slopsmith* globals are adapted to the current feedBack* names and
the vendored asset URLs repointed to /api/plugins/highway_3d/assets/
Vendors butterchurn.min.js + butterchurnPresets.min.js (MIT) + viz-worklet.js
under assets/vendor/; see plugins/highway_3d/NOTICE for attribution.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Q9BpGYqUaga9ZJyS3dDPq
* fix(highway_3d): anchor Butterchurn panel to the highway, centered
Addresses three issues found testing the visualizer control panel:
- Attach the panel + preset pane to the 3D highway's `wrap` (position:absolute,
pointer-events:auto) instead of position:fixed on document.body, so they sit
on the highway's right edge and only exist while the highway is on-screen
(no longer linger on the main menu / float at the app edge).
- Re-home the singleton panel to the active highway wrap on mount, so it follows
whichever highway is showing (e.g. moves off Virtuoso's embedded highway onto a
normal song's highway) instead of sticking to the first one created.
- Center it vertically (top:50% + translateY(-50%), folded into the slide
transform) so a top overlay element no longer covers it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Q9BpGYqUaga9ZJyS3dDPq
* fix(highway_3d): harden Butterchurn audio + lifecycle (review #597)
Browser audio reactivity now REUSES the highway's existing shared analyser
(the fog scenery's #audio / stems side-chain tap) instead of opening a
second createMediaElementSource on #audio. The old _bcBrowserSource path:
- threw InvalidStateError when the fog tap already owned #audio (default
config), leaving the visualizer non-reactive in the browser, and could
permanently disable fog reactivity if it tapped first (one-shot/element);
- rerouted the song through a fresh, possibly-suspended AudioContext, which
could MUTE playback when butterchurn was selected mid-song;
- ignored the stems analyser, so it saw only silence on sloppak songs.
_bcCreateController now takes an audioProvider (wired to _bgGetAnalyser) and
connectAudio()s the shared AnalyserNode (a passthrough, so the fog's own
reads are undisturbed).
Also:
- destroy() now closes the AudioContext when we own it (desktop / browser
fallback), fixing a per-mount leak that hit the browser ~6-context cap
after a few style toggles. The shared (fog-owned) context is never closed.
- _bgApplyVenueSceneFog keeps the clear transparent while butterchurn is
active, so the venue scene no longer occludes the visualizer.
- _bcLoadLib no longer caches a rejected promise, so a transient vendor-load
failure can be retried instead of disabling the feature for the session.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(highway_3d): Butterchurn lifecycle + audio re-bind (Codex preflight)
Local Codex preflight on the Butterchurn feature flagged four issues; all fixed:
- WebGL context leak on teardown: destroy() (and the async-init failure path)
now call _bcReleaseCanvasGL() to force WEBGL_lose_context before dropping the
canvas, so repeated mount/toggle cycles can't exhaust the browser's WebGL
context cap.
- Stale shared analyser across songs: the browser path captured the analyser
once at mount, so a sloppak stems swap (new analyser, often new context) left
the visualizer reacting to a dead node. update() now compares the live
_bgGetAnalyser() against what the controller actually bound (boundAnalyser(),
guarded by ready()) and either reconnects (same context) or rebuilds the
controller (context changed) via the proven destroy()+_bcSyncMode paths.
- Half-mounted controller on createVisualizer failure: the async .catch now
cleans up (closes an owned AudioContext, removes layers, marks dead) and
_bcSyncMode retries when bcCtrl.dead(), instead of leaking and never recovering.
- _bcFfIdx off-by-one dropped accents landing exactly on a seek/loop target
time; it now uses strict < so the update walkers fire the boundary event.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: ChrisBeWithYou <christian.a.cowan@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(highway_3d): add one-click string-color presets
Adds 12 named string-color presets (Warm→Cool, Vivid, Colorblind-friendly,
Neon, Accessible, Warm Ember, Tape Deck, CRT Green/Amber, Pitch Ramp, Sunrise)
selectable from the 3D Highway settings panel.
Extends the existing core HWC (highway-color) subsystem in static/app.js with
HWC_PRESETS + applyHighwayStringPreset(), exposed on the existing facade as
window.feedBack.highwayColors.{presets, applyPreset}. The plugin settings page
renders the preset buttons from that core list and refreshes the per-string
pickers on apply. Purely additive — stock behavior is unchanged.
Scope: core static/app.js (the shared HWC facade both highways consume) plus the
highway_3d plugin's settings.html / screen.js / CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Q9BpGYqUaga9ZJyS3dDPq
* fix(highway_3d): address review of colour-theming PR
- Rebuild assets/plugin.css so the new `flex-wrap` (preset row) and
`text-[10px]` (theme-dropdown helper) Tailwind classes are actually
compiled, and bump plugin.json 3.26.0 -> 3.27.0 so the <link>'s ?v=
cache-buster fetches the fresh CSS (per the plugin's build rule).
- Replace the mirror-at-every-read hwTheme migration with a one-time
backfill (persist hwTheme := bgTheme on first load, no emit). The two
scene-color axes are now genuinely independent: changing the Background
dropdown no longer silently retints the Highway surface/lane, and the
rendered highway can't disagree with the Highway dropdown value.
- Collapse the duplicated theme id-set in settings.html (two identical
<option> lists + VALID_BG_THEMES) into a single SCENE_THEMES source the
dropdowns and validator are generated from; sync points 4 -> 2.
- Update CLAUDE.md to document the backfill + reduced sync contract.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
* Update GitHub repo references from feedback* to feedBack*
* rename: slopsmith -> feedBack, byron -> got-feedBack
Renames across the entire codebase:
- slopsmith/Slopsmith/SLOPSMITH/SlopSmith -> feedBack/FeedBack/FEEDBACK/FeedBack
- byron/Byron/Byrongamatos -> got-feedBack/got-feedBack/got-feedBack
- /home/byron/ -> /opt/got-feedBack/
- byron@ougsoft.com -> hi@got-feedBack.org
- github.com/byrongamatos/ -> github.com/got-feedback/
- com.byron. -> com.got-feedback.
- SLOPSMITH_ env vars -> FEEDBACK_ with backward-compat fallback
- Protocol/storage strings migrated with read-old/write-new pattern
- window.slopsmith JS API -> window.feedBack (canonical) + backward-compat alias
Refs: #rename-slopsmith
* rename: complete regen against current main + fix backward-compat alias
Regenerated the slopsmith->feedBack / byron->got-feedBack rename on top of
current main (3 commits had landed since the branch: #572/#554/#574),
resolving the four content conflicts in favour of main's newer content
(autoplay/auto-exit, accuracy-badge, Virtuoso re-home, feedpak badge).
Completion fixes on top of the mechanical rename:
- Re-apply rename to post-branch content the original rename never saw:
window.slopsmith(.Tour) consumers in lessons.js / notifications.js /
onboarding-tour.js, and the matching JS + python tests (autoplay_exit,
progression_*, test_feedpak_extension FEEDBACK_* env vars). The test env
vars now match server.py (which reads FEEDBACK_SYNC_STARTUP /
FEEDBACK_SKIP_STARTUP_TASKS), so the sync-startup test exercises the real
path again.
- Restore the window.slopsmith backward-compat alias dropped during conflict
resolution, and move the bus aliases to AFTER the _feedBackExisting merge
block so they reference the fully-assembled object (also fixes the
loop_api.test.js API-surface regex, which the original PR latently broke).
- Drop the stray empty data/web_library.db (runtime DB lives in CONFIG_DIR)
and gitignore it.
- Fix stale tone-source test: feed[dB]ack -> fee[dB]ack to match shipped
source labels.
Verified locally (org CI billing-blocked): JS 819/819 pass; pytest 1669
passed / 1683 collected with 0 import errors; zero residual slopsmith/byron
except the two intentional window.slopsmith aliases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* rename: implement advertised backward-compat + prune dead community plugins
Address gaps where PR #537's "Backward compatibility" section was advertised
but not implemented, and clean up the community plugin list.
Env vars (FEEDBACK_* canonical, legacy SLOPSMITH_* honoured):
- New lib/env_compat.py (getenv_compat / env_flag_compat) + tests. server.py
(_env_flag + all FEEDBACK_* reads), diagnostics_hardware, gp2midi and
tailwind_rebuild now resolve the legacy alias, so existing SLOPSMITH_UI /
SLOPSMITH_PLUGINS_DIR / etc. deployments keep working.
- Fix the rename collapsing plugins/__init__.py and minigames/routes.py from
`FEEDBACK_PLUGINS_DIR or SLOPSMITH_PLUGINS_DIR` into a redundant
`FEEDBACK_ or FEEDBACK_` (the fallback was silently lost).
Storage (app.js update-channel):
- Read feedBack-update-channel, fall back to legacy slopsmith-update-channel,
and clear the legacy key on write — so a user's update-channel preference
survives the rename instead of resetting to "stable".
Community plugin list (README): the rename rewrote third-party repo URLs we
don't own. Probed every one; their owners never renamed, so:
- Restore the 13 live community plugins to their real slopsmith-* names.
- Prune 6 that are 404 to the public (topkoa splitscreen/stems, OmikronApex
tuner, Jafz2001 nam-rig-builder, DeathlySin song-preview, Erikcb91 shuffle).
- Fix a pre-existing Guitar Theory clone-command typo (nam-tone -> guitar-theory).
Verified: env_compat 7/7, JS 819/819, pytest 1690 collected / 0 import errors,
rename-sensitive + startup suites green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: byrongamatos <xasiklas@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switching between the 3D drum highway (renders onto #highway) and the 3D
guitar highway (renders into its own .h3d-wrap overlay) left the previous
drum frame showing through the gap the overlay did not cover.
Core: _setRenderer now replaces #highway on a genuine viz change (keyed on
viz id via _rendererVizKey, not object identity, so benign same-viz
re-installs don't churn the canvas) as well as on a context-type change.
highway_3d: applySize pins the .h3d-wrap overlay to #highway's exact box,
derived from the same getBoundingClientRect measurements that size the
renderer (sub-pixel correct under zoom). Re-pins once the canvas lays out
(init race) and resets to the static anchor in the not-laid-out fallback.
Reviewed locally via codex (5 rounds, converged clean). CI checks are the
known org Actions billing block, not real failures.
window.h3dSetFretSpacing was the only 3D-highway setting that applied via
location.reload(). The SPA boots with #home as the active screen and has
no restore-last-screen mechanism, so the reload ejected the user from
Settings onto the home screen.
Apply it live like every other 3D-highway setting: rebind the module-scope
_h3dFretUniform flag (so panels mounted later this session pick up the new
mode), recompute the two fretX-derived scalars baked at init
(_fretLabelScaleRefW, FRET_WIDTH_MID), and broadcast a 'fretSpacing' change
over the existing _bgEmitChange pub-sub so every mounted panel rebuilds its
board via buildBoard(). Per-frame note geometry already reads fretX live.
Settings copy updated (no longer reloads) and tests/js pin the no-reload /
live-rebuild behavior.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the voicing/fn.rn teaching-mark render for the two new chord-template
fields, in both the 2D and 3D highways:
- Extend the shared pure chordHarmonyLabels() helper (identical in static/highway.js
and plugins/highway_3d/screen.js) to also surface caged ("CAGED: E") and
guideTones ("gt 4,10"), pre-formatted and node-testable. Invalid caged enum and
out-of-range / non-int guide tones are filtered out.
- Draw both, stacked above the existing rn/voicing labels, in distinct colors.
- Gated behind the SAME teaching-marks toggle (_showTeachingMarks 2D /
teachingMarksVisible 3D) — no clutter on the default highway.
Render only — no scoring / NoteVerifier coupling.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(core): carry chord harmony fn + template voicing on the wire (§6.3.1, §6.6)
Add two OPTIONAL per-chord harmony annotations (feedpak 1.7.0), mirroring the
teaching-marks (fg/ch/sd) wire work:
- Chord.fn (instance): {rn, q, deg} harmonic-function object, key-dependent.
Validated by _validate_fn on BOTH decode and emit so a partial / out-of-range
fn (which would fail the schema's required-keys rule) never rides the wire.
Default-omitted, mirroring bend bnv.
- ChordTemplate.voicing (template): key-independent voicing-type string
("open", "triad", "shell", "drop2", "barre", ...). Emitted only when
non-empty; non-string wire values fall back to "".
Display/teaching only — never fed to a grader (honesty rule). fn auto-derivation
is DEFERRED (carry-only): a complete rn/q needs chord-quality analysis, and a
deg-only fn would be schema-invalid, so server.py carries author-provided fn
unchanged. GP import unchanged (no reliable per-chord function/voicing).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(highway): render chord harmony fn.rn + voicing on 2D + 3D (§6.3.1, §6.6)
Draw the chord's harmonic-function Roman numeral (instance fn.rn) and its
template voicing string, stacked above the chord name on both highways. A shared
pure helper chordHarmonyLabels(fn, voicing) formats the two labels (empty when
absent/malformed) and is node-tested against both files.
Both labels are gated behind the EXISTING teaching-marks opt-in
(_showTeachingMarks / teachingMarksVisible bundle flag) — they're chord-level
teaching overlays, same class as sd/ch, so they stay off the default highway.
2D guards the empty-note-chord case; 3D reuses the gold chord-label sprite style.
Render only — no scoring / NoteVerifier path is touched (honesty rule).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-merge review of #538 noted the fg finger numeral rendered unconditionally on
both highways and couldn't be turned off — only sd/ch sat behind the (default-off)
teaching-marks toggle. A user who finds per-note numerals busy had no way to
declutter.
Add a SEPARATE finger-hints gate that keeps fg shown by default but makes it
hideable, independent of the sd/ch opt-in (so the two defaults — fg on, sd/ch off —
coexist; a single boolean can't express that):
- 2D static/highway.js: _showFingerHints (localStorage 'showFingerHints' !==
'false', i.e. default on), a fingerHintsVisible bundle flag, and
get/toggle/setFingerHintsVisible API; gates the fg label.
- 3D plugins/highway_3d/screen.js: mirrors via bundle.fingerHintsVisible !== false
(default on); gates the fg sprite. sd/ch unchanged.
Default-on preserved (absent localStorage / absent bundle flag => shown); only an
explicit false hides fg. Codex-reviewed: clean. Render test 7/7.
Part of got-feedback/feedback#334.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render the three per-note teaching marks on both highways, mirroring the
bend-curve render (#532). Display only — no scoring / NoteVerifier coupling.
- 2D static/highway.js: fg renders by default as a small finger numeral hugging
the gem (T = thumb, 1..4); sd (degree label) and ch (strum bracket connecting
notes that share a ch key, arrow direction from pkd) are opt-in behind a new
`showTeachingMarks` toggle (exposed via toggle/get/set + the bundle's
`teachingMarksVisible` flag). Pure helpers teachingFingerLabel /
teachingDegreeLabel / strumGroupBuckets drive the glyphs. ch bracket is
note-stream-only (chord notes already read as one gesture).
- 3D plugins/highway_3d/screen.js: fg (default) + sd (opt-in, mirrors the 2D
toggle via bundle.teachingMarksVisible) render next to the per-note fret label
via a new pooled sprite (pTeachMarkLbl); _scrChordNote resets fg/sd so chord
notes don't inherit stale marks. ch strum brackets are deferred in 3D (no
cross-note batch pass in the per-note render); 2D covers ch.
Tests: tests/js/highway_teaching_marks.test.js extracts the pure helpers from
both files (extract-and-eval) and asserts label mapping + strum-group bucketing.
Part of got-feedback/feedback#334
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-merge Codex review of the bend-curve PRs (#531/#532) surfaced edge cases:
- GP8 (#531 P2): bnv timing used rn.sustain, which is zeroed for notes <= 0.2s,
so short GP8 bends kept the scalar bn but lost bt/bnv. Use the beat duration
`dur` (matching the GP5 path) so the curve survives.
- 2D highway (#532 P2): bnvNormalizedPoints mapped x over the curve's own t-range
[first,last] instead of the note span, so curves not starting at 0 / ending at
sus were time-distorted. Now maps over [0, sus] (clamped), with a curve-span
fallback when sus<=0 (existing no-sus callers unaffected).
- 3D highway (#532 P3): the sustain ribbon + bend chevron were gated on bn>0, so a
note carrying an authoritative bnv with bn==0 drew no ribbon/marker. Both now
also fire on bnv presence; chevron steps derived from max(bn, bnv peak).
Codex-reviewed: clean (no findings). +1 JS test (sus-relative mapping + fallback).
JS 8/8, 250 core GP/song tests pass.
NB: GP8's short-bend path still lacks a dedicated synthetic-GPIF fixture (same gap
as the GP8 offset-prop-names P3) — _gpx_bend_shape units cover the function; the
fix is the one-line caller change.
Part of got-feedback/feedback#334.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR-B of the bend-shape feature (feedpak §6.2.1). Both highways drew a bend
from the scalar `bn` only; now they trace the authoritative `bnv` curve
([{t, v}]) when present and fall back to the `bn` arc/envelope otherwise.
2D (static/highway.js drawNote): when a note carries `bnv`, draw the real
shape as a contour above the gem (round-trip rises then falls, pre-bend
starts high, release descends — `bt` is implicit in the point shape), with
an arrowhead only when the gesture ends rising. `bnvNormalizedPoints` maps
{t,v} to a 0..1 x span. The scalar-arrow path is preserved unchanged as the
fallback; the peak label is unchanged.
3D (plugins/highway_3d/screen.js): `bnvSampleAt` linearly interpolates the
curve (clamped to its endpoints) and `bendSemisAtTime` samples it when
present, else keeps the synthetic rise→hold→release envelope from `bn`. The
chevron count still comes from the peak. Fixed a stale-scratch hazard: the
reused `_scrChordNote` now resets `bnv`/`bt` (omit-when-default) after
Object.assign, mirroring the existing `fhm` reset, so a chord note without a
curve can't inherit the previous note's contour.
Render-only — no wire/schema change. Pure helpers covered by
tests/js/highway_bend_curve.test.js (interp, clamping, round-trip,
degenerate/empty); node --check passes on both files; full tests/js green.
Part of got-feedback/feedback#334
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reword comments/docstrings/strings and rename identifiers that referenced
the external game and its file formats:
- format-id "psarc" -> "archive"; local vars psarc_path -> song_path,
psarc_base -> tone_base
- lyrics provenance value "sng" -> "notechart" (legacy "sng" still accepted)
- highway_3d fret-ghost scope value "rocksmith" -> "chords" (invalid/legacy
values fall back to the default, preserving behaviour)
- neutralise references in prose, test names/data, .gitattributes and docs
No functional change beyond the renamed identifiers; all Python compiles.