Raise both quality profiles; rule out entropy coding on CPU grounds

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
This commit is contained in:
prosolis
2026-08-23 12:06:52 -07:00
parent e4062ed294
commit f0f2f807a4
3 changed files with 129 additions and 25 deletions
+80
View File
@@ -370,3 +370,83 @@ Both checked this session:
Having **two independent permissively-licensed transcriptions** of the arcade
scene graph is better than one: they can be diffed against each other to catch
transcription errors before any of it is committed to 68000 tables.
## 17. The profiles were set far too low — and entropy coding is a CPU trap
Prompted by the user asking why the SCSI profile was only 75 KB/s. It should not
have been. Two separate errors, one of them serious.
### 17.1 The profile bitrates were not derived from the hardware at all
They were read off the knee of the rate-distortion curve and then presented as
though bandwidth-driven. Against the (folklore) bus figures from 5:
| profile | was | bus figure | utilisation |
|---|---|---|---|
| `sasi` | 45 KB/s | ~300-500 KB/s | **12%** |
| `scsi` | 75 KB/s | ~1 MB/s | **7%** |
Nothing justified leaving 90% of the pipe unused. Raised to `sasi` 110 KB/s
(lam=60) and `scsi` 280 KB/s (lam=10), which is 35% and 28% utilisation —
still conservative, because the bus figures are folklore.
### 17.2 CPU is NOT the reason to stay low — but entropy coding would be
Budget is 833,333 cycles/frame at 12 fps. At session 1's measured ~6.5 cycles
per GVRAM pixel write:
| work | cycles | % of budget |
|---|---|---|
| blit 20.1% of pixels (session 1's 24fps figure) | 64k | 7.7% |
| blit 40% of pixels (the same content at 12fps) | 128k | 15.3% |
| **blit the FULL frame, every frame** | **319k** | **38.3%** |
| deflate decode, ~30 KB/frame output | 1,800k | **216%** |
| LZ4/LZSS decode, ~30 KB/frame output | 450k | **54%** |
Two conclusions, and the second one corrects 8:
- **Raising the VQ bitrate is nearly free on CPU.** Even a full-frame pixel-exact
blit fits in 38% of budget, and VQ decode is table copies — RAW, the mode that
dominates at high bitrate, is the *cheapest* mode to blit, not the dearest.
- **The 247 KB/s "lossless changed-spans + deflate" figure in 8 is a compression
upper bound, NOT a shippable design.** Deflate's Huffman decode is bitwise and
costs about 2.2x the entire frame budget on a 68000. Even byte-oriented LZ4 at
54% leaves too little beside a 38% blit. **Do not plan on entropy coding.**
All profile bitrates are raw payload.
This inverts session 1's "the CPU is idle, I/O is the ceiling" for the *decode*
path specifically: the blit is cheap, but any bit-oriented decompressor is not.
VQ is the right architecture precisely because its decode is a table copy.
### 17.3 The hybrid at lam=0 IS the lossless codec
Measured, un-entropy-coded raw payload, and deflated for reference only:
| scene | lam=0 raw | lam=0 deflated | lossless changed-spans+deflate | PSNR |
|---|---|---|---|---|
| 00020 | 442.1 KB/s | 274.5 KB/s | 267.3 KB/s | 39.90 = ceiling |
| 00146 | 467.6 KB/s | 223.2 KB/s | 219.1 KB/s | 35.25 = ceiling |
The hybrid at `lam=0` converges to within 3% of the purpose-built lossless coder.
That confirms the architecture unifies: there is no separate lossless path to
maintain, just the same bitstream with the knob open.
### 17.4 Full curve in raw (shippable) bytes
| lam | 00020 PSNR | 00020 KB/s | 00146 PSNR | 00146 KB/s | RAW% |
|---|---|---|---|---|---|
| 0 | **39.90** (exact) | 442.1 | **35.25** (exact) | 467.6 | ~76% |
| 10 | 39.38 | 248.1 | 32.27 | 305.2 | ~44% |
| 25 | 38.68 | 182.2 | 31.04 | 193.5 | ~26% |
| 60 | 36.94 | 108.0 | 29.61 | 103.1 | ~10% |
| 150 | 35.31 | 55.6 | 28.63 | 56.1 | ~1% |
| 300 | 34.80 | 44.1 | 28.28 | 44.4 | 0% |
### 17.5 This makes the blocked disk benchmark critical-path
Session 1 judged it "NOT on the critical path" because VQ at 30 KB/s was correct
whether SASI did 300 or 600 KB/s. That reasoning no longer holds. The profiles
now sit at 110 and 280 KB/s, close enough to the folklore ceilings that the
error bars matter, and **if SCSI sustains >=800 KB/s the correct `scsi` profile
is lam=0 — pixel-exact video.** Whether this port ships transparent or lossy on
SCSI is now waiting on one measurement.
+24 -13
View File
@@ -18,10 +18,14 @@ 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) | machine |
|---|---|---|---|---|
| `sasi` | 45 KB/s | 300 | 34.8 / 28.3 dB | stock 10MHz ACE/EXPERT |
| `scsi` | 75 KB/s | 100 | 35.9 / 29.0 dB | Super/XVI, or CZ-6BS1 board |
| 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
@@ -71,8 +75,8 @@ multi-byte fields are **big-endian** so the 68000 reads them with a plain `move`
- **Rate control is written but not yet wired into `encode.py`** — the CLI uses a
fixed `lam` from the profile. `ratectl.encode_rate_controlled()` exists and
builds a lam-ladder per frame; it needs hooking up and validating.
- **Payload is not entropy-coded.** Deflate on the payload should buy ~1.4x
(measured on the lossless path, FINDINGS 8). LZ decode is cheap on a 68000.
- **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.
- `_paint` is a Python per-block loop — fine for prototyping, slow for a full
disc encode. Vectorise before the 224-stream run.
@@ -107,12 +111,16 @@ 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 now matters more than session 1 thought.** Session 1 dismissed it because
"VQ at 30 KB/s is correct whether SASI does 300 or 600 KB/s". But we now ship
*two profiles*, and the profile bitrates (45 / 120 KB/s) are set against
**folklore** bandwidth figures. A real measurement would let us set them
honestly instead of conservatively. Next move is the untried SCSI path:
`-exp1 cz6bs1 -hard disk.chd`.
**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`.
---
@@ -120,7 +128,10 @@ honestly instead of conservatively. Next move is the untried SCSI path:
1. **Wire rate control into `encode.py`** and validate that the hard ceiling
actually holds on an action scene (the whole point of choosing VQ).
2. **Entropy-code the payload** (deflate) — ~1.4x for cheap 68000 decode cost.
2. ~~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.
3. **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.
+25 -12
View File
@@ -19,25 +19,38 @@ import vq_hybrid as H
# see docs/FINDINGS.md 5 -- these are FOLKLORE-grade until the disk benchmark
# is unblocked, so they are deliberately conservative fractions of the quoted
# ceiling (audio, seeks and container overhead come out of the same pipe).
# Calibrated against the CORRECTED rate-distortion measurement (FINDINGS 14).
# Calibrated against the corrected rate-distortion curve (FINDINGS 14-15) AND
# against the bus and CPU budgets (FINDINGS 17). Session 2 initially set these
# far too low: 45 / 75 KB/s is 12% of the pessimistic SASI folklore figure and
# 7% of the SCSI one. Nothing justified that -- the numbers were read off the
# knee of the RD curve, not off the hardware.
#
# k=256 with 1-byte indices beats k=1024 with 2-byte indices at every matched
# bitrate. The earlier "+2.4 dB for k=1024" was an artifact of a rate model that
# charged 1 byte for a 10-bit index. 1-byte indices also mean the 68000 decoder
# reads a plain move.b with no alignment case, and the codebook is 8 KB not 32 KB.
# What actually bounds the high end:
# - Bus: unmeasured. ~300-500 KB/s SASI / ~1 MB/s SCSI, both FOLKLORE.
# This is the binding unknown and the reason the disk benchmark matters.
# - CPU: a FULL-frame blit is only 38% of the 12fps budget, and VQ decode is
# table copies (RAW, the mode that dominates at high rate, is the CHEAPEST
# to blit). So raising the bitrate is nearly free on CPU.
# - Entropy coding is NOT the way to buy headroom here: deflate decode is
# ~216% of the frame budget on a 68000 and even LZ4 is ~54%. See FINDINGS 17.
# The rates below are therefore RAW payload, no entropy coding.
#
# The two profiles are the SAME codec, decoder and bitstream -- only `lam` differs.
PROFILES = {
"sasi": dict(kbps=45, lam=300.0, k1=256, k4=256,
"sasi": dict(kbps=110, lam=60.0, k1=256, k4=256,
desc="stock 10MHz ACE/EXPERT, SASI",
quality="34.8 dB on 00020 / 28.3 dB on 00146"),
"scsi": dict(kbps=75, lam=100.0, k1=256, k4=256,
quality="36.9 dB on 00020 / 29.6 dB on 00146",
util="~105 KB/s = 35% of the pessimistic 300 KB/s SASI figure"),
"scsi": dict(kbps=280, lam=10.0, k1=256, k4=256,
desc="Super/XVI, or CZ-6BS1 board in a 10MHz machine",
quality="35.9 dB on 00020 / 29.0 dB on 00146"),
quality="39.4 dB on 00020 / 32.3 dB on 00146",
util="~275 KB/s = 28% of the 1 MB/s SCSI folklore figure"),
}
# Not a shipping profile, but the curve continues: lam=25 is ~185 KB/s at ~38.7 dB
# with 26% RAW blocks, and lam->0 is pixel-exact (0.00 dB loss). Entropy-coding
# the payload (NOT YET IMPLEMENTED) should shift the whole curve ~1.4x left.
# lam=0 is PIXEL-EXACT against the palettised frame (0.00 dB loss) at ~450 KB/s
# of raw payload, and costs only 38% of the CPU budget. If the blocked disk
# benchmark confirms SCSI sustains >=800 KB/s, the `scsi` profile should become
# lam=0 and the port ships transparent video. That decision is waiting on a
# measurement, not on a design choice.
AUDIO_KBPS = 7.8 # MSM6258 ADPCM 15.6kHz mono -- comes out of the same budget