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 Having **two independent permissively-licensed transcriptions** of the arcade
scene graph is better than one: they can be diffed against each other to catch 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. 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 as two quality profiles. This is now implemented rather than hypothetical — the
bitrate ceiling is a build parameter in `tools/encoder/ratectl.py`: bitrate ceiling is a build parameter in `tools/encoder/ratectl.py`:
| profile | target | lam | quality (00020 / 00146) | machine | | profile | target | lam | quality (00020 / 00146) | bus utilisation | machine |
|---|---|---|---|---| |---|---|---|---|---|---|
| `sasi` | 45 KB/s | 300 | 34.8 / 28.3 dB | stock 10MHz ACE/EXPERT | | `sasi` | 110 KB/s | 60 | 36.9 / 29.6 dB | 35% of 300 KB/s | stock 10MHz ACE/EXPERT |
| `scsi` | 75 KB/s | 100 | 35.9 / 29.0 dB | Super/XVI, or CZ-6BS1 board | | `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 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 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 - **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 fixed `lam` from the profile. `ratectl.encode_rate_controlled()` exists and
builds a lam-ladder per frame; it needs hooking up and validating. 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 - **Payload is deliberately NOT entropy-coded** — deflate decode does not fit in
(measured on the lossless path, FINDINGS 8). LZ decode is cheap on a 68000. 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. - 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 - `_paint` is a Python per-block loop — fine for prototyping, slow for a full
disc encode. Vectorise before the 224-stream run. 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 and the four untested hypotheses are in session 1's notes (git history of this
file, commit 65112b9). file, commit 65112b9).
**This now matters more than session 1 thought.** Session 1 dismissed it because **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". But we now ship "VQ at 30 KB/s is correct whether SASI does 300 or 600 KB/s". That reasoning is
*two profiles*, and the profile bitrates (45 / 120 KB/s) are set against dead: the profiles now sit at 110 and 280 KB/s, close enough to the folklore
**folklore** bandwidth figures. A real measurement would let us set them ceilings that the error bars change the product. Specifically —
honestly instead of conservatively. Next move is the untried SCSI path:
`-exp1 cz6bs1 -hard disk.chd`. **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 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). 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, 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 — 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. 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 # see docs/FINDINGS.md 5 -- these are FOLKLORE-grade until the disk benchmark
# is unblocked, so they are deliberately conservative fractions of the quoted # is unblocked, so they are deliberately conservative fractions of the quoted
# ceiling (audio, seeks and container overhead come out of the same pipe). # 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 # What actually bounds the high end:
# bitrate. The earlier "+2.4 dB for k=1024" was an artifact of a rate model that # - Bus: unmeasured. ~300-500 KB/s SASI / ~1 MB/s SCSI, both FOLKLORE.
# charged 1 byte for a 10-bit index. 1-byte indices also mean the 68000 decoder # This is the binding unknown and the reason the disk benchmark matters.
# reads a plain move.b with no alignment case, and the codebook is 8 KB not 32 KB. # - 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. # The two profiles are the SAME codec, decoder and bitstream -- only `lam` differs.
PROFILES = { 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", desc="stock 10MHz ACE/EXPERT, SASI",
quality="34.8 dB on 00020 / 28.3 dB on 00146"), quality="36.9 dB on 00020 / 29.6 dB on 00146",
"scsi": dict(kbps=75, lam=100.0, k1=256, k4=256, 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", 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 # lam=0 is PIXEL-EXACT against the palettised frame (0.00 dB loss) at ~450 KB/s
# with 26% RAW blocks, and lam->0 is pixel-exact (0.00 dB loss). Entropy-coding # of raw payload, and costs only 38% of the CPU budget. If the blocked disk
# the payload (NOT YET IMPLEMENTED) should shift the whole curve ~1.4x left. # 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 AUDIO_KBPS = 7.8 # MSM6258 ADPCM 15.6kHz mono -- comes out of the same budget