Price cycles in the mode decision: 37 misses become 1, for 0.26 dB
The decoder has been CPU-bound since FINDINGS 28 while the mode decision
minimised D + lam*R -- distortion against BYTES. decide() now minimises
D + lam*bytes + mu*cycles, and ratectl bisects mu per frame against the
833,333-cycle budget with the lam bisection nested inside it. On the worst
sustained window:
sasi 27.22 -> 26.95 dB, 109.5 -> 109.4 KB/s, 37/120 misses -> 1
scsi 29.90 -> 29.27 dB, 280.0 -> 278.6 KB/s, 51/120 misses -> 1
Bitrate does not move: the byte controller still binds, and mu changes WHICH
modes are bought. V4 is what it stops buying -- 25.2 -> 20.3% of blocks at sasi
and 15.0 -> 5.3% at scsi, where RAW takes it. That is 28.8's inversion in
practice: RAW is dearer in bytes and cheaper in cycles, so only the byte-rich
profile can buy its way out of V4.
Three things worth knowing beyond the headline:
- The one frame that still misses, at both profiles, is FRAME 0 -- no previous
reconstruction, so 100% changed by definition, which is also what a scene
cut is. It comes out at the all-V1 floor of 110.6% and is emitted late on
purpose. Freezing a cut to make a deadline is the worse failure.
- 28.7's "11 frames are impossible" was too pessimistic. That floor held the
SKIP set fixed and asked how cheaply the drawn blocks could be drawn; the
real decision can also MOVE a block to SKIP, which above ~90% non-SKIP is
the only lever left.
- SKIP's price depends on its neighbours (13.25 cycles clustered, 45 mixed),
which a per-block lagrangian cannot see. The way out is that the two uses
need not share a cost function: a ranking constant inside decide(), the
exact clustered rule for the frame-level bisection. vq_hybrid.cycles() is
now the one definition of that rule and 11_cpu_budget.py imports it.
Gated: 09_ratectl_drift.py runs both controllers, both 0/120 drifting frames.
The cost-aware container decodes pixel-exact on the 68000 (120 frames). ON by
default in encode.py; --no-cpu-fit restores session 7. check.sh ALL GREEN.
Still a model, not a measurement, for THIS container: FINDINGS 31's cycle
figures come from vq_hybrid.cycles (within 1 point of the 68000 on four frames
of the session-7 container). Timing this one on the machine is step 1 of the
next session -- it was started and killed for time, and it is slow.
FINDINGS 31. tools/analysis/13_cpu_ratectl.py.
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -1678,3 +1678,108 @@ re-priced `sasi` stream buys 8773 spans across 120 frames, a mean of 73 a frame,
|
||||
and each one's three-instruction dispatch is inside the fitted 43.7 — but the
|
||||
mode-map walk that decides a span exists is not. `decode.s` does not implement
|
||||
spans yet.
|
||||
|
||||
## 31. The mode decision can see cycles now, and it costs 0.26 dB (session 8)
|
||||
|
||||
FINDINGS 28 left the decoder missing 31% of frames at `sasi` and 42% at `scsi`
|
||||
while the mode decision minimised `D + lam*R` — distortion against BYTES — on a
|
||||
machine whose binding budget is CYCLES. This is the second controller.
|
||||
|
||||
`vq_hybrid.decide(ctx, lam, mu)` now minimises `D + lam*bytes + mu*cycles`, and
|
||||
`ratectl.encode_rate_controlled(cycle_budget=...)` bisects `mu` per frame
|
||||
against 833,333 cycles with the `lam` bisection nested inside it.
|
||||
`tools/analysis/13_cpu_ratectl.py` measures what it costs.
|
||||
|
||||
### 31.1 The result
|
||||
Worst sustained window, 120 frames, same targets, same quality floors:
|
||||
|
||||
| | PSNR | KB/s | CPU median | CPU max | frames missing |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| `sasi` bytes only | 27.22 dB | 109.5 | 74.4% | 136.2% | **37/120** |
|
||||
| `sasi` + cycle ceiling | **26.95 dB** | 109.4 | 81.5% | 110.6% | **1/120** |
|
||||
| `scsi` bytes only | 29.90 dB | 280.0 | 94.9% | 146.6% | **51/120** |
|
||||
| `scsi` + cycle ceiling | **29.27 dB** | 278.6 | 99.6% | 110.6% | **1/120** |
|
||||
|
||||
**36 of 37 misses at `sasi` for 0.26 dB, 50 of 51 at `scsi` for 0.62 dB.** The
|
||||
bitrate does not move: the byte controller still binds, and mu changes *which*
|
||||
modes are bought rather than how many bytes.
|
||||
|
||||
`sasi` pays less quality than `scsi` because it had less to give up: it was
|
||||
already short of bytes, so the cycle-cheap directions it takes (V4 -> V1, and
|
||||
blocks it can afford to hold) were near where the byte lagrangian already sat.
|
||||
28.8 predicted the shape of this and got the sign right.
|
||||
|
||||
Mode mix, `sasi`, bytes-only -> with the ceiling: SKIP 46.4 -> 47.1%,
|
||||
V1 19.8 -> 23.0%, **V4 25.2 -> 20.3%**, RAW 8.5 -> 9.6%. At `scsi` the V4
|
||||
collapse is dramatic — **15.0 -> 5.3%**, with RAW taking it at 41.3 -> 43.2%,
|
||||
which is 28.8's inversion happening in practice: RAW is dearer in bytes and
|
||||
cheaper in cycles, so a byte-rich profile buys its way out of V4.
|
||||
|
||||
Only **46 of 120 frames need any mu at all** at `sasi`; the median frame is
|
||||
decided at mu=0 and is unchanged from session 6.
|
||||
|
||||
### 31.2 The one frame that cannot fit is the intra frame, not a hard case
|
||||
Both profiles miss exactly one frame, both at 110.6% — the all-V1 floor of
|
||||
FINDINGS 28.5 — and in both it is **frame 0**. It has no previous
|
||||
reconstruction, so every block must be coded, which is the definition of a
|
||||
100%-changed frame. A scene cut mid-stream is the same thing.
|
||||
|
||||
That is the correct behaviour rather than a failure, and it is worth being
|
||||
explicit about why: at `MU_CLIFF` a block only becomes SKIP if holding the
|
||||
previous reconstruction costs less than ~28,665 units of distortion. A frame
|
||||
with nothing on screen worth holding stays fully coded and is emitted **late on
|
||||
purpose**, exactly as a frame that will not fit at `LAM_CLIFF` is emitted over
|
||||
budget. Freezing a cut to make a deadline is the worse failure.
|
||||
|
||||
### 31.3 28.7 was too pessimistic, and the reason is instructive
|
||||
28.7 estimated that only ~three quarters of the misses were the encoder's to
|
||||
fix — 26 of 37 at `sasi` — because re-coding every non-SKIP block as V1 still
|
||||
missed 11 frames. Measured, the controller fixes **36 of 37**.
|
||||
|
||||
The gap is that 28.7's floor held the SKIP set fixed and asked "how cheap can
|
||||
the blocks we already decided to draw be?". The real decision can also **move a
|
||||
block to SKIP**, paying distortion for it, and above ~90% non-SKIP that is the
|
||||
only lever left. So 28.7's floor was a floor for a fixed SKIP set, not for the
|
||||
mode decision. Two conclusions of 28.7 stand: the profiles are an I/O axis and
|
||||
both must fit the same 10 MHz budget.
|
||||
|
||||
### 31.4 SKIP is not a constant, and the way out is two cost functions
|
||||
A SKIP block costs 13.25 cycles when all four blocks sharing its header byte are
|
||||
SKIP (one `tst.b` clears the group) and ~45 in a mixed byte, so its price
|
||||
depends on its neighbours — which a per-block lagrangian cannot see. Picking one
|
||||
number is a real trade: 45 overcharges clustered SKIPs and pushes the encoder
|
||||
away from the mode that saves the most cycles, 13.25 undercharges isolated ones
|
||||
and lets frames overrun.
|
||||
|
||||
The resolution is that **the budget check does not have to use the same cost
|
||||
function as the mode decision**. `decide()` uses 13.25 purely to *rank* modes
|
||||
within a block, where the choice only scales the incentive (the V1-SKIP gap
|
||||
moves 12% between the two candidates). The controller scores whole frames with
|
||||
`vq_hybrid.cycles()`, the exact clustered rule, validated to 1 point against the
|
||||
68000 — so the bisection converges on what the machine will really do, whatever
|
||||
the ranking constant was. That function is now defined once and imported by
|
||||
`11_cpu_budget.py`, rather than living in two places that can drift apart.
|
||||
|
||||
### 31.5 Both controllers are gated against decoder drift
|
||||
The mu controller varies the mode map frame to frame exactly as the lam
|
||||
controller does, so it is exposed to the FINDINGS 26.1 failure — an encoder
|
||||
reporting a reconstruction the decoder will never produce. `09_ratectl_drift.py`
|
||||
now runs **both** configurations and both report 0/120 drifting frames, 0.00 dB
|
||||
overstatement. The CPU ceiling is on by default in `encode.py`
|
||||
(`--no-cpu-fit` restores session 7 behaviour).
|
||||
|
||||
### 31.6 With spans on top, the window fits completely
|
||||
Re-running the span pricing of FINDINGS 30 against a cost-aware container —
|
||||
lever B first, then lever A on what it leaves:
|
||||
|
||||
| `sasi` | bytes only | + cycle ceiling | + ceiling + spans |
|
||||
|---|---:|---:|---:|
|
||||
| median frame | 74.4% | 81.5% | **56.8%** |
|
||||
| worst frame | 136.2% | 110.6% | **91.5%** |
|
||||
| frames missing | 37/120 | 1/120 | **0/120** |
|
||||
| bitrate | 101.7 KB/s | 101.6 | 449.3 KB/s |
|
||||
|
||||
The intra frame lands at 91.5% — spans are what make a full redraw fit, which is
|
||||
30.6's arithmetic arriving in a real container. That row is still a **model** of
|
||||
a bitstream nothing implements; the two levers have never run on the 68000
|
||||
together, and the ring-buffer question of 30.7 gets sharper at 449 KB/s.
|
||||
|
||||
+100
-94
@@ -1,115 +1,116 @@
|
||||
# Status & next-session handoff — session 8 (2026-08-23)
|
||||
# Status & next-session handoff — end of session 8 (2026-08-23)
|
||||
|
||||
## Where this stands
|
||||
|
||||
The decoder exists, it is pixel-exact, and **it does not fit**: mean 81.7% of a
|
||||
12fps frame on the worst sustained window at `sasi`, 31% of frames over budget
|
||||
(`scsi`: 94.9% median, 42% miss). FINDINGS 28. CPU is the binding constraint.
|
||||
Session 7 left the decoder pixel-exact and **31% of frames over the CPU budget**
|
||||
at `sasi`, 42% at `scsi` (FINDINGS 28), with two levers proposed and neither
|
||||
measured. Session 8 did both.
|
||||
|
||||
Session 7 proposed two levers and session 8 measured the cheap one first.
|
||||
**Lever A, spans: measured.** A row-linear span of word-expanded literals costs
|
||||
**43.7 cycles per span + 9.152 per pixel** — but only in an encoder-assisted
|
||||
format, `{u32 absolute GVRAM address, u16 jump displacement}` into an unrolled
|
||||
copy chain. The obvious decoder, handed `(x, npix)`, is 97.9 + 10.46.
|
||||
FINDINGS 30, `tools/bench/span.sh` (~25 s).
|
||||
|
||||
**Lever A — spend bandwidth to buy cycles — is real, and it is an encoder
|
||||
format.** A row-linear span of word-expanded literals measures **43.7 cycles per
|
||||
span + 9.152 per pixel** (FINDINGS 30, `tools/bench/span.sh`), which is what
|
||||
FINDINGS 29 assumed — but only when the *encoder* hands the decoder an absolute
|
||||
GVRAM address and a jump displacement into an unrolled copy chain. The obvious
|
||||
decoder, handed `(x, npix)` and left to work the copy out, is 97.9 + 10.46 and
|
||||
2.2x dearer on a short span. Re-priced against the unchanged mode maps:
|
||||
**Lever B, the cost-aware mode decision: implemented, measured, and ON by
|
||||
default.** `decide()` minimises `D + lam*bytes + mu*cycles`; `mu` is bisected
|
||||
per frame against 833,333 cycles with the `lam` bisection nested inside it.
|
||||
FINDINGS 31, `tools/analysis/13_cpu_ratectl.py`.
|
||||
|
||||
| | today | 29 (derived) | **30 (measured)** |
|
||||
|---|---:|---:|---:|
|
||||
| `sasi` median frame | 74.4% | 43.0% | **52.0%** |
|
||||
| `sasi` worst frame | 136.2% | 106.2% | **108.7%** |
|
||||
| `sasi` frames missing | 37/120 | 8/120 | **10/120** |
|
||||
| bitrate | 101.7 KB/s | 453.2 | **448.0 KB/s** (bus 488) |
|
||||
| `sasi`, worst sustained window | PSNR | KB/s | CPU median | CPU max | missing |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| bytes only (session 7) | 27.22 dB | 109.5 | 74.4% | 136.2% | **37/120** |
|
||||
| + cycle ceiling (now the default) | 26.95 dB | 109.4 | 81.5% | 110.6% | **1/120** |
|
||||
| + ceiling + spans (MODEL, nothing implements it) | — | 449.3 | 56.8% | 91.5% | **0/120** |
|
||||
|
||||
Break-even moved with it: a run beats all-V1 **from 4 blocks up**, not 2. And
|
||||
29.4 survives — a scene cut needs `x >= 0.196` of the frame as spans and the bus
|
||||
allows `x <= 0.373`, so it fits at 12fps.
|
||||
`scsi`: 51/120 -> 1/120 for 0.62 dB. The one remaining miss at either profile is
|
||||
**frame 0**, which has no previous reconstruction and so is 100% changed by
|
||||
definition — the same case as a scene cut. It is emitted late on purpose.
|
||||
|
||||
**Lever B — stop buying modes the CPU cannot afford — is untouched.**
|
||||
`vq_hybrid.decide()` still minimises `D + lam*R`, distortion against BYTES, on a
|
||||
machine whose binding budget is CYCLES:
|
||||
The cost-aware container is verified pixel-exact on the 68000 (120 frames,
|
||||
`tools/bench/verify_decode.py`).
|
||||
|
||||
| mode | payload bytes | cycles | cycles per byte |
|
||||
|---|---:|---:|---:|
|
||||
| SKIP | 0 | 13 (clustered) | — |
|
||||
| V1 | 1 | 300 | 300 |
|
||||
| V4 | 4 | 448 | 112 |
|
||||
| RAW | 16 | 400 | 25 |
|
||||
| **span, per 4x4 block in a run of L** | **32** | **1053/L, floor 154** | **~5** |
|
||||
## NEXT SESSION, in order
|
||||
|
||||
V4 is 25% of blocks and 50% of the cycles. The lagrangian charges it 4x a V1
|
||||
block; the CPU charges it 1.49x.
|
||||
0. **Green light first.** `./tools/bench/check.sh` (~3 min, Blu-ray mounted).
|
||||
The drift stage now runs BOTH controllers; both must report 0/120.
|
||||
|
||||
## The work, in order
|
||||
1. **Time the cost-aware container on the 68000.** Everything in FINDINGS 31 is
|
||||
the validated cost MODEL (`vq_hybrid.cycles`, within 1 point of the machine
|
||||
on four frames of the session-7 container), not a measurement of this one.
|
||||
The full timing pass was started and killed for time:
|
||||
```
|
||||
python3 tools/bench/prep_dlx.py tmp/rc_fr_singe_sasi_cpufit.dlx
|
||||
tools/vasm/vasmm68k_mot -Fbin -o tmp/decode.bin src/player/decode.s
|
||||
( cd tmp && SDL_VIDEODRIVER=dummy mame x68000 -bios ipl10 -ramsize 2M \
|
||||
-video soft -window -sound none -nothrottle -plugins \
|
||||
-autoboot_script ../tools/bench/decode.lua -snapshot_directory ./snap_decode \
|
||||
-snapview native -seconds_to_run 300 > decode_cpufit.log 2>&1 )
|
||||
```
|
||||
**Budget real time for it: the run was still going at 12 minutes of CPU.**
|
||||
MAME's stdout is block-buffered to a file, so there is no progress to watch
|
||||
— wait on the PID, never on a `pgrep -f` match (see the shell traps below).
|
||||
Confirm the four anchors against `11_cpu_budget.py` on the same container,
|
||||
and update FINDINGS 31 with measured-vs-model errors.
|
||||
|
||||
1. **Add a cycle term to the mode decision.** `decide()` already builds a cost
|
||||
matrix of `error + lam * bytes` per mode per block; add `+ mu * cycles`,
|
||||
with the per-mode cycles measured in FINDINGS 28.2.
|
||||
**SKIP is not a constant and this is the one trap here.** A SKIP block costs
|
||||
13.25 cycles when all four blocks in its header byte are SKIP (one `tst.b`
|
||||
clears the group) and ~45 when it sits in a mixed byte — so SKIP's price
|
||||
depends on its *neighbours*, which a per-block lagrangian cannot see. Do not
|
||||
pick one number and move on: 45 overcharges clustered SKIPs and pushes the
|
||||
encoder away from the mode that saves cycles, 13.25 undercharges isolated
|
||||
ones and lets frames overrun. The way out is that the **budget check does not
|
||||
have to use the same cost function as the mode decision** — score frames with
|
||||
the exact clustered cost (`cycles()` in `tools/analysis/11_cpu_budget.py`,
|
||||
validated to 1 point against the 68000) and let the bisection converge on
|
||||
that, while the per-block term uses a constant purely to *rank* modes.
|
||||
2. **Then bisect `mu` per frame against the 833,333-cycle budget**, exactly as
|
||||
session 6 bisects `lam` against the byte budget. The machinery is already
|
||||
there and already gated: `ratectl.encode_rate_controlled` is frame-driven and
|
||||
feeds back the frame it emitted. **But cycles have NO bucket.** Bytes can be
|
||||
banked in the ring buffer; a frame that misses its decode deadline is just
|
||||
late, because there is no double buffer to decode ahead into. So this is a
|
||||
hard per-frame ceiling, not a leaky bucket — simpler than rate control, and
|
||||
the two controllers have to run together (raising `mu` moves blocks to SKIP
|
||||
and V1, which also *lowers* the bitrate, so the byte controller must see it).
|
||||
3. **Measure the quality cost.** Everything session 6 did for bytes: what does
|
||||
fitting 100% of frames in the CPU budget cost in dB, and does any frame hit a
|
||||
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
|
||||
`tools/bench/decode.lua`.
|
||||
2. **Put spans in the bitstream.** This is the big one and it is now fully
|
||||
specified by measurement: format in FINDINGS 30.2, costs in 30.5, and the
|
||||
scene-cut arithmetic in 30.6. It touches `encode.py` (a fifth mode and a
|
||||
run-aware decision), `dlx.py` (the reference decoder), and `decode.s`. The
|
||||
24-pixel quantisation and the free row overrun are part of the format, not
|
||||
optimisations to add later. Order it AFTER item 1 so the model that prices it
|
||||
has been checked against the machine once more.
|
||||
|
||||
3b. **Know which misses are yours to fix before starting.** Re-coding every
|
||||
non-SKIP block as V1 is the floor any mode assignment *of the current mode
|
||||
set* 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 need item 4.
|
||||
FINDINGS 28.7.
|
||||
3. **The three open items of FINDINGS 29.5/30.7**, now load-bearing because a
|
||||
span design runs at ~449 KB/s of a 488 KB/s pipe: re-run the ring-buffer
|
||||
simulation at that rate (FINDINGS 21 was established at 110 and 280), confirm
|
||||
the provenance of the user's 4 Mbps figure, and **confirm DMA rather than
|
||||
PIO** — a PIO fallback puts a 449 KB/s transfer back on the CPU the whole
|
||||
lever exists to relieve. The DMA check is the cheapest of the three and the
|
||||
most consequential.
|
||||
|
||||
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. **Encoder gap, still open from session 7:** `encode.py` should pad frame
|
||||
records to 4 bytes. Frame boundaries land on odd addresses and a 68000 takes
|
||||
an address error, not a slow read (FINDINGS 28.3). `prep_dlx.py` pads at load
|
||||
time, which is why the decoder works; the container itself does not.
|
||||
Measured cost of fixing it: 1.5 B/frame = 18 B/s.
|
||||
|
||||
4. **Put spans in the bitstream** — the mode is measured and nothing implements
|
||||
it. This is a container change (`encode.py`, `dlx.py`, `decode.s`), a mode
|
||||
decision that can see runs rather than blocks, and the 24-pixel quantisation
|
||||
and row-overrun rules of FINDINGS 30.2. It subsumes item 4 of session 7's
|
||||
plan: with spans, a scene cut fits.
|
||||
|
||||
5. **The three things 30.7 leaves open, now more load-bearing than before**,
|
||||
because the span design runs at 448 KB/s of a 488 KB/s pipe: re-run the
|
||||
ring-buffer simulation at that rate (FINDINGS 21 was established at 110 and
|
||||
280), confirm the provenance of the 4 Mbps figure, and **confirm DMA rather
|
||||
than PIO** — a PIO fallback puts a 448 KB/s transfer back on the CPU this
|
||||
whole lever exists to relieve. The DMA check is the cheapest of the three.
|
||||
5. **A quality-vs-framerate question that is the user's, not the encoder's.**
|
||||
Every miss is now one frame per cut. The options remain: one late frame at
|
||||
each cut (the outgoing content is unrelated, so it may be invisible), a cut
|
||||
spread over two frame times, or 10fps. Spans (item 2) make the question go
|
||||
away if they land as modelled.
|
||||
|
||||
**Do not start by hand-optimising `decode.s`.** The hand-derived timings agree
|
||||
with the measurements to 0.5% on V1 and 1% on RAW (FINDINGS 28.4), so the
|
||||
inner loop is close to what the instruction set allows; the plausible wins are
|
||||
single-digit percentages against a 36-point gap. The V4 write pattern is the one
|
||||
place worth a look afterwards — pairing sub-block rows into `movem.l d0/d2,(a4)`
|
||||
saves ~16 of 448 cycles.
|
||||
with the measurements to 0.5% on V1 and 1% on RAW (FINDINGS 28.4), so the inner
|
||||
loop is close to what the instruction set allows.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## What session 8 settled
|
||||
|
||||
0. **The mode decision can see cycles, it is on by default, and it costs
|
||||
0.26 dB.** `decide(ctx, lam, mu)` minimises `D + lam*bytes + mu*cycles`;
|
||||
`ratectl` bisects mu per frame against a HARD 833,333-cycle ceiling (bytes
|
||||
bank in the ring buffer, cycles cannot — there is no double buffer to decode
|
||||
ahead into). `sasi` 37/120 misses -> 1, `scsi` 51 -> 1. Bitrate does not
|
||||
move: mu changes which modes are bought, not how many bytes. FINDINGS 31,
|
||||
`tools/analysis/13_cpu_ratectl.py`.
|
||||
0b. **28.7's "11 frames are impossible" was too pessimistic — it is 1.** That
|
||||
floor held the SKIP set fixed; the real decision can also move a block to
|
||||
SKIP, which above ~90% non-SKIP is the only lever left. FINDINGS 31.3.
|
||||
0c. **V4 collapses when cycles are priced**, as 28.8 predicted: 25.2 -> 20.3%
|
||||
of blocks at `sasi` and **15.0 -> 5.3%** at `scsi`, where RAW takes it. RAW
|
||||
is dearer in bytes and cheaper in cycles, so the byte lagrangian's preference
|
||||
inverts and only the byte-rich profile can take the escape.
|
||||
0d. **SKIP's price depends on its neighbours, and the way out is two cost
|
||||
functions**: a ranking constant inside the per-block lagrangian, the exact
|
||||
clustered rule (`vq_hybrid.cycles`, validated to 1 point against the 68000)
|
||||
for the frame-level bisection. That function is now defined once and imported
|
||||
by `11_cpu_budget.py`. FINDINGS 31.4.
|
||||
0e. **Both controllers are gated against decoder drift.**
|
||||
`09_ratectl_drift.py` runs bytes-only AND bytes+cycles; both 0/120.
|
||||
1. **The span is measured: 43.7 cycles/span + 9.152/pixel, fitted to 0.3% over
|
||||
eleven span lengths.** `tools/bench/blit.s` v5/v6, `prep_spans.py`,
|
||||
`span.lua`, driven by `tools/bench/span.sh` (~25 s, not in `check.sh`
|
||||
@@ -217,10 +218,15 @@ saves ~16 of 448 cycles.
|
||||
```
|
||||
./tools/bench/check.sh
|
||||
```
|
||||
~3 min, needs the Blu-ray mounted. From source media it re-runs both display
|
||||
regression tests, the rate-control drift test (session 6), the display-path
|
||||
coherency counterexample and a **120-frame 68000 decode** (session 7), then
|
||||
prints `ALL GREEN`. Verified green at end of session 7.
|
||||
~4 min, needs the Blu-ray mounted. From source media it re-runs both display
|
||||
regression tests, the rate-control drift test (session 6, now covering BOTH
|
||||
controllers -- bytes, and bytes+cycles), the display-path coherency
|
||||
counterexample and a **120-frame 68000 decode** (session 7), then prints
|
||||
`ALL GREEN`. Verified green at end of session 8.
|
||||
|
||||
Do not run two of these at once, and do not run one alongside a MAME timing
|
||||
job: they share `tmp/` snapshot directories and log files, and the second run
|
||||
silently truncates the first one's output.
|
||||
If it fails, fix that before doing anything else — everything downstream assumes
|
||||
the display path is pixel-exact.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user