Skip to content

Curated plugin candidacy: Scorecard #1103

Open

Description

@mattromano

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.

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:miss window 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) under context["config_dir"], declared in settings.server_files so 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 reads meta_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 reusable plugin-validate.yml).
  • CI also runs the org's reusable-ci.yml: npm test (node --check of every module + 49 node --test tests 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. Declares plugin-runtime-idempotent.v1: a second evaluation finds the window.__feedBackScorecard singleton and installs nothing.
  • No per-frame DOM work: no requestAnimationFrame loop, no MutationObserver. 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 into feedBack.ui.playerControlSlot().
  • Self-hosted plain CSS via styles, all selectors sc-*. No Python dependencies beyond the host's FastAPI; no external network access. Routes validate every input and log through context["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: synthetic notedetect:hit/miss events 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:miss events, noteDetect.getStats() (to notice a stats reset), noteDetect.isEnabled(), noteDetect.applySettings({ latencyOffset }) (the documented public setter)
  • Highway: getFilteredNotes/Chords() (fallback getNotes/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's playbackRate (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/stats score.

Review request

Please confirm:

  1. whether a curated-list entry is the right path, or whether you'd prefer the repository transferred into the got-feedback org (I'm happy to transfer it);
  2. whether the API gaps below are welcome as issues/PRs (I can open them); and
  3. whether core's /api/stats should 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)

  1. 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.
  2. core / highway_3d — provider contract is too narrow for game-style feedback. color is ignored by the 3D highway (and by every renderer for miss); there is no "consumed" state (hit gem disappears in a burst), no partial state for chords, and no ttl/past-the-line hint so a missed gem can keep scrolling. Candidate: extend { state, alpha, color } with state: 'consumed' | 'partial' (or a style field) and honour color in highway_3d.
  3. 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 private jucePlayer). Candidate: window.feedBack.getPlaybackRate() + a song:speed-changed event.
  4. core — A/V offset setter is only a legacy window global. window.setAvOffsetMs exists for inline handlers; highway.getAvOffset() reads it. Candidate: window.feedBack.setAvOffset(ms) / getAvOffset() on the namespaced API (and an av-offset:changed event so gauges can reset without polling).
  5. notedetect — no side-effect-free settings getter. The only way to read latencyOffset outside a judgment is applySettings({}), which also persists settings. Candidate: noteDetect.getSettings() (the headless harness already has one) — today Scorecard relies on latencyMs in each judgment.
  6. 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.
  7. 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 through applySettings, or a way for a HUD plugin to claim the stats surface.
  8. core — no player HUD overlay slot. Overlays append to #player per docs/plugin-v3-ui.md; a feedBack.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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions