Skip to content

fix: accept * N anywhere in the track header, not only the first slot - #6

Merged
spacedevin merged 2 commits into
mainfrom
fix/star-bars-anywhere-in-header
Aug 3, 2026
Merged

fix: accept * N anywhere in the track header, not only the first slot#6
spacedevin merged 2 commits into
mainfrom
fix/star-bars-anywhere-in-header

Conversation

@spacedevin

Copy link
Copy Markdown
Owner

The bug

* N was recognized only in the slot immediately after gen <id>. Anywhere else it fell through to the key/value loop and became a macro param literally named *:

track Pad id pad gen gameBoyDmg layer 0 * 16

loopBars: null, genParams: { layer: 0, "*": 16 }

The pattern length silently became 1 bar instead of 16. No error, no warning — just a song playing at 1/16th of its intended length.

How it was found

Moving tish-gba onto this parser (it had its own hand-written one that scanned header tokens in any order). I diffed the baked ROM data for all 77 shipped .deck files, old parser vs new — 75 were byte-identical and 2 were not. Both were this.

It's not rare in that corpus:

`* N` immediately after gen (canonical):   174 track lines
`* N` after other params (misparsed):       24 track lines, 4 files

This survived because emit always writes * N first — so round-tripping through a host never reproduces it. It only bites hand-written and generated files, which is most of tish-gba's corpus.

Fix

* N and the trailing key value pairs are now accepted in any order after gen <id>. And a * that names no valid length (* 0, * zz, a bare trailing *) is an error rather than a quietly dropped token — a pattern length that silently becomes 1 is the worst kind of wrong.

canonical       loopBars=16  genParams={"layer":0}
star last       loopBars=16  genParams={"layer":0}
star middle     loopBars=4   genParams={"layer":0,"cutoff":900}
inf last        loopBars=null
star bad        errors=["track * N: expected number or inf/infinite"]
star trailing   errors=["track * N: expected number or inf/infinite"]

Verification

Corpus case 002-track-header gains both non-canonical orderings, so the expected-parse file now pins this. Suite green, coverage back at 100% lines/functions/statements (54/54).

Once this releases and deckfile republishes, tish-gba's 77-song bake becomes byte-identical to its old hand-written parser — which is the acceptance test for retiring that parser entirely.

`* N` was recognized only immediately after `gen <id>`. Anywhere else it fell
through to the key/value loop and became a macro param literally named `*`, so

  track Pad id pad gen gameBoyDmg layer 0 * 16

silently lost its pattern length and played as ONE bar. No error — the length
just became the default.

Emit always writes `* N` first, which is why this survived: it only bites
hand-written and generated files. tish-gba has 24 such track lines across 4
songs, and two of them bake differently because of it — found by diffing the
baked ROM data before and after moving that repo onto this parser.

Now `* N` and the trailing `key value` pairs are accepted in any order, and a
`*` that names no valid length is an error rather than a dropped token — a
length that quietly becomes 1 is the worst kind of wrong.

Corpus case 002 gains both orderings; coverage holds at 100%.
@spacedevin
spacedevin merged commit 15bd418 into main Aug 3, 2026
5 checks passed
@spacedevin
spacedevin deleted the fix/star-bars-anywhere-in-header branch August 3, 2026 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant