Put the palette on the channel, and find one start paints a whole frame
ROADMAP K1, the packed player's one open structural item. A frame is a picture
AND a palette, and no run in this tree had pointed a DMA channel at the palette
registers. dmagate.s runs 7-9, gated by dma_run.sh and check.sh:
7. 512 B off the disc into $E82000, bus held -- byte-exact in 256 register
words, read back OUT OF the registers by the 68000;
8. the SAME transfer aimed at RAM -- byte-exact at $2C000, and 256 of 256
palette words still read the poison the CPU wrote, which is what attributes
run 7 to the channel's MAR rather than to the readback path;
9. ONE array-chained start across two kinds of destination -- the palette and
six picture rows at the 1,024 B line stride, 2,048 B byte-exact.
So a packed frame is one channel start: a 193-entry array, palette first, CPU
halted from the first byte to the last. The array is scene-constant, because
the packed layout spends both 256-colour pages and there is no page to flip.
What is left on the CPU per frame in the video path is the channel start and the
READ(10) -- no per-frame PAINT, which is not the same claim as no per-frame CPU.
The destination is POISONED first (62.1). Runs 4-6 wrote into RAM that was zero
and GVRAM that was stale against a record that is mostly pad; "it matches the
disc" was weaker than it read as. The host counts whether the poison actually
discriminates instead of assuming it: 511 of 512, and the gate refuses under 500.
And it opened a hardware item (62.4, ROADMAP B4). MAME maps the palette to
palette_device over memory_array, whose write16 is a plain COMBINE_DATA -- RAM
that honours mem_mask, with no handler that could refuse a byte write. Unlike
GVRAM's 256-colour arm there is nothing here to be wrong about, so the run
bounds the model and not the board. What a real palette register does with a
byte write is unmeasured. A negative costs 0.28% of a frame and nothing else.
29_packed_player.py now also prints the two rows with the per-frame palette
charged -- 55.7% of a frame on the chain, 582 KB/s -- alongside the picture-only
figures the codec comparison is quoted against.
check.sh ALL GREEN before (tmp/check_s30_start.log) and after
(tmp/check_s30_end.log).
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
+116
@@ -1,3 +1,119 @@
|
||||
# Status & next-session handoff — end of session 30 (2026-08-25)
|
||||
|
||||
## Session 30: one channel start paints a whole frame, and the palette has no handler to be wrong about
|
||||
|
||||
**Green light first and last: `./tools/bench/check.sh` was ALL GREEN before any
|
||||
of this (`tmp/check_s30_start.log`) and ALL GREEN after** — the same stages, with
|
||||
three new runs inside the DMA gate.
|
||||
|
||||
**FINDINGS 62.** **Emulated** — MAME 0.277, `x68000 -bios ipl10`, `-exp1 cz6bs1`.
|
||||
No real hardware ran. 47.4 — does buffer mode blank the layer — is still the
|
||||
board question and is still open, and **this session opened a second one.**
|
||||
|
||||
**FIRST, THE UNRECORDED WORK WAS COMMITTED.** Sessions 28 and 29 were sitting in
|
||||
a dirty tree with the last commit being session 27's. They are one commit now
|
||||
(`1be428c`), because their edits share files and could not be split honestly
|
||||
after the fact. Session 30's own work is a commit of its own.
|
||||
|
||||
**THE HEADLINE. ROADMAP K1 is done and the answer is the good one: a DMA channel
|
||||
writes the palette registers at `$E82000`, and one array-chained start crosses
|
||||
from device registers into GVRAM.** So a packed frame is **one channel start**:
|
||||
a 193-entry array — the palette, then 192 picture rows at the 1,024 B line
|
||||
stride — walked by the channel with the 68000 halted from the first byte to the
|
||||
last. `dmagate.s` runs 7, 8 and 9; `dma_run.sh` gates all three; `check.sh` runs
|
||||
it.
|
||||
|
||||
| run | result |
|
||||
|---|---|
|
||||
| **7. DMA → `$E82000`, bus held** | **512 B byte-exact in 256 register words**, read back out of the registers by the 68000 |
|
||||
| **8. the SAME transfer aimed at RAM** | byte-exact at `$2C000`, and **256 of 256 palette words still poison** |
|
||||
| **9. ONE array-chained start, palette + 6 rows** | **2,048 B byte-exact across BOTH kinds of destination**, `MAR` at `+5,376` |
|
||||
|
||||
**1. THE DESTINATION IS POISONED NOW, and it should have been all along** (62.1).
|
||||
Runs 4–6 wrote into RAM that was zero and GVRAM that was stale, against a record
|
||||
that is mostly pad — "it matches the disc" was a weaker claim than it read as.
|
||||
`dg_poison` puts `$A500|i` in palette word *i* first, and the host **counts**
|
||||
whether that actually discriminates rather than assuming it: 511 of 512
|
||||
positions differ from the disc's bytes, and the gate refuses a run under 500.
|
||||
|
||||
**2. THE CONTROL IS AN ATTRIBUTION CONTROL, and it is a different shape from run
|
||||
5's** (62.2). There is no mode bit here to point at. The same transfer aimed
|
||||
20 KB away makes two claims at once: the bytes land at `$2C000`, and the palette
|
||||
still reads poison — so what reached `$E82000` in run 7 was decided by the
|
||||
channel's `MAR`. The second half is also the positive control on the readback
|
||||
path, because the poison it finds is what the CPU wrote.
|
||||
|
||||
**3. THE ARRAY IS SCENE-CONSTANT** (62.3). The row bases never change: the
|
||||
packed layout spends both 256-colour pages — page 0 the low byte of a word,
|
||||
page 1 the high — so there is no page to flip into. The 1,158 B array is built
|
||||
once at scene setup and started once a frame. **What is still on the CPU per
|
||||
frame is the channel start and the READ(10)**, and neither is priced here: say
|
||||
"no per-frame PAINT work", not "no per-frame CPU work".
|
||||
|
||||
**4. AND THE NEW BOARD QUESTION, B4** (62.4). **MAME cannot be asked this
|
||||
one.** `x68k.cpp:817` maps the palette to `palette_device::read16/write16`,
|
||||
`emupal.cpp:417` forwards to `memory_array::write16`, and `memarray.h:75` is a
|
||||
plain `COMBINE_DATA` — RAM that honours `mem_mask`, with **no handler that could
|
||||
refuse a byte write**. That is a different bound from 61.2's: GVRAM has a real
|
||||
256-colour arm, which is why run 5 could find a mechanism to fail on. What a
|
||||
real X68000 palette register does with a byte write is **UNMEASURED — not
|
||||
folklore, an absence.** B4 is the cheapest hardware item in the project: write
|
||||
`$A5` to `$E82000`, `$5A` to `$E82001`, read the word back. A negative costs
|
||||
0.28% of a frame and nothing else, which is why it sits below B2.
|
||||
|
||||
**5. ONE DESIGN CHOICE IS FILED, NOT ANSWERED** (62.5). Palette FIRST or 193rd
|
||||
is visible on screen — old rows under the new palette, or new rows under the old
|
||||
one — for one paint either way. It is moot if buffer mode blanks (B2). It is
|
||||
named so the final player's order is a decision and not an accident of which run
|
||||
happened to be written first.
|
||||
|
||||
## HANDOFF — start here
|
||||
|
||||
**THE TREE IS ALL GREEN AND COMMITTED.** Two commits: `1be428c` (sessions 28+29,
|
||||
unrecorded until now) and session 30's own.
|
||||
|
||||
### The work, in the order it should be done
|
||||
|
||||
**1. K2 — A PACKED CONTAINER.** Simpler than a coded one: quantise to **254**
|
||||
colours (index 0 is the transparency key, black at 255 — 47.2), interleave
|
||||
column *i* with *i+128*, emit a per-frame palette, sector-align the record. No
|
||||
VQ, no rate control, no mode map, no `lam`. `encode.py` already sector-aligns
|
||||
(DLX5) and `prep_frame.py --pack-transparent` already emits the exact pixel
|
||||
layout. **Re-derive 61.9's 34.08 dB against `vq.scene_palette` / `H.build`
|
||||
here** — the measured figure is PIL's MEDIANCUT and is a direction, not the
|
||||
player's number (risk 2 below).
|
||||
|
||||
**2. K3 — END TO END, OFF THE DISC.** Scene setup — palette, page-1 X-scroll
|
||||
384, priority `vc1 = 0x0002`, R20 bit 11 — then **one chained DMA a frame**,
|
||||
now including the palette entry K1 just established. Gate it pixel-exact over
|
||||
the same 120-frame window the decoder is gated on.
|
||||
|
||||
### What is PARKED, so it is not re-derived
|
||||
|
||||
**E7, E4 and C1** (61.8), and **P4a's wiring** for the video path: a DMAC-direct
|
||||
packed player has no ring at all. Do not delete any of it — B2 is unanswered and
|
||||
48.1's prior leans against packing.
|
||||
|
||||
### Risks that are OURS, not hardware
|
||||
|
||||
1. **A chained transfer has never run back to back at 12 fps.** Unchanged by
|
||||
this session: run 9 was seven entries, once. A frame is 193 and a second is
|
||||
twelve frames.
|
||||
2. **34.08 dB is PIL's MEDIANCUT**, not `vq.scene_palette` / `H.build`.
|
||||
3. **The packed player deletes `ring.i`, `xfer.i` and most of `stream.s` from
|
||||
the video path.** A simplification that large usually hides something.
|
||||
4. **B2 sits over all of it**, and now B4 sits over the palette entry
|
||||
specifically — with a known, small blast radius.
|
||||
|
||||
### Reproducing this session
|
||||
|
||||
./tools/bench/check.sh # ALL GREEN, ~5 min
|
||||
bash tools/bench/dma_run.sh # runs 1-10, with controls
|
||||
|
||||
**WHAT IS NEXT.** K2: the packed container.
|
||||
|
||||
---
|
||||
|
||||
# Status & next-session handoff — end of session 29 (2026-08-25)
|
||||
|
||||
## Session 29: the decoder-free packed player fits the budget the codec misses
|
||||
|
||||
Reference in New Issue
Block a user