Move the loader onto the 68000, and find 5,920 bytes nobody counted

src/player/load.i expands both codebooks to word-per-pixel form and packs the
palette to GGGGGRRRRRBBBBBI out of the RAW container header, byte-exact against
tools/bench/dlxload.py on both CPU cores.  The palette half is gated on words
read back out of the palette registers at $E82000, so "the words reached the
hardware" is part of what passes.  ROADMAP P1 is done; P2's encoder half (a
reserved black entry, 23.4) is not, and is a re-encode rather than an edit.

A scene change costs 18.96 ms of 68000 time, 22.8% of one 12 fps frame; boot
costs 24.70 ms.  The scratch tables describe the CRTC, not the scene, so
pal_tables is a separate entry point built once at boot -- 5.29 ms off every
scene change.

The one that moves something: the scene header is 5,920 B that no rate table in
this tree included, because it belongs to no frame record.  In FINDINGS 51.3's
currency it is divided by the surplus pipe - wire, so it is hypersensitive:
138 ms of extra refill climb at 488 KB/s and 1.099 s at 451.4 KB/s, for the
same bytes.  tools/analysis/22_scene_load.py prices it across explicit rates.

Recorded as open: the two CPU cores agree to <3% on every stage but the table
build, where they differ by 16.4%.  px68k's C68K charges a flat 50 clocks for
MULU/MULS (c68kmacro.h:1869) where the 68000 charges 38+2n, which explains
4,608 of the 8,703 clock gap.  4,095 clocks are unexplained.  Nothing else in
src/player/ multiplies, so no figure in FINDINGS 24-52 is affected.

decode.s and stream.s are untouched; decode.bin is still 1,296 B at the same
MD5.  check.sh gains a stage that gates byte-exactness on both cores and
deliberately does not gate the cycle counts -- MAME's clock is 1/55.46 s and a
wall timing would make the green light host-sensitive.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-24 20:20:40 -07:00
parent ed172c2da2
commit 7179339bd2
13 changed files with 1189 additions and 18 deletions
+90
View File
@@ -1,3 +1,93 @@
# Status & next-session handoff — end of session 21 (2026-08-24)
## Session 21: the loader moves onto the 68000, and a scene change gets a price
**Green light first and last: `./tools/bench/check.sh` was ALL GREEN before any
of this and ALL GREEN after**, 120/120 on both cores, no `TRUNCATED`, plus a new
load-time stage.
**ROADMAP P1 is DONE and P2 is half done. FINDINGS 53.** Session 20's handoff
named P4 as the item that decides the project, and P4 is **blocked in this
tree** — re-checked, not assumed: there is still no `scsiexrom.bin` anywhere on
this machine (`~/mame/roms/x68000.zip` holds six files, four IPLs, a cgrom and
an sram), MAME's `x68000` has no MB89352 path, and `hd63450.cpp` decodes no
DTYP. **Nothing here can measure W.** P1+P2 was the M2 item that could be built
here, and it is the one that touches an already-measured number: it lands at a
scene change, where FINDINGS 51.3's refill climb is.
**1. The transforms are on the 68000 and they are byte-exact.** `src/player/
load.i` expands both codebooks to word-per-pixel form and packs the palette to
`GGGGGRRRRRBBBBBI` with the shared LSB chosen per entry, out of the RAW
container header. Gated **byte-for-byte against `tools/bench/dlxload.py`**,
which stays the reference — what changed is where the transforms run, not what
they produce. The palette half is read back **out of the palette registers at
`$E82000`**, so "the words reached the hardware" is part of what passes. Both
CPU cores emit the same 10,752 B. 53.1.
**2. A scene change costs 18.96 ms of 68000 time, 22.8% of one 12 fps frame.**
Boot costs 24.70 ms. Split: codebooks 92,609 clocks, palette entries 97,019,
scratch tables 52,919. Cross-checked on px68k's C68K, which agrees to 1.4% on
the scene-change figure. 53.2.
**3. The scratch tables describe the machine, not the scene.** They are the
CRTC's 6-to-8-bit rendering, its square, and the per-channel error difference —
so `pal_tables` is a separate entry point, built once at boot. **5.29 ms off
every scene change**, 22% of what a naive port of `dlxload.py` would have cost
per scene. 53.3.
**4. THE ONE THAT MOVES SOMETHING: the scene header is 5,920 bytes nothing has
ever counted.** Palette + CB1 + CB4, and it must arrive before frame 0 can be
decoded. It is not part of any frame record, so no rate table in this tree
includes it. `tools/analysis/22_scene_load.py` prices it across explicit rates.
The whole fixed cost of a scene change is about **a third of one frame slot**
but its cost in FINDINGS 51.3's currency, accumulated slack, is divided by the
surplus `pipe - wire` and so is hypersensitive: **138 ms of extra climb at 488
KB/s, and 1.099 s at 451.4 KB/s**, for the same 5,920 bytes. 53.5.
**5. An instrument disagreement worth carrying forward.** The two CPU cores
agree to <3% on every stage except the table build, where they differ by 16.4%
— and that is the only code in this tree that multiplies. **px68k's C68K charges
a flat 50 clocks for `MULU`/`MULS`** regardless of operand (`c68kmacro.h:1869`),
where the 68000 charges 38+2n. That explains 4,608 of the 8,703 clock gap;
**4,095 clocks are not explained and are recorded as open.** Nothing in
`src/player/` outside these three instructions multiplies (checked), so no
figure in FINDINGS 24-52 is affected — but the second opinion this tree leans on
over-charges multiplies, and a future measurement containing one must not be
taken from it uncorrected. 53.4.
**6. Shipping pre-expanded codebooks was considered and refused.** It would
trade 9.26 ms of 68000 time for 5,120 more bytes in every scene header — 10.5 ms
of pipe at 488 KB/s, and bytes that lengthen the climb. Close to a wash in
milliseconds, not a wash in kind: the CPU is idle during a seek and the pipe is
what this project is short of. **Derived, not measured.** 53.6.
**New in the tree:** `src/player/load.i` (the transforms) and
`src/player/loadgate.s` (its front-end, 488 B); `tools/bench/prep_load.py`,
`load.lua`, `verify_load.py`, `load_run.sh` (the rig, both cores);
`tools/analysis/22_scene_load.py` (the pricing). `tools/bench/c68k/harness.c`
gains a `--loadraw` mode, which also makes its flag-watch address a variable
rather than a constant. `check.sh` gains a stage that gates byte-exactness on
both cores, and deliberately does **not** gate the cycle counts — MAME's clock
is 1/55.46 s and a wall timing would make the green light host-sensitive, the
same reason `blit.s` and `span.sh` are not in it.
**`decode.s` and `stream.s` are unchanged.** Nothing in the per-frame path was
touched; `decode.bin` is still 1,296 B at the same MD5.
**Still open in P2:** the encoder does not reserve a black entry (23.4), so the
letterbox still gets the palette's closest thing to black (index 255 here). That
half is encoder-side, it changes the container, and it moves every constant
fitted to the gate container — a re-encode plus a re-measurement, not an edit.
**Next:** P3 (a real frame clock from the MFP or VBL) and P5 (per-record index,
prefill policy, the accumulated-slack rule in the player rather than the rig)
are both buildable here. G1 (import the scene graph) is the one that would let
this tree ask a question it currently cannot: what is the worst gap between
consecutive decision points, and does the refill climb survive it. P4 still
decides the project and still cannot be measured here.
---
# Status & next-session handoff — end of session 20 (2026-08-24)
## Session 20: the DMAC configuration was in the IPL ROM the whole time