From 6ede4dc2fe1a9cba9c214d06223cbcf12dd81c71 Mon Sep 17 00:00:00 2001 From: mattromano <42412983+mattromano@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:14:20 -0700 Subject: [PATCH] feat(events): richer notedetect:hit/miss payloads for stats plugins A stats or scorecard plugin could only get per-string chord results, technique names, latency or section by patching screen.js. Every emitted judgment now also carries: - key: the note id (same as getNoteState's popKey) - strings: per-string verdicts { s, f, hit, centsError, snr?, detectedAt? } for chords on every path (a missed browser chord reports its best frame) and for engine-path single notes - techniques: named techniques from the chart note's wire flags - latencyMs / avOffsetMs, detector, section (by note time) - counted, points, multiplier, streak, isDefault All additive; existing fields are unchanged. README documents the contract. Co-Authored-By: Claude Opus 5.5 Signed-off-by: mattromano <42412983+mattromano@users.noreply.github.com> --- CLAUDE.md | 2 +- README.md | 27 +++++ package.json | 2 +- plugin.json | 2 +- screen.js | 130 ++++++++++++++++++++++- test/judgment_events.test.js | 198 +++++++++++++++++++++++++++++++++++ 6 files changed, 353 insertions(+), 8 deletions(-) create mode 100644 test/judgment_events.test.js diff --git a/CLAUDE.md b/CLAUDE.md index 9229fa0..9636877 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,7 +67,7 @@ defer the window copy a task and drop it if the element copy arrived | Event | Bus too? | Payload (additive-only contract) | |---|---|---| -| `notedetect:hit` / `notedetect:miss` | as `note:hit`/`note:miss` | the full judgment (`{ hit, chord, note, notes, noteTime, timingState, timingError, pitchState, pitchError, ... }`) | +| `notedetect:hit` / `notedetect:miss` | as `note:hit`/`note:miss` | the full judgment (`{ hit, chord, note, notes, noteTime, timingState, timingError, pitchState, pitchError, ... }`), plus from v1.33 `{ key, strings, techniques, latencyMs, avOffsetMs, detector, section, counted, points, multiplier, streak, isDefault }` (field reference in README "Events"; stamped by `_ndEnrichJudgment` at emit) | | `notedetect:fx` | yes | `{ fxType: 'multiplier', mult, prevMult, streak }` on tier change; `{ fxType: 'milestone', streak, mult }` at 25/50/every 100; `{ fxType: 'streakBreak', lostStreak, prevMult }` after a ≥10 run dies. All carry `{ isDefault, ts }` | | `notedetect:session` | no | end-of-song summary: `{ title, artist, arrangement, accuracy, hits, misses, bestStreak, score, maxMultiplier, grade, fullCombo, sections, timestamp }` (consumed by the practice journal — never rename/remove keys) | | `notedetect:skin` | yes | `{ skin }` — re-resolve any cached palette on this | diff --git a/README.md b/README.md index d921626..cfff966 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,18 @@ Field reference for the per-note events: | `hit` | `true` only when timing and pitch are both clean | all events (`false` for misses) | | `timingState` / `pitchState` | Independent diagnostic axes: `OK`, `EARLY`, `LATE`, `SHARP`, or `FLAT` | clean hits and matched diagnostic misses; `null` for pure misses | | `timingError` / `pitchError` | Signed timing error in milliseconds and signed pitch error in cents; `pitchError` is octave-folded to the nearest octave (so it is not necessarily equal to `(detectedMidi - expectedMidi) * 100`) | clean hits and matched diagnostic misses; `null` for pure misses | +| `chord` / `notes` | `true` for a chord-level judgment; `notes` lists the chord's `{ s, f }` (`note` is then the lead string) | all events | +| `hitStrings` / `totalStrings` / `score` | How many of the chord's strings rang, out of how many, and the ratio the Chord Leniency setting is compared with | chord judgments where the scorer ran | +| `key` | Stable id of the judged chart note, the same key the highway's `getNoteState` hands back as `popKey`: `"