First pixels on an actual X68000 screen. Everything up to now was Python-side
or a headless -video none run, which cannot snapshot at all.
The blocker was not the video controller. The IPL leaves CRTC R20 = 0x0B16,
and bit 11 is "G-VRAM set to buffer", which makes MAME's draw_gfx() return
early. GVRAM writes still land and read back correctly while the layer is
invisible, so six attempts at $E82400/$E82500/$E82600 all rendered black with
every register holding the value I intended.
Two more facts, both confirmed against MAME 0.277 source rather than assumed:
- $E8E001 monitor contrast is left at 14 by the IPL, scaling all output to
93.3%. The player must set it to 15. Contrast 0 blanks the screen, which is
a free fade-to-black for scene transitions.
- The palette word is GGGGGRRRRRBBBBBI with a shared LSB, expanded as
pal6bit((field<<1)|I). With contrast at 15 the render is pixel-exact, not
merely close, which also confirms the 1024-byte GVRAM line stride.
That exactness gives a new quality ceiling: the 15-bit+I palette alone costs
38.88 dB against the 24-bit palettised source, the same order as the scsi
profile's own codec error. scsi is close to display-transparent on hardware,
which bounds how much further it is worth raising.
Unblocks next step 2, the 68000 decoder skeleton, which now has a known-good
reference image to diff against.
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
Answers session 1's critical-path question. Flat 4x4 VQ at k=256 was prototyped
and REJECTED by eye: Dirk's face disintegrates and ink outlines break into
4-pixel stair-steps. The 256-colour palettised frame is excellent, so the
palette was never the problem -- block VQ was.
Replaced it with a Cinepak-style hybrid: each 4x4 block is SKIP, one 4x4
codeword, four 2x2 codewords, or RAW literal pixels, chosen per block by
rate-distortion. The RAW escape makes lam=0 pixel-exact (measured 0.00 dB loss),
so the quality knob spans lossless to heavily-compressed in one bitstream.
Per the user's decision, ships TWO quality profiles from that one codec, one
decoder and one bitstream -- only the rate knob differs:
sasi 45 KB/s lam=300 34.8 dB stock 10MHz ACE/EXPERT
scsi 75 KB/s lam=100 35.9 dB Super/XVI or CZ-6BS1
Three corrections to earlier numbers:
1. Session 1's "183 KB/s at 12fps" was a bad extrapolation. Halving the
framerate does not halve the bitrate -- decimation roughly doubles the
per-frame delta. Re-measured directly: 340 KB/s for session 1's own RLE,
247 KB/s for changed-spans+deflate. The lossless floor is 319 MB.
2. A FOURTH false-good result, same family as the three in FINDINGS 4:
k=1024 codebooks appeared to buy +2.4 dB free, because the rate model
charged 1 byte for a 10-bit index. Charging the true cost reverses the
verdict -- k=256 wins at every matched bitrate, and by 5 dB at the low end
where the SASI profile lives. k=256 ships.
3. Stream inventory: the ~3-5MB clips are 1.2-1.7s, not ~60s, and some 60s
streams are menus, not content. Any survey must classify before averaging.
Also cleared both candidate sources for the game-logic layer: the SNES project
is MIT and DirkSimple is zlib, so the arcade scene graph can be imported and
the two transcriptions diffed against each other.
Encoder is working end-to-end: extract.py -> vq/vq_hybrid/ratectl -> encode.py,
emitting a big-endian DLX1 container the 68000 can parse with plain moves.
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6