Rework the megademo for the 80-column line limit #35

Merged
andrew merged 1 commits from fix/megademo-80col into feature/reduce_memory_usage 2026-08-04 09:11:55 -04:00
Collaborator

Unbreaks examples/megademo under this branch's AKBASIC_MAX_LINE_LENGTH cut from 256 to 80, per the caveat in #33's description and the corpus impact tracked in #32. Seventeen lines were over the limit: the sixteen IM$() picture strings (up to 252 characters) and the two four-bar PLAY strings in TUNEA/TUNEB. Nothing else in the file was over — but see the finding below about what "80" actually means.

The picture: regenerate at a record-aware 64-character chunk

No decoder changes. DRAWSTREAM already carries its X#/Y# cursor from one IM$ entry to the next, and the VA#/VB# frame tables already describe multi-entry frames, so the fix is entirely in vaporwave.py:

  • PAYLOAD 240 → 64, so every emitted IM$(NN) = "..." line fits the ceiling with margin.
  • chop() now walks the stream a record at a time — two characters for a run, three for an R row record — and never cuts inside one. The decoder reads a record's tail with MID on the string it is walking; a record straddling two entries decodes as garbage. The old blind slice at 240 was only safe by luck: nothing checked it, because verify() simulated the unchopped blobs.
  • verify() now simulates the chopped strings, threading the cursor across boundaries exactly the way DRAWSTREAM executes them, so a bad cut is an assertion failure instead of a corrupted screen. emit_block() also asserts every emitted line fits.

The regenerated block is 56 strings where it was 16 (same 3,370 encoded bytes), and re-running --splice is idempotent.

The music: one PLAY per bar

TUNEA and TUNEB each become four PLAY statements, one bar apiece. play.c keeps voice, envelope, level and duration state on the runtime across statements, and every PLAY appends to the same queue — four bars queue exactly as the one long string did. Each bar restates the V1T3U9S prefix so a bar dropped by QFULL cannot leave the next batch playing on the drum kit's envelope.

Findings along the way

  • The effective line limit is 78 characters, not 80. The buffer is char code[80] and the new stdio_readline() guard refuses a read that consumes 79 characters without a newline, so content + \n must fit in 79. Worth stating in the constant's comment if 80 was meant to be the content width — as it stands, an authentic 80-column Commodore line does not load. (The guard's error message, "exceeds the %zu character limit" with len - 1 = 79, is also one off from the real content ceiling.)
  • Everything else in the demo clears the shrunken pools with room to spare: 1,625 source lines of 2,048; ~704 array slots of 2,048; longest identifier 11 of the 24-character symtab key; no DEF FN; batch size and PLAY queue interplay unchanged.
  • Verified end to end on this branch: built plain and with AKGL (SDL console build, dummy drivers), the demo loads and the offscreen host renders every scene; the scene-5 sunset still is pixel-identical to vaporwave.py --preview's reference. The test suite fails the identical seventeen cases with and without this commit (the fifteen from #32 plus docs_examples and one golden case that appear environmental here).
  • examples/breakout still needs the same treatment; its longest line cleared the old 256 ceiling "by more than half" per the sink_stdio comment, and it shares the stroke-font DATA idiom but not the string-picture machinery.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ACffnV6F7sxQuG3Y8a1L3s

Unbreaks `examples/megademo` under this branch's `AKBASIC_MAX_LINE_LENGTH` cut from 256 to 80, per the caveat in #33's description and the corpus impact tracked in #32. Seventeen lines were over the limit: the sixteen `IM$()` picture strings (up to 252 characters) and the two four-bar `PLAY` strings in `TUNEA`/`TUNEB`. Nothing else in the file was over — but see the finding below about what "80" actually means. ## The picture: regenerate at a record-aware 64-character chunk No decoder changes. `DRAWSTREAM` already carries its `X#`/`Y#` cursor from one `IM$` entry to the next, and the `VA#`/`VB#` frame tables already describe multi-entry frames, so the fix is entirely in `vaporwave.py`: - `PAYLOAD` 240 → 64, so every emitted `IM$(NN) = "..."` line fits the ceiling with margin. - `chop()` now walks the stream a record at a time — two characters for a run, three for an `R` row record — and never cuts inside one. The decoder reads a record's tail with `MID` on the string it is walking; a record straddling two entries decodes as garbage. **The old blind slice at 240 was only safe by luck**: nothing checked it, because `verify()` simulated the unchopped blobs. - `verify()` now simulates the *chopped* strings, threading the cursor across boundaries exactly the way `DRAWSTREAM` executes them, so a bad cut is an assertion failure instead of a corrupted screen. `emit_block()` also asserts every emitted line fits. The regenerated block is 56 strings where it was 16 (same 3,370 encoded bytes), and re-running `--splice` is idempotent. ## The music: one `PLAY` per bar `TUNEA` and `TUNEB` each become four `PLAY` statements, one bar apiece. `play.c` keeps voice, envelope, level and duration state on the runtime across statements, and every `PLAY` appends to the same queue — four bars queue exactly as the one long string did. Each bar restates the `V1T3U9S` prefix so a bar dropped by `QFULL` cannot leave the next batch playing on the drum kit's envelope. ## Findings along the way - **The effective line limit is 78 characters, not 80.** The buffer is `char code[80]` and the new `stdio_readline()` guard refuses a read that consumes 79 characters without a newline, so content + `\n` must fit in 79. Worth stating in the constant's comment if 80 was meant to be the *content* width — as it stands, an authentic 80-column Commodore line does not load. (The guard's error message, "exceeds the %zu character limit" with `len - 1` = 79, is also one off from the real content ceiling.) - Everything else in the demo clears the shrunken pools with room to spare: 1,625 source lines of 2,048; ~704 array slots of 2,048; longest identifier 11 of the 24-character symtab key; no `DEF FN`; batch size and `PLAY` queue interplay unchanged. - Verified end to end on this branch: built plain and with AKGL (SDL console build, dummy drivers), the demo loads and the offscreen host renders every scene; the scene-5 sunset still is pixel-identical to `vaporwave.py --preview`'s reference. The test suite fails the identical seventeen cases with and without this commit (the fifteen from #32 plus `docs_examples` and one golden case that appear environmental here). - `examples/breakout` still needs the same treatment; its longest line cleared the old 256 ceiling "by more than half" per the sink_stdio comment, and it shares the stroke-font `DATA` idiom but not the string-picture machinery. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01ACffnV6F7sxQuG3Y8a1L3s
tachikoma added 1 commit 2026-08-03 22:58:43 -04:00
Rework the megademo to fit the 80-column source line limit
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m29s
akbasic CI Build / coverage (push) Failing after 3m58s
akbasic CI Build / sanitizers (push) Failing after 4m36s
akbasic CI Build / mutation_test (push) Failing after 3m52s
akbasic CI Build / akgl_build (push) Failing after 7m29s
01adf80751
AKBASIC_MAX_LINE_LENGTH's cut from 256 to 80 left seventeen lines of
examples/megademo unloadable: the sixteen IM$() picture strings (up to
252 characters) and TUNEA/TUNEB's four-bar PLAY strings (174 and 175).
The real ceiling is 78 characters, not 80 -- stdio_readline() refuses a
read that fills the 80-byte buffer without a newline, so content plus
its terminator must fit in 79.

The picture: vaporwave.py's PAYLOAD drops from 240 to 64, so every
emitted IM$(NN) = "..." line fits under the ceiling. chop() no longer
slices blind; it walks the stream a record at a time -- two characters
for a run, three for an R row record -- and never cuts inside one,
because the decoder reads a record's tail with MID on the string it is
walking and a record straddling two IM$ entries decodes as garbage.
The old blind slice at 240 only happened to be safe. verify() now
simulates the CHOPPED strings with the cursor threaded across the
boundaries exactly the way DRAWSTREAM executes them, so a bad cut is
an assertion failure instead of a corrupted screen, and emit_block()
asserts every emitted line fits. The picture is 56 strings where it
was 16; the decoder needed no changes at all, since it already carries
X#/Y# from one IM$ entry to the next.

The music: TUNEA and TUNEB each become four PLAY statements, one bar
apiece. play.c keeps voice, envelope, level and duration state on the
runtime across statements and every PLAY appends to the same queue, so
four bars queue exactly as one long string did. Each bar restates the
V1T3U9S prefix so a bar dropped by QFULL cannot leave the next batch
playing on the drum kit's envelope.

Everything still clears the shrunken pools with room to spare: 1625
source lines of 2048, ~704 array slots of 2048, identifiers within the
24-character symtab key. Verified end to end against this branch's
build: the demo loads, the offscreen host renders every scene, and the
scene-5 still is pixel-identical to vaporwave.py's own preview. The
test suite fails the same seventeen cases with and without this
commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACffnV6F7sxQuG3Y8a1L3s
andrew merged commit 844ebeef22 into feature/reduce_memory_usage 2026-08-04 09:11:55 -04:00
Sign in to join this conversation.