Repository navigation
Description
Summary
I would like to propose Scorecard for inclusion in FeedBack's curated plugin list. It turns Note Detection's per-note judgments into a Rock Band-style performance HUD: a score, a streak and ×1–×4 multiplier, an early/late timing gauge that can set your latency and A/V offsets, hit/miss marks on the highway, and a history of every run per song and arrangement.
- Repository: https://github.com/mattromano/feedBack-plugin-scorecard
- Plugin ID:
scorecard - Current version:
1.0.0 - Category:
practice - License: MIT
- Minimum FeedBack version:
0.3.0-alpha.1 - Requires: the Note Detection plugin (richer data with notedetect ≥ 1.33, feat(events): richer notedetect:hit/miss payloads for stats plugins feedBack-plugin-notedetect#78)
What it shows while you play:
- Streak box (left of the highway): note streak, ×1–×4 multiplier with progress pips, run and song best streak, the score, an "on fire" glow at 50 / 100 / 200 in a row, milestone and "streak ended" popups.
- Stats card (right): accuracy to two decimals, last-50 form, single-note vs chord accuracy, pitch tendency, why notes were missed; this song's best accuracy / best score / last run / pace vs best and a sparkline; the current section's pass vs its best; weak techniques and per-string accuracy.
- Timing gauge: the last 24 timed notes as early/late ticks with their median, and the Audio Latency Offset that would centre them, with an Apply button. An A/V check compares playing by ear with playing by eye and suggests the A/V offset that lines them up.
- Solos: a live solo meter and glow on sections named "solo" — or, when a chart names none, on sections that look like a solo (mostly single notes, fast, not repeated, up the neck) — with a bonus and a rating at the end.
- Hit / miss marks through the note-state provider: hits flash, misses stay marked while they scroll past, partly played chords light amber.
- End-of-song card: full combo / new best / practice-run banner, the score and where it came from, solos and technical runs, sections vs their bests, weakest techniques, per-string accuracy.
- Compact card in a multiplayer room; hidden on Drums arrangements.
Scoring: 50 per note (per rung string for chords) × timing (≤ 25 ms ×1, ≤ 50 ms ×0.8, else ×0.6), +25 % bends/slides, +100 % harmonics, +200 % pinch harmonics, ×1.5 in dense technical passages, × streak multiplier, × speed and difficulty (capped at 1), plus solo bonuses.
How it works
- Input: the public
notedetect:hit/notedetect:misswindow events. Only counted, main-player judgments (counted !== false,isDefault !== false) are scored. With notedetect ≥ 1.33 it uses per-string chord results, technique names, latency and A/V offset; with an older notedetect it still scores and records runs, but per-string bars count single notes only and no latency value is suggested. - Chart data:
getFilteredNotes()/getFilteredChords()(the notes the player is shown),getSections(),getBeats(),getMastery()for techniques, sections, technical passages and solo detection. - Runs: a run is saved when the song ends, another song or arrangement loads, Note Detection resets its stats, or the page closes (≥ 20 judged notes). It counts toward bests only when complete, unlooped and at 1.00×. History is a small SQLite file (
scorecard.db) undercontext["config_dir"], declared insettings.server_filesso Settings export/import carries it. - Core stats: core already records plays, accuracy and XP from Note Detection's events (
static/v3/stats-recorder.js→POST /api/stats), so Scorecard never posts there; its song endpoint readsmeta_db.get_song_stats()to show core's play count next to its own history.
Compliance and validation
- The manifest validates against feedBack-plugin-spec (
tools/validate.py; CI calls the reusableplugin-validate.yml). - CI also runs the org's
reusable-ci.yml:npm test(node --checkof every module + 49node --testtests for the score engine, multiplier, solo detection, timing classification and gauge, run tracking and the hit/miss latch) and 25 pytest tests for the routes. - ES modules (
scriptType: "module"), no build step, every file well under 1,500 lines. Declaresplugin-runtime-idempotent.v1: a second evaluation finds thewindow.__feedBackScorecardsingleton and installs nothing. - No per-frame DOM work: no
requestAnimationFrameloop, noMutationObserver. Elements are created once and cached; updates are event-driven (one coalesced render per judgment burst) plus a 250 ms tick that only runs on the player screen and only writes nodes whose content changed. - No source rewriting, no wrapped host functions, no patching of other plugins. The HUD is an overlay inside
#player(z-index 15, under the v3 chrome); the controls go intofeedBack.ui.playerControlSlot(). - Self-hosted plain CSS via
styles, all selectorssc-*. No Python dependencies beyond the host's FastAPI; no external network access. Routes validate every input and log throughcontext["log"]. - Verified in a browser against core
main(eef58c8) with the notedetect Documented Python-subprocess auto-restart policy (or a clear "we don't auto-restart" st... #78 build: syntheticnotedetect:hit/missevents drive the card, streak, score, gauge Apply buttons, solo meter, highway marks on the 2D and 3D highways, the multiplayer compact mode, the Drums hide, and the end-of-song card.
Public APIs used
- Note Detection:
notedetect:hit/notedetect:missevents,noteDetect.getStats()(to notice a stats reset),noteDetect.isEnabled(),noteDetect.applySettings({ latencyOffset })(the documented public setter) - Highway:
getFilteredNotes/Chords()(fallbackgetNotes/Chords()),getSections(),getBeats(),getTime(),getAvOffset(),hasPhraseData(),getMastery(),getSongInfo(),setNoteStateProvider()/getNoteStateProvider() window.feedBack:on(screen:changed,song:loading,song:ready,song:ended,song:stop,song:play,song:seek,song:arrangement-changed,loop:restart),emit('scorecard:run'),currentSong,getLoop(),holdAutoExit()(while the results card is open),ui.playerControlSlot(),diagnostics.contribute()window.setAvOffsetMs()for the A/V Apply button (the only setter core exposes today; see below)- The
<audio>element'splaybackRate(speed factor) - Backend context:
config_dir,meta_db.get_song_stats,log - Routes:
GET /api/plugins/scorecard/song,POST /api/plugins/scorecard/run,GET /api/plugins/scorecard/bests
Known limitations
- Hit/miss marks go through the single note-state provider slot. Scorecard composes with Note Detection's provider (it keeps its sustain glow and falls back to its answers), but how a state looks is up to the renderer: the built-in 2D highway honours the colours (white hit flash, amber partial chord); the 3D highway ignores
color, and no renderer can make a hit gem vanish. - Note Detection's own HUD (accuracy, streak, its own score) stays on screen next to Scorecard's; there is no public way to slim it down.
- The speed factor reads the
<audio>element's rate; on desktop native playback it may not reflect the speed slider. - Scorecard's score is its own; it doesn't replace Note Detection's score or core's
/api/statsscore.
Review request
Please confirm:
- whether a curated-list entry is the right path, or whether you'd prefer the repository transferred into the
got-feedbackorg (I'm happy to transfer it); - whether the API gaps below are welcome as issues/PRs (I can open them); and
- whether core's
/api/statsshould eventually accept a plugin-supplied score (so the dashboard shows the Scorecard score) or stay accuracy-based.
I'm happy to address review feedback and keep the plugin compatible with future FeedBack releases.
Public API gaps found (each a candidate core / notedetect issue)
- core — note-state provider is a single last-wins slot. A second consumer (Scorecard) must chain the previous provider by hand via
getNoteStateProvider(), and the result depends on load order (notedetect only registers if the slot is empty or its own). Candidate: a provider stack/priority (addNoteStateProvider(fn, { priority })) with documented merge rules. - core / highway_3d — provider contract is too narrow for game-style feedback.
coloris ignored by the 3D highway (and by every renderer formiss); there is no "consumed" state (hit gem disappears in a burst), nopartialstate for chords, and nottl/past-the-line hint so a missed gem can keep scrolling. Candidate: extend{ state, alpha, color }withstate: 'consumed' | 'partial'(or astylefield) and honourcolorin highway_3d. - core — no public playback-rate getter or event. Speed-aware plugins read
<audio>.playbackRate(wrong on desktop native playback, where the rate lives on the privatejucePlayer). Candidate:window.feedBack.getPlaybackRate()+ asong:speed-changedevent. - core — A/V offset setter is only a legacy window global.
window.setAvOffsetMsexists for inline handlers;highway.getAvOffset()reads it. Candidate:window.feedBack.setAvOffset(ms)/getAvOffset()on the namespaced API (and anav-offset:changedevent so gauges can reset without polling). - notedetect — no side-effect-free settings getter. The only way to read
latencyOffsetoutside a judgment isapplySettings({}), which also persists settings. Candidate:noteDetect.getSettings()(the headless harness already has one) — today Scorecard relies onlatencyMsin each judgment. - notedetect — no "stats reset" event. Restart, seek-back ledger rollback and re-enable reset the counters silently; consumers infer it from
getStats()totals going down. Candidate:notedetect:reset(with a reason) on window / the bus. - notedetect — HUD can't be slimmed when another plugin presents the stats. Accuracy, streak, multiplier and score show twice. Candidate: a
hud: 'full' | 'minimal' | 'off'setting reachable throughapplySettings, or a way for a HUD plugin to claim the stats surface. - core — no player HUD overlay slot. Overlays append to
#playerper docs/plugin-v3-ui.md; afeedBack.ui.playerOverlaySlot()(sized to the highway, z-ordered under the chrome) would spare plugins from guessing positions around the Up-Next pill and Note Detection's HUD.
🤖 Generated with Claude Code
Activity