Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **Highway arrangement fallback excludes drum arrangements.** When no explicit arrangement is requested, the most-notes fallback now skips drum arrangements (detected via manifest `type: drums` or legacy name keywords) so songs open on a guitar/bass part instead of the drum part. Drum-only packs still fall back to the first arrangement.
- **Keys Highway 3D: a mastery change no longer resets the run's score.** Hits, misses, streak and best streak now carry over when the mastery slider (or Difficulty Ladder's auto-adjust) rebuilds the playable notes, so the stats posted at song end cover the whole run (#94).
- **Keys Highway 3D: changing the hand filter mid-song no longer resets the run's score** (#130). Both / LH / RH rebuilds the playable notes but kept calling `_resetScoring()`, wiping hits, misses, streak and best streak — reachable mid-song from Split Screen's per-panel popover. Judged-note keys are `(t, midi)`-keyed so they stay valid across the rebuild and the miss sweep only runs over the visible notes, so only the sweep cursor is re-anchored, mirroring the mastery path (#94).
- **Docker and Proxmox builds now include the starter feedpaks.** First-run seeding can find all listed sources instead of repeatedly warning that the packs are missing.

- **`gp8_audio_sync` and `gp_autosync` now route GPIF parsing through the shared hardened parser (`lib/safe_xml.py`) instead of each carrying its own local `defusedxml`-or-stdlib fallback.** Both files parse `Content/score.gpif` out of an imported `.gp`/`.gpx` container — attacker-influenceable input — and previously duplicated the exact try/except-ImportError-then-stdlib-`ET.fromstring` pattern `safe_xml.py` centralized for every other untrusted-XML call site. Consolidating removes two divergent copies of security-relevant parsing logic; also corrected a stale `requirements.txt` comment that claimed both modules already delegated to `safe_xml.py`.
Expand Down
2 changes: 1 addition & 1 deletion plugins/keys_highway_3d/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "keys_highway_3d",
"name": "Keys Highway 3D",
"version": "0.2.6",
"version": "0.2.7",
"description": "RS+-style 3D falling-note piano highway fed by the Sloppak Notation Format, with Web MIDI input scoring.",
"type": "visualization",
"bundled": true,
Expand Down
31 changes: 20 additions & 11 deletions plugins/keys_highway_3d/screen.js
Original file line number Diff line number Diff line change
Expand Up @@ -3384,7 +3384,7 @@

// Switch this instance's hand filter. A no-op when unchanged, so a
// host re-applying the same value (splitscreen restores on every
// provider refresh) can't reset scoring mid-song. Meshes are rebuilt
// provider refresh) can't touch scoring mid-song. Meshes are rebuilt
// only once the scene exists; before that, the next build reads the
// refiltered `playable`.
function _setHandFilter(next) {
Expand All @@ -3395,9 +3395,19 @@
if (!notesGroup) return;
const curTime = _latestTime;
buildNoteMeshes();
_resetScoring();
// same as the mastery path: resume the sweep where playback is
// now — notes already elapsed must not re-sweep as misses
// Same as the mastery path, this is the SAME run: hits / misses
// / streak / best streak carry over, so changing the hand filter
// during play doesn't wipe the score (and the stats posted at
// song end cover the whole run). The judged-note sets are keyed
// by noteKey(t, midi), not by list index, so they stay valid
// across the new playable set — and keeping them stops a note
// already judged from being counted again if a later hand change
// brings it back into view. The miss sweep only runs over the
// visible `playable`, so a note hidden by the filter can never
// accrue a miss while hidden. Only the sweep cursor indexes the
// old list, so re-anchor it: playback kept advancing, and notes
// the player already passed must not be retroactively counted
// as misses.
_anchorMissSweep(curTime);
}

Expand Down Expand Up @@ -3631,13 +3641,12 @@
// miss sweep at the current playback position instead of the list
// head. _resetScoring() rewinds _sweepCursor to 0 — right for a fresh
// chart, wrong here, where playback keeps advancing: a sweep from 0
// against a just-emptied _hitNoteKeys/_missedNoteKeys would re-mark
// every already-elapsed visible note (including ones already hit) as
// a miss and tank the run's accuracy. The floor keeps the tolerance
// band around the change retro-free (a "run restart", matching what
// the reset already does to hits/streak), and _latestTime is restored
// so a note-on landing before the next draw still judges against real
// song time, not the reset's 0.
// would re-mark every already-elapsed visible note never judged
// (including ones the previous filter hid and this change just
// revealed) as a miss and tank the run's accuracy. The floor keeps
// the tolerance band around the change retro-free, and _latestTime
// is restored so a note-on landing before the next draw still
// judges against real song time.
function _anchorMissSweep(now) {
_sweepCursor.idx = sweepStartIndex(_notation && _notation.playable, now, HIT_TOLERANCE_S);
if (Number.isFinite(Number(now))) {
Expand Down
13 changes: 13 additions & 0 deletions plugins/keys_highway_3d/tests/instance_invariants.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ test('a mastery change keeps the run totals instead of resetting scoring', () =>
assert.match(fn[0], /_anchorMissSweep\(now\)/);
});

test('a hand-filter change keeps the run totals instead of resetting scoring (#130)', () => {
// Same shape as the mastery path: changing Both / LH / RH mid-song
// rebuilds the playable notes, so wiping hits/misses/streak made the
// posted stats cover only the time since the change. Judged-note keys
// are (t, midi)-keyed so they survive the rebuild; the miss sweep only
// runs over the visible playable, so a hidden-hand note can never
// accrue a miss while hidden. Only the sweep cursor needs re-anchoring.
const fn = src.match(/function _setHandFilter\(next\)\s*\{[\s\S]*?\n {8}\}\n/);
assert.ok(fn, '_setHandFilter not found');
assert.doesNotMatch(fn[0], /_resetScoring\(\)/);
assert.match(fn[0], /_anchorMissSweep\(curTime\)/);
});

test('updateScene windows its arrays instead of walking every note (feedBack#95)', () => {
// The window helpers are pure and unit-tested in data_layer.test.js, but
// nothing there can catch them being exported and never wired up — the
Expand Down