Skip to content

Fix: Octatrack card builds keep the 16 bytes the stock DELAY reads past its table - #1045

Closed
repeat98 wants to merge 12 commits into
mainfrom
fix/card-delay-table-bytes
Closed

repeat98 wants to merge 12 commits into
mainfrom
fix/card-delay-table-bytes

Conversation

@repeat98

Copy link
Copy Markdown
Owner

Octatrack card builds no longer place code in the 16 bytes right after the stock DELAY's coefficient table.

Draft: this lands with FM Synth 0.1.4 (or, as the fallback, with #895's FM Synth 0.1.3-experimental). FM Synth 0.1.2 pins its SRC page at exactly 0x400d24d0, so until then its card builds are refused and the checks listed below fail. FM Synth itself is not changed here.

What was wrong

The stock DELAY reads 0x400d1ce0 + 4 * (x >> 6) from a 508-entry table that ends at 0x400d24d0. At a slow tempo with a long TIME the index runs up to 16 bytes past the end (508 at 51 BPM, measured under the ColdFire port in upstream octabam #771; never heard on a unit). On a stock unit those bytes are zero. The card builders started their overflow run at exactly 0x400d24d0, so any module whose code spilled out of the clone window had its first bytes read by DELAY as a coefficient. In the retained seven-effect menu proof, for example, MINI VERB's RATE formatter starts there; in the native comparison below, 39 of 125 built images had code in those bytes.

What changes

  • Native builder (build_bus.py): the overflow run starts at 0x400d24e0. delay_tail_clash() refuses a pinned or floating unit or cave that reaches 0x400d24d0..0x400d24df, and the finished image must hold the stock bytes there (that also covers pokes, detours and anything else the ledger cannot see).
  • Browser composer: the overflow run starts at 0x400d24e0 (module-menus.ts, requested-modules.ts). Menu caves are refused there (menuCaveEnd), and so is every guarded OS write (applyGuardedOsWrites, os-patches.ts), which every card composition goes through.
  • Compat gate (npm run modules:compat): refuses a module whose card form (its manifest) or runtime package writes those bytes, and the base's own sites too.
  • Native exporter and comparison: the new refusal is a known class on both sides (export-composition-proofs.py, native-comparison.mjs), so module:verify matches the two builders' refusals.
  • Docs: a trap entry in docs/OCTATRACK_HARDWARE_TRAPS.md; changelog entry.

Builder records

  • sdk/infrastructure-verification/delay-table-tail-builder.json: two fresh native builds of 236 selections in the pinned image (the ptable2 record's 128, FM Synth's native-comparison selections, the seven-effect menu proof), with main's build_bus.py and with this one, from the same module tree. Of 125 images main builds, 86 are identical, 4 change (each wrote 0x400d24d0..0x400d24df before and keeps it stock now: Euclid + Air Chorus + CC Map + Recorder Loop Fix, and Euclid + Tape Head + Air Chorus + Sidechain Compressor, both menu profiles), and 35 with FM Synth are newly refused. 9 FM Synth selections main already refused now stop earlier, at its page; nothing main refuses builds now. Each row records whether the private image kept the 16 bytes stock (delayTableTail); no image the new builder makes writes them. Chained into test_infrastructure_regressions.py. The browser composer matches all 236 native results (90 images: 36 identical, 54 outside the platform writes; 146 matching refusals, 44 of them the new DELAY class).
  • Analog BD composition proofs re-exported natively (--suite analog-bd): 210 of 212 identical; the two Analog BD + Euclid + Sidechain Compressor + Tape Echo images change (their overflow code moves up by 16 bytes). builderSources and moduleSourceTreeSha256 rebind. verify-analog-bd-native.mjs passes: 146 comparisons, 66 matching refusals, 6 round trips.
  • Module packages rebuilt in the isolated image and imported (--development): source-commit stamps and the source tree only. The Tape Echo and Euclid standalone recipes' hashes of the four rebuilt asset rows are refreshed from standalone_runtime.sources() (provenance only, as in Fix: write Formatter.PLAIN's zero formatter words without a stepped slot (#626) #634 and Fix: native builder places a floating cave in the overflow run before refusing #682).

What fails until FM Synth 0.1.4

  • npm run modules:compat (and so npm run check and CI): synth writes 0x400d24d0..0x400d24d5 (linked unit fmpage).
  • sdk/tests/test_module_compat.py: test_repository_passes and the 16 FM Synth subsets of test_machine_modules_install_in_every_combination_through_events_and_tables.
  • src/engine/module-menus.test.ts: has no requested package pinned into the bytes the stock DELAY reads (synth fmpage).
  • Every FM Synth card build, natively and in the browser (in the record above, all 35 FM Synth selections main builds are refused). FM Synth's USB package is not affected: its adapter already links the page elsewhere.
  • module:doctor synth reads the compat gate, so it is red too, and so is scripts/module-doctor.test.mjs.

Nothing else in CI fails because of this change.

Option, not added: a dated exception for FM Synth 0.1.2

If this should land before 0.1.4, one named exception could let only FM Synth 0.1.2's page through: a constant DELAY_TAIL_EXCEPTION = ('synth', '0.1.2-experimental', 'fmpage', 0x400d24d0, 'until FM Synth 0.1.4, 29 Oct 2026') read by delay_tail_clash and the final image check in build_bus.py, by modules:compat, and by menuCaveEnd/applyGuardedOsWrites (for the synth fmpage ROM unit note only), with a test that fails once the catalogue's FM Synth version is not 0.1.2 while the exception is still there, so 0.1.4 has to delete it. The cost: FM Synth 0.1.2 card builds keep putting code where DELAY reads, as they do today, and the record above would carry those images as an exception. I did not add it.

Not done

  • MIDI Scenes (0.2.5-experimental) is a standalone author image (MIDISC2.0): its recipe writes 2,056 bytes of its own code from 0x400d24d0, so its card builds have code in those 16 bytes too. It is reconstructed whole (reconstructMidiScenesAuthor), not through the composer's cave or write admission, so this PR does not refuse it. Moving it is its author's change; refusing it would take MIDI Scenes' card build away. Owner's call.
  • Retained dynamic-loader menu proofs (menu-recipes.json proofs, DSP_LOADER = false): all four record the overflow cursor starting at 0x400d24d0 (the seven-effect one places MINI VERB's RATE formatter there), so verify-module-menus-native.mjs now differs on them. Their exporter needs a clean native worktree at the pinned revision, which no longer carries this builder; they are left as they were.
  • Older historical proof sets (static, requested, sidechain, utility, tapehead): not re-exported, as with earlier builder changes; they are only read by local verifiers.
  • Hardware: none. The 16-byte read is measured in an emulator only.
  • chooser-metadata.json is left as on main. The native export now gives layout.OVERFLOW_RUN = 0x400d24e0 (the browser does not read it), but the file is an Octatrack base input (build_core.py hashes it into the base's source identity), so updating it means a new base. Until it is updated, npm run module:verify stops with "Native chooser layout differs ... in layout" for every module. Owner's call: update it with the next base (npm run octatrack:base -- --stock OS.bin --beta --write), in this PR or when FM Synth 0.1.4 lands.
  • vendor/elekloader/kit/src/devices.ts still lists 0x400d24d0..0x400d2ce0 as free image space; it is vendored upstream code and nothing here reads it.

Checks run locally

  • npm run sdk:check: 485 tests, the only failures are the 17 FM Synth ones above.
  • vitest run (all): only the FM Synth tests above, plus module-doctor.test.mjs (doctor prints the compat ✗ for synth) and module-pages.test.mjs (36, ignored generated public/module-media missing in this worktree, unrelated).
  • tsc -b, eslint on the changed files, modules:compat, the docs link test, the changelog check.
  • Native: the 236-selection before/after builds and the Analog BD suite in the pinned image; browser comparison of all 236 and verify-analog-bd-native.mjs on a local 1.40C file.
  • The isolated module build and modules:import --development.

Guides

docs/OCTATRACK_HARDWARE_TRAPS.md did not name these 16 bytes; it does now. Nothing else in the guides was wrong.

🤖 Generated with Claude Code

repeat98 and others added 11 commits October 29, 2026 09:33
…st its table

The stock DELAY reads 0x400d1ce0 + 4 * (x >> 6) from a 508-entry table that
ends at 0x400d24d0; at a slow tempo with a long TIME the index runs up to 16
bytes past the end, which are zero on a stock unit (measured in an emulator,
upstream octabam #771). The card builders started their overflow run at
exactly 0x400d24d0.

- build_bus.py: OVERFLOW_RUN starts at 0x400d24e0; a pinned or floating cave
  or unit reaching 0x400d24d0..0x400d24df is refused at placement, and the
  finished image must hold stock bytes there.
- Browser composer: the overflow run starts at 0x400d24e0 (module-menus.ts,
  requested-modules.ts); menu caves and every guarded OS write refuse the 16
  bytes (os-patches.ts).
- modules:compat refuses a module whose card form or runtime package writes
  them.
- Tests (sdk unittest and vitest) for the refusal and the new start, a trap
  entry in docs/OCTATRACK_HARDWARE_TRAPS.md and the changelog.

FM Synth 0.1.2 pins its SRC page at 0x400d24d0, so its card builds and the
repository compat tests fail until FM Synth 0.1.4 moves the page into DRAM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it stamps and the source tree)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rison

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ur rebuilt asset rows (provenance only)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sdk/infrastructure-verification/delay-table-tail-builder.json: two fresh
native builds in the pinned image, main's build_bus.py and this one. Of 125
images, 86 are identical, 4 move (each wrote 0x400d24d0..0x400d24df before
and keeps it stock now) and 35 with FM Synth 0.1.2 are newly refused; 9
FM Synth refusals stop earlier, at its page; nothing refused builds now.
Chained into the infrastructure tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ve chooser layout

The two Analog BD + Euclid + Sidechain Compressor + Tape Echo images change
(their overflow code moves past the DELAY's bytes); the other 210 proofs are
identical. builderSources and moduleSourceTreeSha256 rebind.
verify-analog-bd-native.mjs passes: 146 comparisons, 66 matching refusals,
6 round trips. chooser-metadata.json's layout.OVERFLOW_RUN is the native
export's 0x400d24e0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its native layout.OVERFLOW_RUN is now 0x400d24e0; updating it restamps the
base's sources, so it rides with the next base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…amps and the source tree)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ur rebuilt asset rows (provenance only)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two Analog BD + Euclid + Sidechain Compressor + Tape Echo images change;
the other 210 are identical. verify-analog-bd-native.mjs: 146 comparisons,
66 matching refusals, 6 round trips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the bug Something isn't working label Oct 29, 2026
@repeat98
repeat98 marked this pull request as ready for review October 29, 2026 10:39
@repeat98

Copy link
Copy Markdown
Owner Author

Closing this as superseded. Since today modwerk.app builds no card firmware and the card composer is out of the repository (#1451, #1453), so the overflow run this fixed no longer exists, and most of the files it edits are gone.

One part is worth keeping: the modules:compat rule that refuses a base package or base site in the bytes the stock DELAY reads, with the trap note. That comes as a small pull request of its own.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant