Put the player on a real volume, and find the write window is the frame

ROADMAP K3. src/player/packed.s (2,898 B) brings up its own display, builds
its own 193-entry DMA chain, keeps its own frame clock off V-DISP and fetches
every record itself with READ(10) off a CZ-6BS1. The rig writes no picture
byte, no palette entry and no CRTC register.

120 of 120 frames pixel-exact, every one compared, in both palette orders --
the gate had to grow to do it, because a packed frame is a LITERAL and the
codec's recursion was what made one comparison audit 120.

And the write window turns out to be the frame. A packed write requires R20
bit 11, buffer mode blanks the layer, and a DMAC-direct player holds the
window open for the whole data phase, so

    dark fraction of a slot = record bytes / (DATA-PHASE rate x slot)

which is 1.0 at the container's own 582.0 KB/s: every frame delivered, on
time, pixel-exact, and none of them displayed. The rate in that expression is
the BURST rate, a third hardware number B1 has no test for. It reverses 61.5's
ranking -- a packed player that DMAs to RAM and paints with the measured 27.3%
blit is on screen 72.7% of every slot at any rate, and the two are equal only
at 2,131 KB/s = 3.7x the wire.

And a held channel costs the frame clock half its ticks without the clock
being able to tell: 487 of 1,038 V-DISP edges lost, zero late frames reported,
the player believing 12 fps while the screen ran at 6.37.

FINDINGS 64. ROADMAP K4 opened and fenced behind B2.
check.sh ALL GREEN before and after.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-25 09:10:48 -07:00
parent f1007a0dbc
commit 6f698ca226
14 changed files with 2091 additions and 23 deletions
+223 -1
View File
@@ -1,4 +1,161 @@
# Status & next-session handoff — end of session 31 (2026-08-25)
# Status & next-session handoff — end of session 32 (2026-08-25)
## Session 32: the packed player runs, and the write window turns out to be the frame
**Green light first and last: `./tools/bench/check.sh` was ALL GREEN before any
of this (`tmp/check_s32_start.log`) and ALL GREEN after** — the same stages, plus
one new one.
**FINDINGS 64. ROADMAP K3 is done.** `src/player/packed.s` (2,898 B),
`tools/bench/packed.lua`, `tools/bench/prep_packed.py`,
`tools/bench/packed_run.sh`, `tools/bench/verify_packed.py`,
`tools/analysis/31_display_duty.py`. `tools/bench/mkvol.sh` grew a DLXP arm —
for a packed container the file **is** the volume, which is the format's own
claim arriving at the disc.
**Everything below is EMULATED**: MAME 0.277 `x68000`, `-bios ipl10`, stock
10 MHz / 2 MB, `-exp1 cz6bs1`, a real CHD volume. No real hardware ran.
**1. THE PLAYER WORKS, AND IT IS THE STRONGEST END-TO-END RESULT IN THE TREE.**
The 68000 brings up its own display (CRTC mode, both scroll register pairs,
priority `vc1 = $0002`, R20 bit 11), builds its own **193-entry** DMA chain,
keeps its own frame clock off V-DISP, and fetches every record itself with
`READ(10)`. The rig writes no picture byte, no palette entry and no CRTC
register.
| | |
|---|---|
| frames | **120 of 120 pixel-exact**, EVERY ONE COMPARED |
| both palette orders | palette FIRST and `--palette-last`, **120/120 each** |
| chain | `$E82000`/512 B then 192 rows of 256 B at the 1,024 B stride — one start |
| record `i` | LBA `1 + i*97`. Arithmetic. No index, no ring, no decoder |
The gate had to change shape: `verify_decode.py` checks the LAST frame and that
audits 120 because the codec is temporally recursive. **A packed frame is a
literal**, so `verify_packed.py` compares all of them, letterbox included.
**2. THE HEADLINE, AND IT IS NOT THE GOOD HALF.** The free-running run — which
is what a 12 fps player becomes once the transfer is longer than the slot —
reported a number no budget in this tree has a column for: **the write window
was open on 99.5% of the host frames.** Every frame was pixel-exact and almost none of them was
visible. It is arithmetic: a packed write needs R20 bit 11, buffer mode blanks
the layer, and a DMAC-direct player holds the window open for the whole data
phase — there is no second page to hide behind, because the packed layout spends
both.
dark fraction of a slot = record bytes / (DATA-PHASE rate x slot)
**The rate in that expression is the BURST rate, not the sustained one**, and
that is a third hardware number **B1 has no test for**. At the container's own
582.0 KB/s the dark fraction is **1.0**: every frame delivered, on time,
pixel-exact, and **none of them displayed**.
**3. AND IT REVERSES 61.5's RANKING.** A packed player that DMAs into RAM with
the window SHUT and paints with the measured `movem` blit (27.3% of a slot) is
on screen **72.7% of every slot at any rate**. The two are equal only at a
data-phase rate of **2,131 KB/s = 3.7x the wire**. Below that — every rate
anyone has proposed — **the player with the CPU in the loop is on screen
longer.** That is ROADMAP **K4**, and it costs 82.2% of a frame at the 9 clk/B
floor against 54.9%, plus 99,328 B of RAM for two record buffers.
**4. A HELD CHANNEL COSTS THE FRAME CLOCK HALF ITS TICKS, AND THE CLOCK CANNOT
TELL.** `clock.i` counts V-DISP interrupts; a held channel halts the 68000; the
MFP's pending bit is one bit. Held at 12 fps: **487 of 1,038 edges lost
(46.9%)**, and the player reported **zero late frames** — because `PACE` is
advanced by the ISR the channel stops from running, so the loss cancels out of
the comparison. The player believed 12 fps; the screen was at **6.37**. Only the
host's raster count contradicts it, and `packed_run.sh` gates on the difference
being non-zero. Stealing at half rate loses 0.6%. Held, the CPU went round its
transfer wait **120 times in 120 frames** — once each; it never executed during a
transfer.
**5. THE CHANNEL CONFIGURATION DOES NOT SET THE TRANSPORT'S TIME.** Free-running,
held and stealing delivered the same record within **0.5%** (90.27 vs 90.72 ms).
That figure is the APPARATUS — MAME's device models carry no transfer timing —
but the negative is real and no arithmetic could have given it: **a channel
configuration buys who owns the CPU, not when the picture appears.**
**RISKS IN THIS SESSION'S RESULT, stated rather than left to be found:**
- **Everything in 2 and 3 is conditional on B2**, and B2 is still unmeasured on
a board. What changed is the stakes: it now picks WHICH packed player, and
each answer makes the other one's design work wasted.
- **MAME's ~535 KB/s transport ceiling is not diagnosed.** It is not the DMAC
(both configurations agree) and not the CPU (held, it is halted throughout),
and no MAME source tree was on this machine to name it. It bounds nothing in
the arithmetic, which is geometry, but it is the reason the gate is paced at
half rate and that should not be mistaken for a rig limitation being worked
around.
- **The cadence sampler is quantised to one host frame** (17.6 ms). The MEAN
over 120 frames is not (±0.30 ms); the min/median/max are the sampler's.
## HANDOFF — start here
**THE TREE IS ALL GREEN**, session 32's stage included (`tmp/check_s33_start.log`,
ALL GREEN, ~6 min).
### The work, in the order it should be done
**1. NOT K4 — NOT YET.** ROADMAP K4 is written and priced (82.2% of a frame at
the 9 clk/B floor, 99,328 B of RAM, on screen 72.7% of every slot at any rate),
and it is **conditional on B2**, which is a board question nobody here can
answer. If buffer mode does NOT blank, K3's player is already on screen the
whole slot and K4's paint is 27.3% of a frame spent on nothing. 61.7 wrote that
rule for the codec; it applies to the branch that replaced it. **Do not open
K4 until B2 comes back.**
**2. WHAT IS NOT BLOCKED IS P6 — AUDIO.** It is the largest unpriced item left
in the project and it is on M3's critical path, which is the completion target.
The bus half closed in session 20 (52: 1.25%..1.48% of a frame, and the unit
error was caught) — **everything else in it is open**: extraction, an encoder,
the container interleave, and what a second stream does to `wire` and therefore
to 51.3's refill climb. It is independent of B2 in the part that matters: both
packed containers and the codec carry the same audio.
Three things make it a session rather than a chore:
- **No ffmpeg ENCODER exists for this format.** `adpcm_ima_oki` is a decoder
only, so the encoder is ours to write and ours to gate.
- **The two available references DISAGREE, and it was MEASURED not recalled**
(session 33, `tmp/adpcm_probe/`). ffmpeg's `adpcm_ima_oki` is
`((2*delta+1) * step) >> 3`, 12-bit signal clipped to [-2048, 2047], output
x16, **high nibble first** — reproduced sample-exact over 2,268 nibbles. The
OKI datasheet form truncates per term (`step/8 + step + step/2 + step/4`,
selected by nibble bits) and differs on **445 of those 2,268 samples, by up to
4 in 12-bit units**. Which one the machine runs is not a matter of taste, and
**MAME has an `okim6258` in the x68000 — so it can be asked.**
- **DLXP1 has no room for it.** A packed record is 97 sectors EXACTLY because
49,664 B is what a literal frame costs; audio does not fit in the slack,
because there is none. What audio does to the packed record's geometry is a
container question, not an encoder one.
### What is PARKED, so it is not re-derived
**E7, E4 and C1** (61.8), and **P4a's wiring** for the video path (K3 deletes the
ring from it). Do not delete any of it — B2 is unanswered.
### Risks that are OURS, not hardware
1. **A chained transfer has never run back to back at 12 fps and now it has**,
120 times, pixel-exact — but only at HALF rate, because at full rate there is
nothing to snapshot (64.2). The gate's pacing is a consequence of the finding.
2. **The packed player deletes `ring.i`, `xfer.i` and most of `stream.s`.**
Unchanged: a simplification that large usually hides something, and it has
now run clean for 120 frames without finding out what.
3. **MAME's ~535 KB/s transport ceiling is undiagnosed** (64.4). It bounds
nothing in the arithmetic, which is geometry.
4. **B2 sits over the branch and now picks WHICH PLAYER**, not how much headroom
one has.
### Reproducing this session
./tools/bench/check.sh # ALL GREEN
bash tools/bench/packed_run.sh tmp/packed_singe.dlxp # all four runs
python3 tools/analysis/31_display_duty.py
**WHAT IS NEXT.** P6: audio, from the disc to a byte count that the container
has to carry.
---
## Session 31: the packed container, and the palette that buys 2.31 dB has a price
@@ -1704,6 +1861,39 @@ Green light: `./tools/bench/check.sh` **ALL GREEN**, before and after.
## NEXT SESSION, in order
**AS OF SESSION 32 the top of the list is this, and the rest of this section is
the session-19 list kept for its detail.** `docs/ROADMAP.md` is the current
shape; where the two disagree, ROADMAP is the one that was edited last.
1. **B2, and it is now the fork rather than a headroom question.**
`tools/bench/probe_bit11_blank.lua` is written and settles it in minutes on a
real board. FINDINGS 64.2 is why it moved to the top: **if buffer mode
blanks, the K3 player that now exists shows nothing at the container's own
wire and K4 is the player; if it does not blank, K3 is the player and K4's
27.3% paint is spent on nothing.** Each answer makes the other branch's work
wasted, so building either before the answer is the one thing to avoid.
2. **B1 gained a THIRD number: the DATA-PHASE BURST RATE** (64.2). Sustained
throughput decides whether record *i* arrives before slot *i*; the burst rate
decides how much of the slot the picture is on screen. A drive with a
read-ahead cache can pass one and fail the other. The acceptance test is
`record / (burst x slot)` = the dark fraction, and it is **1.0 at 582.0 KB/s**
and 0.273 — the same as K4's paint — only at **2,131 KB/s**.
3. **K4, and NOT before item 1.** ROADMAP has the shape. The one thing in it
that has never been run is the **overlap**: a channel filling buffer *i+1*
while the CPU paints buffer *i*, which is the first time the DMAC and the
68000 have had to want the bus at the same time for a whole scene.
4. **Diagnose MAME's transport ceiling, or stop quoting it.** ~535 KB/s a record
in both channel configurations, mechanism unknown, no MAME source on this
machine. It bounds nothing — the arithmetic in 64.2 is geometry — but it is
why `check.sh`'s packed stage is paced at half rate, and an undiagnosed
apparatus constant is exactly the shape of thing FINDINGS 42.1 caught.
---
**Item 4 is CLOSED (session 19, FINDINGS 51).** The list below is otherwise
unchanged, and items 1-3 are all still blocked on hardware this machine does not
have. What session 19 adds to item 1 is a SECOND number to measure the medium
@@ -2984,6 +3174,38 @@ flattering possible way to state one.
**Do not run this alongside another MAME job.** They share `tmp/` logs and
snapshot directories and the second run silently truncates the first.
## Reproducing the packed player (session 32)
```
python3 tools/encoder/pack.py tmp/fr_singe tmp/packed_singe.dlxp --nframes 120
./tools/bench/packed_run.sh tmp/packed_singe.dlxp
```
~4 min, four MAME runs. `DLX_PK_GATE_ONLY=1` runs only the first, which is what
`check.sh` takes. Expected: 120 of 120 pixel-exact; 193 chain entries; 0 late
frames; 0 frames unsampled; a free-running transport of ~90 ms a record; and
**487 of 1,038 V-DISP edges lost in the held configuration**, which is the run's
whole point and is asserted rather than admired.
The palette-LAST container is a flag and not a re-encode:
```
python3 tools/encoder/pack.py tmp/fr_singe tmp/packed_singe_last.dlxp \
--nframes 120 --palette-last
./tools/bench/packed_run.sh tmp/packed_singe_last.dlxp
```
The chain assertion is read out of the container's own flags, so a run that
built the array the other way round fails instead of painting 192 rows of
picture into the palette registers.
And the arithmetic underneath the result, which needs no emulator:
```
python3 tools/analysis/31_display_duty.py tmp/packed_singe.dlxp \
--rate 582 700 1164 2131 3000
```
`--rate` is REQUIRED and has no default, for FINDINGS 50's reason.
**Do not run this alongside another MAME job.** It shares `tmp/` logs with every
other rig here.
## Reproducing the decoder result (session 7)
```