Skip to content

Octatrack base: PERSONALIZE rows as a base table (ot_personalize_rows), so table owners no longer pin package hashes - #1023

Draft
repeat98 wants to merge 5 commits into
mainfrom
fix/table-owner-declarations
Draft

repeat98 wants to merge 5 commits into
mainfrom
fix/table-owner-declarations

Conversation

@repeat98

@repeat98 repeat98 commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Fixes #1010 (draft: blocked on #1007 and on Zac's OK, see Still to do).

What was wrong

Mute Modes grows stock's three PERSONALIZE tables, and the base admits that table owner only by its exact package SHA-256 (table-owners.json → build_core.fill_table_owners). So every Mute Modes update needs a new declaration, a new approval line and a new base. #1007 shows the record can't just keep the offsets instead: its 2 extra bytes move all three tables. Track Mutes (#931) and Play Start (#947) copy the same table growing, so none of the three fit in one build.

What this changes

  • ot_personalize_rows (hook index 38), a base table, modelled on ot_sequencer_rows. The base owns the five pointer sites (0x40068efe, 0x40068f0a, 0x40069022, 0x4006903e, 0x40069056) and the row count (0x40068fb2, moveq #15+n). Modules contribute {count, {label, get, set}...}, with the same getter and setter convention as stock's rows.
  • Hook index 38; 37 is reserved for ot_machine_starts (Improvement: versioned Octatrack source-machine START callbacks #1029). Hook indices are a package's ABI, so neither hook may move when the other merges. A new "kind": "reserved" entry (reserved_37, room 0) holds 37: the loader's room check and the site's package check (module-set.ts) refuse any word there, events.index() doesn't resolve it, and the SDK header defines nothing for it. When Improvement: versioned Octatrack source-machine START callbacks #1029 merges, its entry replaces reserved_37 (a one-entry conflict in events.json, then events.py --write). HOOKS.md's intro says how to reserve an index when two hook proposals are open at once.
  • Anchored rows (as agreed with @Zac-Kyoti on Mute Modes 0.2.2-experimental: SHADOW out of the stock timestretch task's SRAM (owner review) #1007): each struct modwerk_personalize_row gains uint32_t after, the stock row it goes after, numbered as stock lists them (1-15, MODWERK_PERSONALIZE_AFTER_* in modwerk_events.h; MUTE MODE uses MODWERK_PERSONALIZE_AFTER_PREVIEW_WITHOUT_FX, so it sits at row 3 as in the card build and octabam). MODWERK_PERSONALIZE_END (0) puts a row at the end.
  • Row order: each stock row, then the rows anchored after it; rows on the same anchor in table order (order, then module id, the catalogue's reviewed order), one contribution's rows in record order; then the unanchored rows; then stock's LED BRIGHTNESS, always last. LED BRIGHTNESS is no anchor: after 16 or more refuses the whole contribution (none of its rows shows; the others keep their places). At most 8 contributed rows, the first 8 in table order. Stock counts 15 + MKII, so LED BRIGHTNESS stays last on an MKII and hidden on an MKI without any model check. Unused slots show nothing and do nothing.
  • Why moving stock rows is safe (checked against my own 1.40C, read-only, addresses only, nothing committed): the label, getter and setter tables (0x400b2a34, 0x400b2a74, 0x400b2ac0) are referenced only at the five pointer sites, inside the window's renderer (0x40068e00), row count (0x40068fa8) and key handler (0x40068fd0), whose only references come from the window descriptor at 0x400cbe9c. The window's list state (0x460e4668..0x460e4678: scroll, cursor) is read only there too. Stock's getters and setters take (delta, wrap), never the row index. So a row's index is not its identity, as Zac said.
  • Reader: machine_registry.c personalize(), rebuilt with the SEQUENCER rows on every table change and UI tick.
  • Builder: runtime_units.compile_linked (Mute Modes' path) now reads runtime.json contributions as well as subscriptions. A grown table is dropped when the table the module contributes to owns every one of its references; replacing only some of them is refused.
  • Admission: a Mute Modes package that contributes is no table family (table_admission.c, as Scale Quantizer), so no base record names its hash.
  • Docs: system guide checklist, module-guides table, HOOKS.md (generated), changelog.

How I checked it

  • test_machine_registry.c (host, ASan/UBSan) covers PERSONALIZE rows: stock alone; MUTE MODE after PREVIEW WITHOUT FX (row 3, every later stock row with its getter and setter one down); two modules on the same anchor in both table orders (the tie broken by table order); an unanchored row after stock's fifteenth; a row anchored after the last stock row before the unanchored ones; LED BRIGHTNESS last in every case; anchors 16 (LED BRIGHTNESS) and 99 refuse their contribution while another keeps its place; 9 rows offered against room for 8; removal back to one and to none; count byte and placeholders.
  • test_runtime_loader.py: reserved_37 is room 0, no span, no table, no SDK define, kind: reserved in site-events.json; malformed reserved entries are refused; ot_personalize_rows is 38. module-set.test.ts: a word at a reserved index is refused, one at 38 is accepted.
  • ColdFire compile of machine_registry.c (-mcpu=5475 -Os -Werror): +166 bytes of text.
  • test_table_admission.c: a contributing Mute Modes is TABLE_NONE. It is admitted on an empty base, updated in place with a different package hash, and removed.
  • python3 -m unittest test_machine_events test_runtime_loader test_table_admission test_table_declarations test_table_ab_participants test_sample_editor test_machine_composition test_mute_modes_runtime: OK (test_loader_on_the_host failed once under load and passed on four reruns; it doesn't read these files). One test was skipped because it needs a local stock MAIN.
  • Site guards were computed from my own 1.40C (hashes only). I also confirmed the count function (15 - sne(MKII flag)) and the renderer's null-getter check from a private disassembly.
  • modules:compat and build_core.py both refuse the base sites while Mute Modes still patches them. That's expected until the Mute Modes step below.

Still to do (in this PR)

  1. Rebase after Mute Modes 0.2.2-experimental: SHADOW out of the stock timestretch task's SRAM (owner review) #1007 (owner: Mute Modes 0.2.2-experimental: SHADOW out of the stock timestretch task's SRAM (owner review) #1007 ships first, the old way).
  2. Mute Modes contributes its row. About six lines in its runtime.json (contribute, replacing the five pointer sites and the count poke) and a 5-word record in patch_mutemode.s inside the existing .ifdef MODWERK_EVENTS: count, label, getter, setter and after = 2 (PREVIEW WITHOUT FX). Native bytes and references are unchanged. Zac agreed to the table with the anchor (Mute Modes 0.2.2-experimental: SHADOW out of the stock timestretch task's SRAM (owner review) #1007); the change waits for Mute Modes 0.2.2-experimental: SHADOW out of the stock timestretch task's SRAM (owner review) #1007 to merge.
  3. Retire the table owner: table-owners.json owners [], drop Mute Modes' declaration and recipe, recipe tables: [], rebind candidate-recipes.json sources (runtime_units.py changed, which touches cc-map's pins too).
  4. Rebuild Mute Modes' package (version bump and release notes; MUTE MODE stays at row 3 over USB, as in the card build), then regenerate the beta base and base-bound packages. Coordinate with the base-oct28 rebuild so testers get one base.
  5. Emulator check of the PERSONALIZE window, then the dev unit: install, scroll, set MUTE MODE, Keep/Undo, removal with the window closed, a power cycle. MKII; MKI not tested.

Not in this PR

A shared settings store. Track Mutes and Play Start also both write 0x800000dc, Mute Modes' word, and widen the ANDY restore. They need base-owned per-module storage before they can sit beside Mute Modes. That's the next base piece (the guides' "Module settings: not built").

What the guides lacked

The module guides didn't say that PERSONALIZE rows go through the base. I added that to docs/module-guides/system.md (now with the anchor) and the shared-sites table. The hook-proposal steps say "add one entry at the end of events.json", which breaks when two proposals are open at once (here with #1029). HOOKS.md's intro now says how to reserve the earlier one's index. The module-guides README is over its size limit and can't grow, so that note lives in HOOKS.md.

🤖 Generated with Claude Code

repeat98 and others added 3 commits October 28, 2026 14:34
Stock's fifteen rows, then each contributed row, then LED BRIGHTNESS (MKII), so
several modules can add PERSONALIZE rows without growing stock's tables.

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

runtime_units.compile_linked now reads a runtime.json's contributions as well as its
subscriptions: what they replace goes, and a grown table goes when the table the module
contributes to owns every reference. A Mute Modes package that contributes its row to
ot_personalize_rows is no table owner, so no base record names its package hash (#1010).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
repeat98 and others added 2 commits October 29, 2026 09:21
Hook index 37 is held for ot_machine_starts (#1029) by a reserved entry
(events.json "kind": "reserved", room 0): the loader's room check and the
site's package check refuse any word there, and #1029's entry replaces it
when it merges, so neither hook renumbers whichever merges first.

A PERSONALIZE row now names the stock row it goes after (after, 1-15;
MODWERK_PERSONALIZE_AFTER_* in modwerk_events.h): MUTE MODE after PREVIEW
WITHOUT FX, as in the card build and octabam. Rows on the same anchor keep
the table's order, unanchored rows go after stock's fifteenth, LED
BRIGHTNESS stays last, and an anchor past 15 refuses its contribution.
Checked against 1.40C (privately, addresses only): the three tables and the
window's list state are read only by the window's renderer, row count and
key handler (0x40068e00-0x40069074), so moving stock rows down is safe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@repeat98
repeat98 marked this pull request as draft October 31, 2026 05:57
jonathanwardle1992-beep added a commit to jonathanwardle1992-beep/modwerk that referenced this pull request Nov 2, 2026
Drop the shared compatibility filter, save d2 on the FUNC+T path, gate FUNC+REC on the checkbox, and keep the setting out of Mute Modes' battery word. PERSONALIZE table growth waits for repeat98#1023.

Co-authored-by: Cursor <cursoragent@cursor.com>
cursor Bot pushed a commit to jonathanwardle1992-beep/modwerk that referenced this pull request Nov 4, 2026
Current main is 5f28c9a. Keep the packages already on this branch; they were not rebuilt. PERSONALIZE growth still waits on repeat98#1023.

Co-authored-by: jonathanwardle1992-beep <jonathanwardle1992-beep@users.noreply.github.com>
cursor Bot pushed a commit to jonathanwardle1992-beep/modwerk that referenced this pull request Nov 4, 2026
Current main is 5f28c9a. Packages were not rebuilt. Track Mutes is not on main yet, so this conflict id stays unresolved until that module lands. PERSONALIZE growth still waits on repeat98#1023.

Co-authored-by: jonathanwardle1992-beep <jonathanwardle1992-beep@users.noreply.github.com>
repeat98 pushed a commit that referenced this pull request Nov 5, 2026
…er's mc68k and exec /tmp; Mute Modes updates and the table owner until #1023

From Mute Modes 0.2.2 (#1007).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
repeat98 added a commit that referenced this pull request Nov 5, 2026
Docs: a module's own probe is native source, the probe container's mc68k and exec /tmp, Mute Modes updates until #1023

This branch has not been deployed

No deployments
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.

Octatrack base: table owners pin package hashes, so a table-owning module update needs a new base

1 participant