Separate the two axes: profiles are I/O, the CPU ceiling is one target for both

The profiles were chosen against disk bandwidth and say nothing about CPU. The
locked CPU target is a stock 10MHz 68000 for both of them, so both must fit
833,333 cycles -- picking sasi does not rescue it, it still misses 31% of
frames against scsi's 42%.

Splits the miss into what the encoder can fix and what it cannot: re-coding
every non-SKIP block as V1 is the floor, and it still misses 11 frames at sasi
and 12 at scsi, all of them above ~90% non-SKIP. So a cost-aware mode decision
can reach about three quarters of the misses; the rest need a structural
answer, not a better encoder.

Also: V4 is 448 cycles against RAW's 400, and RAW is pixel-exact. On the CPU
axis V4 is strictly dominated and the byte lagrangian's mode preference
inverts. Only the byte-rich profile can take that escape, so the cycle ceiling
should cost sasi MORE quality than scsi despite costing it fewer cycles.
FINDINGS 28.7/28.8.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-23 15:14:12 -07:00
parent ed353d24a9
commit 31c4c1aba1
2 changed files with 65 additions and 0 deletions
+52
View File
@@ -1371,3 +1371,55 @@ One 10 s window of one stream at one profile, and MAME still models no GVRAM
wait states. The `scsi` profile will be worse: FINDINGS 25.5 has it collapsing wait states. The `scsi` profile will be worse: FINDINGS 25.5 has it collapsing
to RAW under stress, and RAW is 1.93x the old model's block. Nothing here has to RAW under stress, and RAW is 1.93x the old model's block. Nothing here has
been run on `00020` or on quiet content, where the median frame is far cheaper. been run on `00020` or on quiet content, where the median frame is far cheaper.
### 28.7 The profiles are an I/O axis; the CPU limit is the clock
`sasi` and `scsi` are two points on one rate-distortion curve, chosen against
**disk bandwidth**. They say nothing about CPU, and the locked target CPU is a
stock 10MHz 68000 for both. So both have to fit the same 833,333 cycles:
| | sasi | scsi |
|---|---:|---:|
| stock / Super, 10 MHz | median 74.4%, **31% of frames miss** | median 94.9%, **42% miss** |
| XVI, 16.67 MHz | median 44.6%, 0% miss | median 56.9%, 0% miss |
Clocks confirmed from MAME 0.277 `x68k.cpp:1133/1194/1200`: `x68000` and
`x68ksupr` are **both** `40_MHz_XTAL/4` = 10 MHz, and only `x68kxvi` is faster
at `33.33_MHz_XTAL/2`. The Super has SCSI at 10 MHz, so a faster bus does not
imply a faster CPU — the XVI column above is headroom, not a target.
`sasi` is the cheaper profile, but choosing it is not a fix: it still misses
31% of frames. The cycle ceiling has to be enforced in the encoder either way.
**How much of the miss is the encoder's to fix.** Re-coding every non-SKIP block
as V1 — the cheapest mode, quality ignored — is the floor any mode assignment
can reach:
| | frames that miss | recoverable by re-coding | impossible at 12fps |
|---|---:|---:|---:|
| `sasi` | 37/120 | 26 | **11** (from 89.8% non-SKIP up) |
| `scsi` | 51/120 | 39 | **12** (from 91.9% non-SKIP up) |
So a cost-aware mode decision can reach about **three quarters** of the misses.
The remaining ~10% of frames are 28.5's ceiling in practice: past ~90% non-SKIP
no mode assignment fits, because the blocks have to be drawn at all. Those
frames need a structural answer — a late frame at a cut, a cut spread over two
frame times, or a lower framerate — not a better encoder.
### 28.8 V4 costs more cycles than RAW, so it is CPU-dominated by it
448.2 against 400.4 cycles. RAW is also pixel-exact where V4 is lossy, so V4's
only advantage is that it costs 4 payload bytes instead of 16. **On the CPU
axis V4 is strictly dominated**, which inverts the mode preference the byte
lagrangian has: an encoder short of cycles but not of bytes should buy RAW
wherever it would have bought V4, and gain quality doing it.
That escape is only open to the byte-rich profile. `scsi` already spends 41.3%
of its blocks on RAW (FINDINGS 25.5 saw it "collapse to RAW under stress" and
read that as a failure; on the CPU axis it is the cheap direction). `sasi` at
110 KB/s cannot afford it, so its only lever is V4 -> V1 -> SKIP, every step of
which costs quality. **The CPU constraint therefore bites harder on `sasi` in
quality terms even though it bites less in cycles.**
Caveat: this ordering is a property of *this* decoder, not of the codec. V4's
cost is four indexed `movem.l` lookups; pairing sub-block rows into
`movem.l d0/d2,(a4)` would save ~16 of 448 cycles, which narrows the gap to RAW
without closing it.
+13
View File
@@ -43,6 +43,18 @@ worth 4 bytes, with no idea what it costs to draw.
cliff? `tools/analysis/11_cpu_budget.py` scores a container without needing cliff? `tools/analysis/11_cpu_budget.py` scores a container without needing
MAME, so the search loop is cheap; confirm the winner on the 68000 with MAME, so the search loop is cheap; confirm the winner on the 68000 with
`tools/bench/decode.lua`. `tools/bench/decode.lua`.
3b. **Know which misses are yours to fix before starting.** Re-coding every
non-SKIP block as V1 is the floor any mode assignment can reach, and it
still misses 11 frames at `sasi` and 12 at `scsi` — every frame above ~90%
non-SKIP. So the cost-aware decision can reach about three quarters of the
misses (26 of 37 at `sasi`) and the rest are item 4. FINDINGS 28.7.
3c. **Buy RAW, not V4, wherever the bytes allow.** RAW is 400 cycles against
V4's 448 *and* is pixel-exact, so on the CPU axis V4 is strictly dominated —
the byte lagrangian's preference inverts. `scsi` can take that escape and
`sasi` cannot afford it, so expect the cycle ceiling to cost `sasi` more
quality even though it costs `sasi` fewer cycles. FINDINGS 28.8.
4. **28.5 may not be solvable by the encoder at all.** An all-V1 frame — the 4. **28.5 may not be solvable by the encoder at all.** An all-V1 frame — the
cheapest possible full redraw — is **110.5%** of the budget. A scene cut cheapest possible full redraw — is **110.5%** of the budget. A scene cut
changes 100% of the screen, so *no* mode assignment fits one at 12fps. Decide changes 100% of the screen, so *no* mode assignment fits one at 12fps. Decide
@@ -157,6 +169,7 @@ the display path is pixel-exact.
| Double buffer | **none** — page 1 sacrificed | enables `movem.l` 24px bursts; delta coding needs a RAM reference frame anyway | | 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 | | **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 | | **Quality modes** | **two: `sasi` and `scsi`** (USER DECISION, session 2) | one codec, one decoder, one bitstream; only `lam` differs |
| Profile axis | **I/O bandwidth only** | the profiles say nothing about CPU; **both target the same stock 10MHz 68000**, and the Super has SCSI at 10MHz. FINDINGS 28.7 |
| Framerate | 12 fps, **explicit decimation** | source has zero duplicate frames; no free "twos" win | | 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 | | 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 | | SNES project reuse | **MIT — cleared** | `data/events/` scene graph is reusable with attribution |