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
|
||||
|
||||
@@ -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
|
||||
|
||||
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
|
||||
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.
|
||||
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
|
||||
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).
|
||||
@@ -229,7 +259,38 @@ SDL_VIDEODRIVER=dummy mame x68000 -bios ipl10 -video soft -window \
|
||||
- ~~Flat 4x4 VQ.~~ Rejected by eye (FINDINGS 9).
|
||||
|
||||
## 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
|
||||
- Disk image packaging
|
||||
- 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