Measure the finer chain tail: 84/120 becomes 18/120, and the derivation was right by cancellation

blit.s gains v7 -- v6's 24-pixel movem chain plus a second chain whose unit is
one `move.l (a0)+,(a2)+`. Measured over 13 span lengths by span.sh, every config
pixel-exact:

    cycles = 66.0 per span + 9.143 per COARSE pixel + 9.978 per FINE pixel

fitting all 13 to within 0.2%. v5 and v6 re-measure to FINDINGS 30 exactly, so
the harness has not drifted underneath the new variant.

Rescored against the same scsi window and the same additive model, v7 takes
84/120 frames over budget to 18/120 -- exactly what FINDINGS 39.4 derived, and
that agreement is two cancelling errors: the derivation's 2-register movem tail
is 29% too dear per pixel, and its "nothing per span" for the second chain entry
is 22.3 clocks too cheap. The plain post-incrementing move.l is the right tail
instruction, and it makes the padding quantum 2 pixels, which a run of 4x4
blocks pads to exactly zero.

The DMAC stays dropped on a measurement now rather than an argument: v7 takes
back 37 of the 43 frames the array chain would, with no reserved channel and no
timing neither emulator here can verify. Break-even against all-V1 moves from
L=4 blocks to L=2.

The fine displacement is carried mid-stream rather than in the span record, so
the decoder holds nothing across the copy and keeps all 12 payload registers --
which is the whole reason the coarse unit is 24 pixels.

span.sh is now -seconds_to_run 200 (30 s wall, 36 configs) and takes its
expected snapshot count from the generated metadata instead of a literal 23.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-23 19:02:23 -07:00
parent c5ca56330e
commit c520a89e14
9 changed files with 522 additions and 52 deletions
+112 -4
View File
@@ -1,7 +1,109 @@
# Status & next-session handoff — end of session 10 (2026-08-23)
# Status & next-session handoff — end of session 11 (2026-08-23)
## Where this stands
Session 11 measured the one item session 10 left at the top of the list, and it
paid: **`blit.s` v7, the literal span with a fine tail, is MEASURED and takes
the `scsi` window from 84/120 frames over budget to 18/120.** FINDINGS 40.
```
v7: cycles = 66.0 per span + 9.143 per COARSE pixel + 9.978 per FINE pixel
13 span lengths, all fitted to within 0.2%, all pixel-exact
```
| | frames over budget, 120-frame `scsi` window |
|---|---:|
| today (no spans) | 84/120 |
| v6 span as built | 55/120 |
| **v7, measured** | **18/120** |
| DMAC chain (datasheet) | 12/120 |
**The DMAC stays dropped, and now on a measurement rather than an argument.**
v7 takes back 37 of the 43 frames the DMAC chain would, with no reserved
channel, no two-region container, and no transfer timing neither emulator here
can verify. FINDINGS 39.1 still holds if that ever changes: a chain array entry
and a v6/v7 span record are the same six bytes.
**Break-even against all-V1 moves from L=4 blocks to L=2.** 39.4 predicted L=3.
**18/120 is exactly what 39.4 derived, and that is a coincidence of two
cancelling errors** — worth knowing before the next derived figure gets trusted
for landing on its measurement. 39.4 assumed a 2-register `movem` tail at 14.0
clocks/pixel (the real tail is 9.978, 29% cheaper) and assumed the second chain
entry costs nothing per span (it costs 22.3 clocks). The two nearly cancel over
this window. FINDINGS 40.2.
**The tail instruction the derivation should have picked is `move.l (a0)+,(a2)+`.**
A 2-register `movem` pays two instruction words and a `lea` to move what two
post-incrementing `move.l`s move: 14 bus cycles against 10 for the same 4
pixels. Taking the plain instruction also makes the padding quantum **2 pixels**
instead of 4 — and a span is a run of 4x4 blocks, so **its padding is exactly
zero**. FINDINGS 40.3.
**The fine displacement lives in the STREAM, not in the span record**, after the
coarse pixels and before the fine ones: the coarse chain falls out into
`move.w (a0)+,d0 / jmp`, where `d0` is dead payload and `a0` already points at
it. That is what lets v7 keep all 12 payload registers, which is the entire
reason v6's unit is 24 pixels. The container costs 2 more bytes a span.
FINDINGS 40.4.
**One process note.** `span.sh` ran 13 minutes producing an empty log and zero
snapshots; the same command with a shorter `-seconds_to_run` did the identical
work in 30 s, and the wedge never reproduced. The cause is unidentified. What
resolved it was not chasing the hang but **shrinking the stimulus**:
`tmp/spans_meta.lua` holds byte offsets into a blob `prep_spans.py` writes once,
so deleting lines from the metadata runs any subset in seconds against the same
stream file. Keep that trick. `span.sh` is now `-seconds_to_run 200` (30 s wall
for all 36 configs) and takes its expected snapshot count from the metadata
instead of a literal 23. FINDINGS 40.6.
Green light: `./tools/bench/check.sh` **ALL GREEN** at the end of this session.
## NEXT SESSION, in order
0. **Green light first.** `./tools/bench/check.sh` (~5 min, Blu-ray mounted).
Verified green at end of session 11.
1. **Build v7 into `src/player/decode.s`.** This is now the largest thing
standing between the measured decoder and the budget: 84/120 to 18/120, the
format is fully specified (FINDINGS 30.2, costs 40.1), the executor is
written and pixel-exact in `blit.s`, and the encoder side is
`prep_spans.py`'s v7 emitter. The container is `{u32 absolute GVRAM address,
u16 coarse displacement}` per span plus one `u16` fine displacement carried
mid-stream — see FINDINGS 40.4 before changing that layout, the register
pressure is the reason for it.
2. **Then re-run `14_dmac_chain.py` and `13_cpu_ratectl.py` against a container
the encoder actually emits with spans in it.** Every span figure so far is
scored against mode maps chosen without spans available, which FINDINGS 39.3
flags as a lower bound on what a span-aware encoder would find.
3. **Make sure the player actually gets DMA** — unchanged from session 10, and
still not an optimisation. Benchmark `x68000 -exp1 cz6bs1`, **never
`x68ksupr`**; MAME's internal SCSI has no DMA glue (`// TODO: duplicate DMA
glue from CZ-6BS1`) and would measure a PIO fallback the real machine does
not have.
4. **Re-decide the framerate.** 10 fps absorbs the DMA steal on current
estimates. Still the user's call, and now cheaper to defer: v7 buys back
enough of the budget that 12 fps is no longer obviously out of reach.
5. **Re-run the ring-buffer simulation at the surviving rate** and confirm the
488 KB/s figure's provenance (FINDINGS 29.5/30.7, still open).
**Do not start by hand-optimising `decode.s`.** Unchanged and still true: the
hand-derived timings agree with the measurements to 0.5% on V1 and 1% on RAW
(FINDINGS 28.4), 34 confirms the model on a second container, and the cycles to
be won are in the budget, not the loop.
**Always `stdbuf -oL` a MAME job that prints progress — and do not trust it.**
Session 11 added the case where even that is not enough (40.6). If a run is not
producing observable output, shrink the stimulus rather than waiting.
---
## What session 10 settled
Session 10 cross-checked the whole cycle model against a second emulator, then
found that the model was denominated in the wrong currency.
@@ -77,10 +179,16 @@ at **3.3%**, and it runs against us. Two incidental results worth keeping: MAME
rather than two tables; and FINDINGS 28.8's "V4 costs more than RAW" reproduces
independently. FINDINGS 37.
Everything below this line is from session 9 and still stands unless a session-10
section above says otherwise.
---
## NEXT SESSION, in order
## What session 10 put on the list
Kept for the reasoning, not the priorities: items 0 and 1 are done (FINDINGS 40)
and the rest are carried forward, reordered, in the session-11 list at the top.
Everything below this point is from session 9 or earlier and still stands unless
a later section above says otherwise.
### session 10's list
0. **Green light first.** `./tools/bench/check.sh` (~5 min, Blu-ray mounted).
Verified green at end of session 9. The gate container is now