Skip to content

Improvement: Octatrack base hook ev_engine_job at the engine task's job switch (0x4008485e) - #1272

Draft
repeat98 wants to merge 4 commits into
mainfrom
claude/ev-engine-job
Draft

repeat98 wants to merge 4 commits into
mainfrom
claude/ev-engine-job

Conversation

@repeat98

@repeat98 repeat98 commented Nov 6, 2026

Copy link
Copy Markdown
Owner
  • What was wrong or hard: FM Synth 0.1.4 (FM Synth 0.1.4: the author's 2.21 engine; MIDI quantizing moves to Scale Quantizer #1146) detours the engine task's job switch at 0x4008485e (fl_io_dispatch: before a stock job it flushes its step-lock state and may refuse the job). The core logger already holds that site on every build: on the USB base (build_core.py) and on every browser card build (core-logger.ts). So modules:compat -- synth refuses the version, and the site could not build FM Synth 0.1.4 at all: its jmp and the logger's land on the same six bytes (applyGuardedOsWrites: overlapping guards). Other modules want the same site for job types of their own, so a gesture only one module can hold would be a dead end (Propose a hook).
  • What this changes:
    • A call hook, ev_engine_job (events.json, index 38): int f(const uint8_t *job) with the received job, after the engine's receive and before stock's switch. 0 passes; 1 takes the job (stock does not run it, the engine goes back to its loop at 0x4008484e, the idle hook, then the next receive); any other nonzero runs stock at once. The site's tail replays the six stock bytes from the user's OS and goes on at 0x40084864. Generated with events.py --write (HOOKS.md, modwerk_events.h, the dispatch, site-events.json).
    • The logger stays first. The site's head calls olog_job_hook (the JOB record, and the sample editor's job seam), which build_core.py ends with rts on a site-events base instead of its stock replay, and the logger no longer patches the job site itself there.
    • Card builds have no base, so a subscriber keeps its jmp detour on the site there, as BBD does for ev_tail_kill. chainedDetour (core-logger.ts) puts that whole jmp in the logger hook's replay slot, so the logger records the job and then the module runs as it would from the site. Only an exact same-span jmp is handed on; anything else still refuses as overlapping. static-compose.ts leaves the handed-on write out when it applies the plan.
    • The two native comparisons (native-comparison.mjs, verify-sidechain-native.mjs) checked that no logger write touches a module write. They now accept exactly that handover: a logger write at the address of a write the logger chained. The module-owned image is still compared with native byte for byte. Please look at this one: it changes what the checks allow, by design of this hook.
    • Beta base rebuilt (npm run octatrack:base -- --beta --write): new identity 5ef241d4 (main's is 9b3ba417; rebuilt on 6 Nov after merging main, which brought FRACT among the PROTECT machines, native FLEX, Mute Modes and the USB MIDI clock fix). The hook's handler row adds 516 bytes, so the arena and pool move up (arena +0x1800, pool +0x600 against main's base) and modwerk_cue_cfg_clamp / modwerk_raise_ev_mute_keep move. The base-bound packages are relinked (npm run octatrack:module-packages -- --base-only): requiredBase for the five, and Recorder Loop Fix's cave package re-linked for the moved arena (new hash). The other four rebuild byte for byte.
    • No subscriber yet. FM Synth subscribes in FM Synth 0.1.4: the author's 2.21 engine; MIDI quantizing moves to Scale Quantizer #1146: runtime.json gets ev_engine_job → mw_ev_engine_job, replacing fl_io_dispatch, and port.s gets a ten-instruction wrapper around FM's own fl_io_before_job / fl_io_reject_job. I tried this locally on FM Synth 0.1.4: the author's 2.21 engine; MIDI quantizing moves to Scale Quantizer #1146's head with this registry: test_machine_events and test_runtime_package_metadata pass, and modules:compat -- synth no longer reports 0x4008485e.
  • How I checked it:
    • npm run pr:ready: Ready (npm run check -- --base origin/main passed, sdk:check included). The base build is stock-free and flash-safe, and the browser .bin is byte-identical to build_core.py's (ed69b271…, base 5ef241d4).
    • On the first cut of this branch (base 3d934fca, 5 Nov; not rerun on 5ef241d4): scripts/build-modules-isolated.sh in the pinned image, then modules:import --development: the card packages are identical to the committed ones. Only the sourceCommit stamps changed (and the file hashes that follow from them), so I did not commit the import.
    • New test in core-logger.test.ts: a same-span jmp is handed on, a different span or address is not, and a jsr or a non-detour on a logger site is refused.
    • sdk/tests: test_machine_events, test_sample_editor, test_module_compat, test_host_reconstruction, test_runtime_package_metadata pass. test_runtime_loader fails locally only on the known Node 18 default (No FX page for everb: Node.js v18.20.8), the same on main.
    • Emulator smoke only, on base 3d934fca (5 Nov, before the merge of main; not rerun on 5ef241d4) (ot_emu, that base's MAIN.raw, 15 s emulated, 3 s host): it boots, and the core logger's ring holds 256 JOB records in begin/end pairs with rising tokens: the new site's head records each job and every job comes back through the loop. The fixture card has no project, so every job the emulator served was type 0xff (the engine's empty receive, above 45, which stock skips): no project load or save ran through the hook here. Not hardware evidence.
    • Not run on hardware. Base 5ef241d4 has not been booted on a unit yet; the unit run is pending and needed before merge (below). This stays a draft until then.
  • Unit run before merge (a dev twin of this base, RAM boot; about two minutes):
    1. Boot with no modules. The startup screen, then the project, as before.
    2. Load another project, then the first again (engine jobs through the new site). Both load, and audio plays.
    3. Change a pattern and save the project (a save job). It saves, and after a reload the change is there.
    4. npm run device -- log (or the diagnostics pull): JOB records in begin/end pairs, and a log flush after the save (jobs 7, 0x11 and 0x12 request one at the next idle).
    5. Optional, with the sample editor: open AED SLICE once; its job seam still answers.
  • Base PR queue: whichever open base PR merges later regenerates the base and relinks the base-bound packages (npm run octatrack:base -- --stock OS.bin --beta --write, then npm run octatrack:module-packages -- --base-only).
  • What the guides lacked or got wrong, and where I fixed it: (the 6 Nov refresh found nothing new) the logger README did not say the job site is shared. It now says how the site is owned on a base with site events and how a card build hands it on (sdk/runtime/logging/README.md). A gap I did not close: no check sees a card module's detour landing on a core logger site (stock-guards.json) until the browser composes it; modules:compat caught this one only because the USB base patches the same site.

This unblocks #1146 (FM Synth 0.1.4): once it merges, #1146 merges main and subscribes.

🤖 Generated with Claude Code

repeat98 and others added 4 commits November 5, 2026 14:43
… (0x4008485e)

The core logger already holds the job site on every build, and FM Synth 0.1.4
needs it too (its engine dispatch filter), as do other modules' own job types.
The site becomes a call hook in events.json: its head runs the logger's job
record (and the sample editor's seam), then the subscribed handlers; 1 takes
the job back to the engine loop, anything else nonzero runs stock at once.
On card builds, which have no base, a module keeps its jmp detour on the site
and the logger replays it from its hook (core-logger.ts chainedDetour); the
native comparisons accept only that exact handover. Beta base rebuilt
(3d934fca); the base-bound packages relinked (--base-only), Recorder Loop
Fix's cave package re-linked for the moved arena.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main brought base changes (FRACT known among the PROTECT machines, native
FLEX, Mute Modes, the USB MIDI clock fix), so the beta base is rebuilt on top
of them with this hook (npm run octatrack:base -- --beta --write) and the
base-bound packages relinked (--base-only): requiredBase for the five, the
same Recorder Loop Fix bytes as before the merge.

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

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

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant