Handoff: reconcile docs with the verified display path
Session 3 summary in STATUS.md, plus the things a cold start needs. - Reproduce section for the display result, verified cold from the Blu-ray at end of session: extract -> prep -> MAME -> verify, exact match, 38.88 dB. The frames are not in the repo and the old ones lived in /tmp, so the chain starts from extract.py rather than assuming a scratch directory survives. - tools/bench/verify_frame.py turns FINDINGS 22 into a regression check. It is deliberately an exact test rather than a PSNR threshold, since the whole point of that section is that the render is bit-for-bit predictable. It prints the three registers to check when it fails. - Recorded where the MAME source now lives, and why to read it first: six register-poking attempts failed against a gate that one grep found. - Split the CRTC mode table out as its own next step. It is the prerequisite for the decoder skeleton and the smallest well-defined task available, with an explicit warning not to write the timing values from memory. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
+63
-2
@@ -1,4 +1,4 @@
|
|||||||
# Status & next-session handoff — end of session 2 (2026-08-23)
|
# Status & next-session handoff — end of session 3 (2026-08-23)
|
||||||
|
|
||||||
## Decisions locked
|
## Decisions locked
|
||||||
|
|
||||||
@@ -51,6 +51,27 @@ rate-distortion curve, not two codecs.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## What session 3 settled
|
||||||
|
|
||||||
|
1. **The display path works and is verified end to end.** First real frame on an
|
||||||
|
emulated X68000 screen: `docs/images/x68k_first_frame_compare.png`. Full
|
||||||
|
write-up in **FINDINGS 22**. Everything before this session was Python-side
|
||||||
|
or a headless `-video none` run, which cannot snapshot at all.
|
||||||
|
2. **The render is pixel-exact, not merely close.** With monitor contrast at 15,
|
||||||
|
all 256 palette entries render exactly as `GGGGGRRRRRBBBBBI` + `pal6bit`
|
||||||
|
predicts. That exactness is the regression test — see
|
||||||
|
`tools/bench/verify_frame.py`, which exits non-zero if it ever drifts.
|
||||||
|
3. **Three hardware facts that were previously assumed are now confirmed from
|
||||||
|
MAME 0.277 source**, not folklore: the palette word format, the 1024-byte
|
||||||
|
GVRAM line stride, and the 256-colour page aliasing in `HARDWARE.md`. All
|
||||||
|
three were already written down correctly; they are now cited.
|
||||||
|
4. **A new quality ceiling was measured** — the 15-bit+I palette alone costs
|
||||||
|
38.88 dB, the same order as the `scsi` profile's own codec error. FINDINGS
|
||||||
|
22.4. This bounds how much further `scsi` is worth raising.
|
||||||
|
5. **Two shell traps that wedged session 2's background jobs** are documented in
|
||||||
|
the working-setup section below. They cost ~1.5 h of wall clock and a wedged
|
||||||
|
CPU core, and one of them was hit again this session.
|
||||||
|
|
||||||
## What session 2 settled
|
## What session 2 settled
|
||||||
|
|
||||||
1. **The critical-path question is answered.** "Does VQ soften Bluth's linework
|
1. **The critical-path question is answered.** "Does VQ soften Bluth's linework
|
||||||
@@ -205,6 +226,15 @@ SDL_VIDEODRIVER=dummy mame x68000 -bios ipl10 -video soft -window \
|
|||||||
68000's output against. Validates the 38% full-frame blit estimate that the
|
68000's output against. Validates the 38% full-frame blit estimate that the
|
||||||
whole CPU budget rests on. Still needs a real CRTC mode table for 256x256;
|
whole CPU budget rests on. Still needs a real CRTC mode table for 256x256;
|
||||||
the harness deliberately borrows the IPL's timing and invents nothing.
|
the harness deliberately borrows the IPL's timing and invents nothing.
|
||||||
|
2a. **CRTC mode table for 256x192-in-256x256.** Prerequisite for (2) and the
|
||||||
|
smallest well-defined unit of work available right now. Needs real R00-R08
|
||||||
|
timing values. **Do not write these from memory** — session 3 lost time to
|
||||||
|
exactly that failure mode on the video registers. Derive them from the CRTC
|
||||||
|
dividers in `x68k_crtc.cpp` (`m_reg[20] & 0x1f` selects the dot-clock
|
||||||
|
divisor; the IPL's `0x16` gives /2 off the 69MHz clock), or lift a known-good
|
||||||
|
set from a real X68000 title and verify by snapshot. The harness makes this
|
||||||
|
cheap to iterate: change values, snapshot, look.
|
||||||
|
|
||||||
3. **Wire rate control into `encode.py`.** No longer a blocker (FINDINGS 21), but
|
3. **Wire rate control into `encode.py`.** No longer a blocker (FINDINGS 21), but
|
||||||
it is what gives a deterministic ceiling over content not yet measured, which
|
it is what gives a deterministic ceiling over content not yet measured, which
|
||||||
was the original reason for choosing VQ. Insurance, not a fix. Pairs with (1).
|
was the original reason for choosing VQ. Insurance, not a fix. Pairs with (1).
|
||||||
@@ -229,7 +259,38 @@ SDL_VIDEODRIVER=dummy mame x68000 -bios ipl10 -video soft -window \
|
|||||||
- ~~Flat 4x4 VQ.~~ Rejected by eye (FINDINGS 9).
|
- ~~Flat 4x4 VQ.~~ Rejected by eye (FINDINGS 9).
|
||||||
|
|
||||||
## Not yet started
|
## Not yet started
|
||||||
- Any 68000 player code
|
- **Any 68000 player code.** `src/player/` is still empty. The display path is
|
||||||
|
proven, but proven *from Lua* — no 68000 instruction has yet drawn a pixel.
|
||||||
|
- **A real CRTC mode table.** The harness deliberately borrows the IPL's 768x512
|
||||||
|
text timing and invents no CRTC values, which is why the frame repeats at
|
||||||
|
x=512 (FINDINGS 22.5). A 256x256 mode needs real R00-R08 values, and those
|
||||||
|
must be derived or measured, NOT recalled from memory — see the note below.
|
||||||
- ADPCM audio extraction/encoding
|
- ADPCM audio extraction/encoding
|
||||||
- Disk image packaging
|
- Disk image packaging
|
||||||
- Game logic (scene branching, input windows, death clips)
|
- Game logic (scene branching, input windows, death clips)
|
||||||
|
|
||||||
|
## Reproducing the display result
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 tools/encoder/extract.py 00020 tmp/fr_00020 12 crop
|
||||||
|
python3 tools/bench/prep_frame.py tmp/fr_00020 tmp/frame.bin 0
|
||||||
|
mkdir -p tmp/snap_verify && cd tmp && SDL_VIDEODRIVER=dummy mame x68000 -bios ipl10 \
|
||||||
|
-video soft -window -sound none -nothrottle -plugins \
|
||||||
|
-autoboot_script ../tools/bench/show_frame.lua \
|
||||||
|
-snapshot_directory ./snap_verify -snapview native -seconds_to_run 6
|
||||||
|
cd .. && python3 tools/bench/verify_frame.py
|
||||||
|
```
|
||||||
|
Verified cold from the Blu-ray at end of session 3: exact match, 38.88 dB.
|
||||||
|
|
||||||
|
`tmp/` is gitignored scratch. The frames are NOT in the repo — regenerate them
|
||||||
|
with `extract.py`; the earlier ones lived in `/tmp` and do not survive a reboot.
|
||||||
|
|
||||||
|
## Reference material on this box (not in the repo)
|
||||||
|
|
||||||
|
- **MAME 0.277 source: `~/src/mame-mame0277/`** (tarball `~/src/mame0277.tar.gz`).
|
||||||
|
Downloaded this session to settle the graphics-layer question. The files that
|
||||||
|
matter are `src/mame/sharp/x68k_v.cpp`, `x68k_crtc.cpp`, `x68k_crtc.h`,
|
||||||
|
`x68k.cpp`. **Read these before theorising about X68000 video behaviour** —
|
||||||
|
six register-poking attempts failed against a gate that one grep found.
|
||||||
|
- Blu-ray mounted at `/media/reala-misaki/BDROM` via
|
||||||
|
`udisksctl loop-setup -r -f DRAGONS_LAIR.iso`.
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for the display path (FINDINGS 22).
|
||||||
|
|
||||||
|
Compares a MAME snapshot of GVRAM against what the X68000's 15-bit+I palette
|
||||||
|
MUST produce for the same source frame. This is an exact test, not a threshold:
|
||||||
|
if the palette packing, the line stride, or the monitor-contrast setting drifts,
|
||||||
|
`exact` goes False. Do not soften it into a PSNR threshold -- the whole point of
|
||||||
|
FINDINGS 22 is that the render is bit-for-bit predictable.
|
||||||
|
|
||||||
|
python3 tools/bench/verify_frame.py tmp/frame_ref.png tmp/snap_verify/x68000/0000.png
|
||||||
|
"""
|
||||||
|
import sys, numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
ref_path = sys.argv[1] if len(sys.argv) > 1 else "tmp/frame_ref.png"
|
||||||
|
snap_path = sys.argv[2] if len(sys.argv) > 2 else "tmp/snap_verify/x68000/0000.png"
|
||||||
|
|
||||||
|
ref = np.asarray(Image.open(ref_path).convert("RGB")).astype(int)
|
||||||
|
snap = np.asarray(Image.open(snap_path).convert("RGB")).astype(int)
|
||||||
|
H, W = ref.shape[:2]
|
||||||
|
got = snap[0:H, 0:W]
|
||||||
|
|
||||||
|
# MAME x68k_v.cpp: GGGGGRRRRRBBBBBI, expanded via pal6bit((field << 1) | I)
|
||||||
|
pal6bit = lambda v: (v << 2) | (v >> 4)
|
||||||
|
pred = pal6bit(((ref >> 3) << 1) | 1)
|
||||||
|
|
||||||
|
exact = bool((pred == got).all())
|
||||||
|
mse = ((ref - got) ** 2).mean()
|
||||||
|
psnr = 99.0 if mse == 0 else 10 * np.log10(255 * 255 / mse)
|
||||||
|
|
||||||
|
print(f"exact match vs 15-bit+I prediction : {exact}")
|
||||||
|
print(f"display quantisation cost : {psnr:.2f} dB (expected 38.88 on 00020 f0001)")
|
||||||
|
if not exact:
|
||||||
|
bad = np.argwhere((pred != got).any(axis=2))
|
||||||
|
print(f" {len(bad)} differing pixels; first at {tuple(bad[0])}"
|
||||||
|
f" pred={tuple(pred[tuple(bad[0])])} got={tuple(got[tuple(bad[0])])}")
|
||||||
|
print(" check: CRTC R20 bit 11 clear, R20 bits 9-8 = 0x0100, $E8E001 contrast = 15")
|
||||||
|
sys.exit(0 if exact else 1)
|
||||||
Reference in New Issue
Block a user