Move hasLyrics/hasVocals/hasForcedNotes from ParsedChart to ScannedChart (state-derived) - #67
Merged
Merged
Conversation
Owner
Author
This was referenced Apr 20, 2026
elicwhite
changed the base branch from
metadata-consolidation
to
graphite-base/67
April 20, 2026 04:16
elicwhite
force-pushed
the
derived-flags-to-scanned
branch
from
April 20, 2026 04:16
e103858 to
5f09fa9
Compare
elicwhite
force-pushed
the
graphite-base/67
branch
from
April 20, 2026 04:16
87affb0 to
2daa2a6
Compare
elicwhite
marked this pull request as ready for review
April 20, 2026 04:16
elicwhite
force-pushed
the
derived-flags-to-scanned
branch
3 times, most recently
from
April 20, 2026 04:45
72171f8 to
a876247
Compare
This was referenced Apr 20, 2026
…art (state-derived) The three derived boolean flags on ParsedChart (hasLyrics, hasVocals, hasForcedNotes) were parse-time snapshots that went stale whenever consumers mutated chart data post-parse. Remove them from ParsedChart entirely; scanChart derives all three at scan time from the current chart state, piggybacking on its existing single note-walk (no extra iterations). - hasLyrics / hasVocals: computed from parsedChart.vocalTracks.parts — constant-time existence check. - hasForcedNotes: inverts resolveFretModifiers. A note is 'forced' iff its resolved flag disagrees with the natural HOPO state the parser would pick without any force events, or it carries the tap flag (tap can only come from explicit forceTap). Inlined helpers for isNaturalHopo, isFretChord, isSameFretNote, isInFretNote, computeHopoThresholdTicks in chart-scanner.ts (duplicated from notes-parser.ts; a follow-up can extract to a shared helper). Semantic change to note: under the new definition, redundantly-applied force events (e.g. explicit forceHopo on a naturally-HOPO note) no longer contribute to hasForcedNotes — the chart plays identically with or without them, so state-derived detection correctly says false. This eliminates the need for any 'hasForcedNotes backstop' in writers since the flag round-trips naturally: the writer emits force events exactly when a flag disagrees with natural state, and the scanner detects those same disagreements on re-parse. ScannedChart.notesData's shape is unchanged (hasLyrics / hasVocals / hasForcedNotes still present); only the population source changes. Consumers that previously read parsedChart.hasLyrics / parsedChart.hasVocals / parsedChart.hasForcedNotes must switch to scanChart output.
elicwhite
force-pushed
the
derived-flags-to-scanned
branch
from
April 21, 2026 04:29
a876247 to
7be766d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

The three derived boolean flags on ParsedChart (hasLyrics, hasVocals,
hasForcedNotes) were parse-time snapshots that went stale whenever
consumers mutated chart data post-parse. Remove them from ParsedChart
entirely; scanChart derives all three at scan time from the current
chart state, piggybacking on its existing single note-walk (no extra
iterations).
hasLyrics / hasVocals: computed from parsedChart.vocalTracks.parts —
constant-time existence check.
hasForcedNotes: inverts resolveFretModifiers. A note is 'forced' iff
its resolved flag disagrees with the natural HOPO state the parser
would pick without any force events, or it carries the tap flag
(tap can only come from explicit forceTap). Inlined helpers for
isNaturalHopo, isFretChord, isSameFretNote, isInFretNote,
computeHopoThresholdTicks in chart-scanner.ts (duplicated from
notes-parser.ts; a follow-up can extract to a shared helper).
Semantic change to note: under the new definition, redundantly-applied
force events (e.g. explicit forceHopo on a naturally-HOPO note) no
longer contribute to hasForcedNotes — the chart plays identically with
or without them, so state-derived detection correctly says false. This
eliminates the need for any 'hasForcedNotes backstop' in writers since
the flag round-trips naturally: the writer emits force events exactly
when a flag disagrees with natural state, and the scanner detects
those same disagreements on re-parse.
ScannedChart.notesData's shape is unchanged (hasLyrics / hasVocals /
hasForcedNotes still present); only the population source changes.
Consumers that previously read parsedChart.hasLyrics /
parsedChart.hasVocals / parsedChart.hasForcedNotes must switch to
scanChart output.