The profiles shipped in e4062ed were set far too low. 45 KB/s (sasi) and
75 KB/s (scsi) are 12% and 7% of the respective folklore bus figures. They had
been read off the knee of the rate-distortion curve and then presented as
though bandwidth-derived, which they were not.
Raised to sasi 110 KB/s (lam=60) and scsi 280 KB/s (lam=10) -- 35% and 28%
utilisation. scsi is now within 0.52 dB of the palette ceiling on scene 00020.
Checking the CPU side, which nobody had done for the decode path, produces a
second and more important result. Against the 833k cycle/frame budget at 12fps:
full-frame blit, every frame 319k 38% affordable
LZ4/LZSS decode ~30KB/frame 450k 54%
deflate decode ~30KB/frame 1800k 216% infeasible
So raising the VQ bitrate is nearly free -- RAW, the mode that dominates at
high rate, is the cheapest mode to blit -- but entropy coding is not viable at
all. That demotes the "247 KB/s lossless changed-spans+deflate" figure from
FINDINGS 8 to a compression upper bound rather than a shippable design, and
removes entropy coding from the roadmap. VQ is the right architecture precisely
because its decode is a table copy.
Also confirms the architecture unifies: the hybrid at lam=0 lands within 3% of
the purpose-built lossless coder, so there is no separate lossless path.
Consequence for planning: the blocked disk benchmark is now critical-path, not
optional. If SCSI sustains >=800 KB/s the correct scsi profile is lam=0 --
pixel-exact video at ~450 KB/s and 38% CPU. Whether this port ships transparent
or lossy on SCSI is waiting on one measurement.
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
7.4 KiB
Status & next-session handoff — end of session 2 (2026-08-23)
Decisions locked
| decision | value | why |
|---|---|---|
| Target CPU | 68000 @ 10MHz (stock) | hardest honest constraint |
| Display mode | 256 colors, 256x192 in 256x256 CRTC mode | every mode is 1 word-access/pixel, so 256c is free vs 16c |
| Double buffer | none — page 1 sacrificed | enables movem.l 24px bursts; delta coding needs a RAM reference frame anyway |
| Codec | hybrid VQ: SKIP / V1 4x4 / V4 four-2x2 / RAW, per-block rate-distortion | flat 4x4 VQ was measured and rejected — see FINDINGS 9-10 |
| Quality modes | two: sasi and scsi (USER DECISION, session 2) |
one codec, one decoder, one bitstream; only lam differs |
| Framerate | 12 fps, explicit decimation | source has zero duplicate frames; no free "twos" win |
| Emulator | MAME 0.277 x68000 | accurate enough that measured cycles mean something |
| SNES project reuse | MIT — cleared | data/events/ scene graph is reusable with attribution |
The SASI/SCSI question is RESOLVED
Session 1 left "which machine do we target" open. The user's answer: ship both,
as two quality profiles. This is now implemented rather than hypothetical — the
bitrate ceiling is a build parameter in tools/encoder/ratectl.py:
| profile | target | lam | quality (00020 / 00146) | bus utilisation | machine |
|---|---|---|---|---|---|
sasi |
110 KB/s | 60 | 36.9 / 29.6 dB | 35% of 300 KB/s | stock 10MHz ACE/EXPERT |
scsi |
280 KB/s | 10 | 39.4 / 32.3 dB | 28% of 1 MB/s | Super/XVI, or CZ-6BS1 board |
scsi is now within 0.5 dB of the palette ceiling on 00020. These were
initially set at 45 / 75 KB/s, which was 12% / 7% bus utilisation — read off the
RD curve rather than derived from the hardware. See FINDINGS 17.
Codebooks are k=256 with 1-byte indices in both profiles. k=1024 was measured
and rejected — see FINDINGS 14, it was a false-good result from a rate model
that undercharged the index. Do not ship past lam~800; FINDINGS 15 has the cliff.
Because of the RAW escape mode, lam=0 is pixel-exact against the palettised
frame (measured 0.00 dB loss). The profiles are two points on one continuous
rate-distortion curve, not two codecs.
What session 2 settled
- The critical-path question is answered. "Does VQ soften Bluth's linework
unacceptably?" — flat 4x4 k=256 VQ: yes, badly. Hybrid VQ with k=1024: no.
Verified by eye, not just PSNR. See
docs/FINDINGS.md9-11. - Session 1's 12fps bitrate was wrong (183 KB/s claimed, 340 KB/s measured). Halving the framerate does not halve the bitrate. FINDINGS 8. 2b. A fourth false-good result was produced and caught this session — k=1024 codebooks looked like a +2.4 dB free win because the rate model charged 1 byte for a 10-bit index. FINDINGS 14. The k=256 configuration ships.
- The 256-colour palettised frame is the real quality ceiling and it looks excellent. Judge the codec against that, not against 1080p.
- Encoder exists and produces a real bitstream:
tools/encoder/.
Encoder — working
python3 tools/encoder/extract.py 00020 /tmp/fr_00020 12 crop
python3 tools/encoder/encode.py /tmp/fr_00020 out.dlx --profile sasi --preview p.png
| file | role |
|---|---|
extract.py |
.m2ts -> 256x192 PNGs, 12fps, spatial-only denoise |
vq.py |
palette, blockify, hand-rolled k-means (no sklearn on this box), PSNR |
vq_hybrid.py |
the codec: 4 block modes + lagrangian mode decision |
ratectl.py |
SASI/SCSI profiles, leaky-bucket rate control |
encode.py |
CLI + DLX1 container writer |
DLX1 container layout is documented in the encode.py docstring. All
multi-byte fields are big-endian so the 68000 reads them with a plain move.
Known encoder gaps
- Rate control is written but not yet wired into
encode.py— the CLI uses a fixedlamfrom the profile.ratectl.encode_rate_controlled()exists and builds a lam-ladder per frame; it needs hooking up and validating. - Payload is deliberately NOT entropy-coded — deflate decode does not fit in the 68000's frame budget (FINDINGS 17.2). Do not "optimise" this later.
- Codebooks are per-scene and rebuilt from scratch; no inter-scene reuse.
_paintis a Python per-block loop — fine for prototyping, slow for a full disc encode. Vectorise before the 224-stream run.
Working setup (unchanged from session 1, re-verified)
MAME ROMs — ~/mame/roms/x68000.zip. Must pass -bios ipl10.
mame x68000 -bios ipl10 -video none -sound none -nothrottle -seconds_to_run 3
Assembler — tools/vasm/vasmm68k_mot -Fbin -o out.bin in.s
Blu-ray — udisksctl loop-setup -r -f DRAGONS_LAIR.iso -> /media/reala-misaki/BDROM
(still mounted as of end of session 2).
MAME Lua harness — tools/bench/*.lua, working. Three gotchas (retain the
notifier subscription in a global; the stack register is SP not A7;
autoboot_script fires at PC=0 before boot) are documented in FINDINGS.
Two shell traps, both hit again this session:
- piping MAME (or any long job) through
grepblock-buffers — write to a file. pkill -f <pattern>matches your own shell and kills it (exit 144). Usepkill -xor kill by PID.
STILL BLOCKED: disk throughput benchmark
Unchanged from session 1 — IOCS _B_READ returns -1 uniformly. Full diagnosis
and the four untested hypotheses are in session 1's notes (git history of this
file, commit 65112b9).
This is now CRITICAL-PATH, not optional. Session 1 dismissed it because "VQ at 30 KB/s is correct whether SASI does 300 or 600 KB/s". That reasoning is dead: the profiles now sit at 110 and 280 KB/s, close enough to the folklore ceilings that the error bars change the product. Specifically —
If SCSI sustains >=800 KB/s, the correct scsi profile is lam=0: pixel-exact
video, ~450 KB/s, and only 38% of the CPU budget. Whether this port ships
transparent or lossy on SCSI is waiting on one measurement.
Next move is the untried SCSI path: -exp1 cz6bs1 -hard disk.chd.
Next steps, in priority order
- Wire rate control into
encode.pyand validate that the hard ceiling actually holds on an action scene (the whole point of choosing VQ). Entropy-code the payload— ABANDONED, see FINDINGS 17.2. Deflate decode is ~216% of the frame budget on a 68000 and LZ4 is ~54%; there is no room beside a 38% blit. All bitrates are raw payload. This also demotes the "247 KB/s lossless" figure in FINDINGS 8 to a compression bound, not a design.- 68000 decoder skeleton: parse
DLX1, expand codebooks to word-per-pixel, blit V1/V4/RAW/SKIP. Measure real cycles with the existing MAME Lua harness — this is the first time the harness gets used for its actual purpose. - Full-disc survey — classify menu vs content first (FINDINGS 13), then measure bitrate across all 224 streams per profile.
- Resolve the framing question (FINDINGS 12: crop vs squash vs wide).
- Unblock the disk benchmark via the SCSI path, then re-set profile bitrates.
- Import the SNES project's
data/events/(MIT, cleared) as the scene graph. Cross-check against DirkSimple (zlib) which has the same data independently. - ADPCM audio: MSM6258, 15.6kHz mono, 7.8 KB/s — already budgeted in
ratectl, not yet extracted or encoded.
Not yet started
- Any 68000 player code
- ADPCM audio extraction/encoding
- Disk image packaging
- Game logic (scene branching, input windows, death clips)