ROADMAP K1, the packed player's one open structural item. A frame is a picture
AND a palette, and no run in this tree had pointed a DMA channel at the palette
registers. dmagate.s runs 7-9, gated by dma_run.sh and check.sh:
7. 512 B off the disc into $E82000, bus held -- byte-exact in 256 register
words, read back OUT OF the registers by the 68000;
8. the SAME transfer aimed at RAM -- byte-exact at $2C000, and 256 of 256
palette words still read the poison the CPU wrote, which is what attributes
run 7 to the channel's MAR rather than to the readback path;
9. ONE array-chained start across two kinds of destination -- the palette and
six picture rows at the 1,024 B line stride, 2,048 B byte-exact.
So a packed frame is one channel start: a 193-entry array, palette first, CPU
halted from the first byte to the last. The array is scene-constant, because
the packed layout spends both 256-colour pages and there is no page to flip.
What is left on the CPU per frame in the video path is the channel start and the
READ(10) -- no per-frame PAINT, which is not the same claim as no per-frame CPU.
The destination is POISONED first (62.1). Runs 4-6 wrote into RAM that was zero
and GVRAM that was stale against a record that is mostly pad; "it matches the
disc" was weaker than it read as. The host counts whether the poison actually
discriminates instead of assuming it: 511 of 512, and the gate refuses under 500.
And it opened a hardware item (62.4, ROADMAP B4). MAME maps the palette to
palette_device over memory_array, whose write16 is a plain COMBINE_DATA -- RAM
that honours mem_mask, with no handler that could refuse a byte write. Unlike
GVRAM's 256-colour arm there is nothing here to be wrong about, so the run
bounds the model and not the board. What a real palette register does with a
byte write is unmeasured. A negative costs 0.28% of a frame and nothing else.
29_packed_player.py now also prints the two rows with the per-frame palette
charged -- 55.7% of a frame on the chain, 582 KB/s -- alongside the picture-only
figures the codec comparison is quoted against.
check.sh ALL GREEN before (tmp/check_s30_start.log) and after
(tmp/check_s30_end.log).
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
713 lines
44 KiB
Markdown
713 lines
44 KiB
Markdown
# Roadmap — remaining work to a completion target
|
||
|
||
Written end of session 19 (2026-08-24), against a tree that is ALL GREEN.
|
||
Amended end of session 21: P1 done, P2 half done (FINDINGS 53).
|
||
Amended end of session 22: P3 done (FINDINGS 54).
|
||
Amended end of session 23: P5 done (FINDINGS 55).
|
||
Amended end of session 24: G1 done (FINDINGS 56).
|
||
Amended end of session 25: P4 HALF done (FINDINGS 57).
|
||
Amended end of session 26: P4b done, P4a is the last open item before M2
|
||
(FINDINGS 58).
|
||
Amended end of session 27: P4a done at the transport level; THE RE-ENCODE
|
||
BUNDLE under P2 is now the only thing between this tree and M2, because 59.4
|
||
made sector-aligned records a precondition the transport enforces rather than a
|
||
preference (FINDINGS 59). **And 59.7 re-ranks what is left: the frame affords
|
||
6.74 clocks a byte, a dual-address byte costs 9, so B3 stopped being a constant
|
||
to look up and became the question of whether the design fits at all.**
|
||
Amended end of session 28: **THE RE-ENCODE BUNDLE IS DONE — all four items, one
|
||
re-measurement (FINDINGS 60).** The container is DLX5, sector-aligned, and the
|
||
disc now moves exactly the records. Two of the four closed as NEGATIVES: E2's
|
||
`--spans all` default is refused on measurement, and E3's joint span/lam
|
||
selection emits byte-identical containers because `lam` never leaves its floor.
|
||
**What is left of M2 is P4a's wiring** — the DMA channel behind `ring.i`'s
|
||
mailbox — and the budget did not move: headroom 6.74 -> 6.69 clk/B, so every
|
||
conclusion in 59.7 stands.
|
||
Amended end of session 29: **THE DECODER-FREE PACKED PLAYER IS BACK, MEASURED,
|
||
AND IT FITS THE CLOCK BUDGET THE CODEC MISSES (FINDINGS 61).** The packed
|
||
full-frame blit is **27.3%** of a 12 fps frame — measured, not assumed — a
|
||
channel fills GVRAM in buffer mode off the disc with the CPU halted, and it
|
||
walks the 1,024 B line stride itself through array chaining. At the 9 clk/B
|
||
floor the codec is 110.4% and a decoder-free packed player is **55.2%**. It asks
|
||
**576 KB/s, fixed**, against E7's 327 KB/s target. **So B1 stopped setting how
|
||
much headroom the player has and started deciding WHICH PLAYER EXISTS**, and B2
|
||
stopped being a nice-to-have. The codec cannot be packed: 47.6.4 is closed and
|
||
the answer is no, both ways (61.3).
|
||
Amended end of session 30: **K1 IS DONE AND THE ANSWER IS THE GOOD ONE
|
||
(FINDINGS 62).** A channel writes the palette registers at `$E82000` byte-exact,
|
||
and ONE array-chained start crosses from device registers into GVRAM — so a
|
||
frame is a palette entry and 192 row entries, started once, with the CPU halted
|
||
throughout, and the array is scene-constant. **It opened B4**: MAME models the
|
||
palette as plain `COMBINE_DATA` storage with no handler to be wrong about, so
|
||
the run bounds the model and not the board, and what a real palette register
|
||
does with a byte write is UNMEASURED. B4 is the cheapest hardware item in the
|
||
project and a negative costs 0.28% of a frame. **K2, the packed container, is
|
||
next.**
|
||
|
||
**THE COMPLETION TARGET IS M3, THE VERTICAL SLICE** (USER DECISION): one scene
|
||
tree — a decision point, two outcomes, a death clip — with audio, streaming from
|
||
a real SCSI volume on a stock 2 MB machine, playable. That is the point at which
|
||
every layer of this design has been shown to work at once. M4 is listed because
|
||
it is real work, but past M3 it is content grinding rather than open questions.
|
||
|
||
`docs/STATUS.md` remains the session-by-session record and the handoff. This file
|
||
is the shape of what is left; where the two disagree about what is done, STATUS
|
||
is the one with the measurements and this one is the one that goes stale. Both
|
||
were wrong about two encoder gaps until this file was written — see "What was
|
||
already done" below.
|
||
|
||
---
|
||
|
||
## Status of the four resources
|
||
|
||
The project's own framing, restated because every item below is priced in one of
|
||
these units:
|
||
|
||
| resource | state |
|
||
|---|---|
|
||
| **68000 local bus** | the binding one. Decoder occupies 86.7%; 52 of 53 missed frames miss on the bus, not the clock (FINDINGS 38). |
|
||
| **68000 clocks** | measured, and the rate controller binds on them. |
|
||
| **Delivery rate** | **no working figure, deliberately** (FINDINGS 50, USER DECISION). Every tool REQUIRES an explicit rate. |
|
||
| **Seek time** | **no figure at all, and never had one.** 51.3/51.4 made it matter. |
|
||
| **W, clocks stolen per delivered byte** | 5 single-address held, 9 dual held, 12 single arbitrated; the IPL ROM's own disk channel is **16..19** (52.5). **The largest open number in the project.** Session 27 added the row underneath it: with **no external request line** on the card (59.2) the channel is auto-requested and is charged **by time rather than by byte**, so at 460 KB/s a 50% bus share costs **10.61 clk/B** and a smaller share cannot carry the rate at all (59.3). |
|
||
| **The frame's headroom for a transport** | **6.69 clk/B** — 30.2% of a 12 fps frame, after the MEASURED decode (68.6%) and best-case audio (1.25%), at the DLX5 gate container's **37,585 B delivered record** (session 28: the sector pad is delivered, so `15_bus_occupancy.py` charges it). **It is the number every row above is read against**, and a dual-address byte's floor is 9. It was 6.74 against the DLX4 container; the bundle moved it by 0.05 and moved no conclusion. |
|
||
|
||
---
|
||
|
||
## What was already done, and was still on the list
|
||
|
||
Found while inventorying for this file. Both had been closed in code for several
|
||
sessions and were still listed as open gaps in `docs/STATUS.md`:
|
||
|
||
- **4-byte record padding.** `DLX2`, `encode.py:139-156`, inside rate-control
|
||
accounting, reported per frame and per second.
|
||
- **CPU cost in the mode decision.** `vq_hybrid.py:218`, priced against measured
|
||
per-mode cycles with the exact clustered SKIP rule.
|
||
|
||
Both entries are now struck in STATUS. **The lesson is procedural: a gap list
|
||
that is only ever appended to manufactures phantom work.** Anything crossed off
|
||
below should be crossed off in STATUS in the same sitting.
|
||
|
||
---
|
||
|
||
## Blocked on hardware this tree does not have
|
||
|
||
None of these block M2 or M3 software work, because session 18 forced every rate
|
||
to be an explicit argument. They set constants, and two of them decide how much
|
||
headroom the finished player has.
|
||
|
||
**B1. Measure the BlueSCSI — throughput AND seek time.** **Session 29 gave this
|
||
a second acceptance test that is not a codec figure at all: 576 KB/s SUSTAINED,
|
||
which is what a decoder-free packed literal frame costs and cannot be talked down
|
||
from (FINDINGS 61.5).** A codec's bitrate is a lever; a literal frame's is
|
||
geometry. So the measurement now has three thresholds to be read against —
|
||
453.6 KB/s (the gate container needs no prefill), 327 KB/s (E7's target at the
|
||
dual-address floor) and 576 KB/s (no decoder at all) — and which of them the
|
||
medium clears decides which player gets built.
|
||
Throughput has an acceptance test already derived from real record sizes:
|
||
**513.2 KB/s** for the session-14 candidate, **453.6 KB/s** for the DLX5 gate
|
||
container (`19_ring_stream.py`, FINDINGS 49.5; the figure was 451.4 before
|
||
session 28's re-encode and the sector pad raised it). Seek time has nothing.
|
||
51.3/51.4 is why the second half matters: slack is *accumulated* out of
|
||
`pipe - wire`, so what a branch point costs is set by the rate and the time since
|
||
the last branch, not by the ring size. At 460 KB/s every ring from 192 KB to
|
||
512 KB is rate-bound and never fills. **Do not substitute a guess** — run at
|
||
several explicit rates and report the sensitivity. That is exactly how the
|
||
retired pipe constant survived five sessions after 42.1 called it folklore.
|
||
|
||
**B2. Does buffer mode blank the display?** `probe_bit11_blank.lua` is written
|
||
and settles it in minutes on a real board. FINDINGS 48 shifted the prior toward
|
||
MAME and toward "unusable" — **do not pre-build on 1.0 B/pixel**. Same sitting:
|
||
the priority register `0xE82500` at `0x0000` (47.3).
|
||
|
||
> **Session 29 raised what this is worth, and gave it a number to be worth
|
||
> (FINDINGS 61).** It used to gate a derived halving. It now gates a player that
|
||
> has been measured to fit a budget the shipping design misses — 55.2% of a frame
|
||
> against 110.4% at the 9 clk/B floor. And 61.6 found an asymmetry worth carrying
|
||
> to the board: the black interval is the PAINT, not the frame, so a CPU-painted
|
||
> packed player is dark for **27.3%** of a frame while the cheaper DMAC-direct one
|
||
> is dark for **30..113%**. Under MAME's reading the cheap architecture is the
|
||
> dark one. **B2 and B1 are now the same decision from two sides, and B2 is the
|
||
> five-minute half.**
|
||
|
||
**B4. Does a real palette register take a BYTE write?** (62.4, new session 30.)
|
||
`$E82000` is 256 16-bit registers. The decoder-free packed player's per-frame
|
||
palette rides the frame's array chain as a 193rd entry, which means a
|
||
dual-address channel with an 8-bit device port writes it **one byte at a time**,
|
||
even bytes into the high half of a register and odd into the low. **MAME cannot
|
||
be asked**: it maps the palette to `palette_device::read16/write16`, which is
|
||
`memory_array`'s `COMBINE_DATA` over plain storage — RAM that honours
|
||
`mem_mask`, with no handler that could refuse. So the run in 62 is a statement
|
||
about the model and not about the board.
|
||
|
||
**It is the cheapest item on this list, cheaper than B2**: write `$A5` to
|
||
`$E82000` and `$5A` to `$E82001` from the CPU and read the word back. If it
|
||
comes back `$A55A` the palette rides the chain. **The blast radius of a negative
|
||
is small and known** — the palette leaves the chain and the CPU writes 256 words
|
||
a frame at 61.9's derived 0.28% of a frame — which is why this is B4 and not
|
||
above B2.
|
||
|
||
**B3. Single-address vs dual-address DMA — and now, first, DOES THE CARD DRIVE
|
||
`#EXREQ`?** 242 KB/s and 0.69 dB was the old framing. **Session 27 promoted this
|
||
to the item that decides whether the design fits at all (59.7).** The frame
|
||
affords **6.74 clocks a byte**; a dual-address byte is **9** — a 4-clock read of
|
||
the device plus a 5-clock write to memory — so **no dual-address configuration
|
||
fits this container at 12 fps, at any delivery rate and any GCR share.** Single
|
||
address is 5 clk/B and fits at 92.2% of the frame with 7.8% to spare, and it
|
||
needs the device to ACK the DMAC directly, which needs the request line.
|
||
|
||
**MAME cannot answer it**: `x68k_scsiext.cpp`'s `drq_w` only stores a flag and
|
||
the expansion slot has no request path to the HD63450 at all (59.2). The slot
|
||
PINOUT has `#EXREQ` at B36 and `#EXACK` at B37, so the provision exists on the
|
||
real board. **What is wanted from hardware is therefore narrower and sharper
|
||
than "disassemble the ROM": does a CZ-6BS1 assert `#EXREQ` during a data phase,
|
||
and will the HD63450 run a single-address channel against it?** A scope or a
|
||
logic analyser answers the first; the second is a program the player can run.
|
||
|
||
Sourcing `scsiexrom.bin` (8 KB, CRC `7be488de`) and disassembling its DMAC init
|
||
is still the cheapest paper route to the same answer, because whatever Sharp's
|
||
own driver programs into DCR's DTYP is a statement about what the card supports.
|
||
FINDINGS 48.4. Not on this machine (checked, session 18).
|
||
|
||
**Ranking, amended session 29.** It was: B1 sets how much headroom the player
|
||
has, B3 decides whether there is any. FINDINGS 61 adds a third reading — **B1
|
||
and B2 together decide which player exists.** If the medium clears 576 KB/s
|
||
sustained and buffer mode does not blank, the decoder-free packed literal fits at
|
||
the dual-address floor B3 cannot get under, and B3 stops mattering for video at
|
||
all. If it does not, B3 is still the question. The three hardware facts are no longer
|
||
independent, and B2 is by far the cheapest of them.
|
||
|
||
> **ENCODER WORK IS PARKED — USER DECISION, session 29.** The first draft of this
|
||
> amendment said "nothing here is a reason to stop work on the codec". That does
|
||
> not survive its own arithmetic. It rested on comparing the packed player's
|
||
> 576 KB/s against **E7's 327 KB/s target, which does not exist**: the codec that
|
||
> exists is 440 KB/s and 110.4% of a frame, so the real gap is 1.31x, not 1.76x.
|
||
> And the branches are not symmetric. **Packed needs two facts** — buffer mode
|
||
> does not blank, medium clears 576 KB/s. **The codec needs E7 to succeed** —
|
||
> unproven, and 60.4/60.5 measured two of its three levers inert — **AND** the
|
||
> medium to clear 327, **AND** it ships at ~100% of the frame with no margin,
|
||
> which is where 55.2% is now.
|
||
>
|
||
> **E7 and E4 are both parked**, E4 included: `H.build`'s k-means builds VQ
|
||
> codebooks and a literal player has no VQ. C1 is gated by E4 and follows.
|
||
>
|
||
> **The codec is KEPT AND NOT BUILT ON.** That is inventory, not work. B2 is
|
||
> unanswered and 48.1's prior leans against packing — an assertion against a
|
||
> silence — and if buffer mode blanks there is no version of the packed player
|
||
> that is merely expensive (48.3), at which point the codec is the only path
|
||
> left. Keeping a working decoder on disk costs nothing; building on it costs
|
||
> sessions.
|
||
|
||
**The older ranking, which still holds inside the codec branch:** B1 (throughput
|
||
and seek) sets how much headroom the finished player has. **B3 decides whether
|
||
there is any.** If the card drives `#EXREQ`, the ladder applies and the design fits with
|
||
room. If it does not, the fallback is limited-rate auto-request at a share the
|
||
player picks (P4c), and the container has to come down to **328 KB/s of payload**
|
||
to fit at the 9 clk/B floor — 34% below where the gate container sits (59.7).
|
||
|
||
> **Session 20 moved the prior hard, and it moved the wrong way (FINDINGS 52.5).**
|
||
> The IPL ROM *is* on this machine, and `tools/analysis/21_iplrom_dmac.py` reads
|
||
> its HD63450 setup: the on-board disk channel (ch1, SASI) is `DCR = $80` —
|
||
> **dual address, 8-bit port, cycle steal WITHOUT hold**, with `REQG = 10`
|
||
> external request, i.e. a full bus arbitration per byte. That is **16..19
|
||
> clocks per delivered byte**, above the whole 5..12 bracket 42.4 costs P4 in.
|
||
> Same vendor, same DMAC, same class of 8-bit port — but it is *not*
|
||
> `scsiexrom.bin`, so B3 stays open. What it changes is that a cheap
|
||
> configuration is now the thing that has to be **shown**, not assumed.
|
||
|
||
---
|
||
|
||
## The packed branch — what building it means (session 29, USER DECISION)
|
||
|
||
**This is where the work goes now.** FINDINGS 61: a decoder-free packed literal
|
||
player is **55.2% of a frame at the 9 clk/B dual-address floor** against the
|
||
codec's 110.4%, and **+4.89 dB** on the shipping container because a literal
|
||
frame is not tied to a scene palette the codec's codewords index into. It costs
|
||
**582 KB/s, fixed, with no lever.** Three items, in order.
|
||
|
||
~~**K1. Can a DMA channel write the palette registers at `$E82000`?**~~
|
||
**DONE, session 30 — FINDINGS 62. YES, in this model.** `dmagate.s` runs 7–9:
|
||
512 B off the disc into the whole graphic palette, **byte-exact in 256
|
||
register words** read back out of `$E82000` by the 68000; the same transfer
|
||
aimed at RAM leaving the palette as the CPU poisoned it, which is what
|
||
attributes the first run to the channel's `MAR`; and **ONE array-chained start
|
||
crossing from device registers into GVRAM**, which is the shape of a whole
|
||
frame. The destination is POISONED first (62.1) because "it matches" was a
|
||
weak claim against a record that is mostly pad, and the host counts the
|
||
poison's discriminating power rather than assuming it: 511 of 512.
|
||
**And the array is SCENE-constant** (62.3) — the packed layout spends both
|
||
256-colour pages, so there is no page to flip and the 193 destinations never
|
||
change; the 1,158 B array is built once at scene setup. What is left on the
|
||
CPU per frame in the video path is the channel start and the READ(10), and
|
||
neither is priced — say "no per-frame PAINT work", not "no per-frame CPU work".
|
||
**It opened B4** (62.4): MAME models the palette as a generic `palette_device`
|
||
over `memory_array`, whose `write16` is a plain `COMBINE_DATA`, so it has no
|
||
handler to be wrong about and cannot discriminate. And it filed one open
|
||
design choice, 62.5: palette FIRST or 193rd is visible on screen and is not
|
||
decided.
|
||
- **K2. A packed container.** Quantise to **254** colours (index 0 is the
|
||
transparency key, black at 255 — 47.2), interleave column *i* with *i+128*,
|
||
emit a per-frame palette, sector-align the record. Simpler than a coded one:
|
||
no VQ, no rate control, no mode map, no `lam`. `encode.py` already aligns
|
||
(DLX5) and `prep_frame.py --pack-transparent` already emits the layout.
|
||
**Re-derive 61.9's 34.08 dB against `vq.scene_palette` here** — the measured
|
||
figure is PIL's MEDIANCUT and is a direction, not the player's number.
|
||
- **K3. End to end, off the disc.** Palette, page-1 X-scroll 384, priority
|
||
`vc1 = 0x0002`, R20 bit 11; one chained DMA a frame. Gated pixel-exact over the
|
||
same 120-frame window the decoder is gated on.
|
||
|
||
**What K3 deletes, and why that is a risk and not a win to be banked:** a
|
||
DMAC-direct packed player has **no ring** — `ring.i`, `xfer.i` and most of
|
||
`stream.s` leave the video path, and **P4a's wiring is parked with them.** A
|
||
simplification that large usually hides something, and 61.7.2 names the specific
|
||
untested thing: a chained transfer has never run back to back at 12 fps.
|
||
|
||
**K1 and K2 survive a bad answer to B2. K3 does not.** ~~Do K1 first.~~ **K1 is
|
||
done (session 30, FINDINGS 62); K2 is next.**
|
||
|
||
---
|
||
|
||
## M2 — a player, as opposed to a decoder
|
||
|
||
`decode.s` draws pixel-exact frames from RAM Lua pre-loaded; `stream.s` decodes
|
||
out of a bounded ring fed by a host file on a paced clock. Neither is a player.
|
||
|
||
**Exit criterion: boots from a real SCSI volume on a stock 2 MB X68000, plays
|
||
one scene at 12 fps from disc, no host-file pipe, no Lua in the loop. Silent.**
|
||
|
||
~~**P1. Codebook expansion on the 68000.**~~ **DONE, session 21 — FINDINGS 53.**
|
||
`src/player/load.i` expands both codebooks out of the raw container header,
|
||
byte-exact against `dlxload.py` on both CPU cores. **9.26 ms**, and it was
|
||
priced where it lands rather than treated as free setup: the scene header is
|
||
**5,920 B that no rate table in this tree counted**, and in the currency of
|
||
51.3 — accumulated slack — those bytes lengthen the refill climb by 138 ms at
|
||
488 KB/s and by **1.099 s at 451.4 KB/s**, because the surplus they are divided
|
||
by goes to zero. The whole fixed cost of a scene change is about a third of one
|
||
frame slot; what makes a branch point expensive is still the seek and the climb.
|
||
Shipping the codebooks pre-expanded was considered and refused: it trades
|
||
9.26 ms of CPU for 5,120 more header bytes, which is a wash in milliseconds and
|
||
not a wash in kind (53.6).
|
||
|
||
~~**P2. Palette packing on the 68000.**~~ **DONE, session 28 — FINDINGS 60.**
|
||
The half that was open was the encoder's, and it closed with the whole bundle
|
||
below. Session 21 — FINDINGS 53 — did the packing itself:
|
||
~~The encoder still emits RGB888; the X68000 word packing is Lua-side.~~ The
|
||
packing is on the 68000: `pal_pack` writes 256 words straight into `$E82000`
|
||
with `I` chosen per entry by minimum squared error (**1.96 dB**, 23.3), gated on
|
||
the words read back **out of the palette registers**. 9.70 ms per scene, plus
|
||
5.29 ms of scene-independent table build hoisted to boot (53.3).
|
||
|
||
~~**What is left is the other half of the sentence: reserve index 0 as black with
|
||
`I = 0` (23.4).**~~ **DONE, session 28 — 60.3.** `VQ.scene_palette` quantises
|
||
the picture into 255 entries and holds index 0 at (0,0,0); `pack_palette` gives
|
||
it `I = 0` by its own minimum-squared-error rule, so 23.3's "the bars sit at
|
||
RGB (4,4,4)" needed no special case. **0.04 dB** on the Singe window, palette
|
||
ceiling unmoved. Black is reserved, not withheld — the mapper may still spend
|
||
index 0 on genuinely black pixels; what it buys is that index 0 is black
|
||
whatever the scene contains.
|
||
|
||
~~**THE RE-ENCODE BUNDLE, collected here because it is now four items and they
|
||
share one re-measurement.**~~ **DONE, session 28 — FINDINGS 60. Two of the four
|
||
closed as NEGATIVES, which is the more useful half.** The four were:
|
||
1. ~~reserve palette index 0 as black, `I = 0`~~ **DONE** (23.4, 60.3);
|
||
2. ~~`--spans all` as the default~~ **MEASURED AND REFUSED** (E2, 60.4).
|
||
267.9 KB/s / 28.88 dB / 2 frames late at `need`, against 448.2 KB/s /
|
||
29.07 dB / 1 late at `all`: **+67% of the wire for +0.19 dB and one frame
|
||
of 120**, against a container the budget already says is 35% too big.
|
||
`need` stays the default. **It was called "the loaded lever on the byte
|
||
side" and it is — it is just loaded the wrong way**, and E7 is why. The
|
||
GATE container keeps `all`: it is a fixture, not a recipe;
|
||
3. ~~re-derive span selection jointly with `lam`~~ **IMPLEMENTED, MEASURED,
|
||
NO-OP** (E3, 60.5). `--joint-spans` hands the span pass's freed bytes back
|
||
to the lam search and re-spans; all four cells of `{need,all} x
|
||
{greedy,joint}` are BYTE-IDENTICAL, and so is `--rc-floor open`. **`lam`
|
||
never leaves its floor on any of 120 frames at either floor the encoder
|
||
offers** (44.3), so there is nothing to spend the freed bytes on. The code
|
||
stays, defaulted off, because a container that moved `lam` off its floor
|
||
would make the question live again;
|
||
4. ~~**sector-align every record**~~ **DONE — the container is DLX5** (58.3
|
||
option C, promoted to a precondition by 59.4; 60.1). Realised cost
|
||
**+0.48%** on the wire against the +0.43% predicted, zero clocks, and
|
||
**120/120 records start on a sector boundary** where 3/120 did. The disc
|
||
and the ring now move the SAME 4,510,208 B and check.sh gates on that
|
||
identity, both figures read out of the container instead of written into
|
||
the script — the old literals went red on the re-encode, correctly.
|
||
**The consumer had to be told too** (60.2): `stream.s` released the ring
|
||
to the last byte it READ, which strands up to 511 B of pad a record, and
|
||
the ring's own audit caught it on frame 0 while every frame still decoded
|
||
pixel-exact. The release rounds to `RECALN` now.
|
||
|
||
The letterbox no longer gets the palette's closest thing to black — item 1 put
|
||
true black at index 0, and `load.i` needed no change, as it said it would not.
|
||
|
||
~~**P3. A real frame clock.**~~ **DONE, session 22 — FINDINGS 54.**
|
||
`src/player/clock.i` derives the tick from the CRTC's own V-DISP through the
|
||
MFP, with a remainder-keeping divider whose two constants are read out of the
|
||
CRTC at init. **Exactly 12.000000 fps, by construction** — measured at 649 ticks
|
||
over 3,000 refreshes where 649.1429 were due, so the remainder still held and
|
||
nothing accumulated. **181.35 clocks per V-DISP, 838 per frame, 0.1006% of the
|
||
budget**, timed by the 68000 itself because the host's 17.64 ms granularity
|
||
cannot see it. `PACEON=0` free-run is untouched and so is the wait loop; the
|
||
free-running path executes none of the new code.
|
||
|
||
The item said "MFP timer or VBL" and **neither can do it alone**: 4e6/12 is not
|
||
an integer and no prescale/data pair reaches 12 Hz, while the slowest MFP tick
|
||
of any kind is 78.125 Hz; and the raster's 55.4577 Hz has no whole divide near
|
||
12 either (4 gives 13.86, 5 gives 11.09). `tools/analysis/23_frame_clock.py`
|
||
walks the whole space rather than asserting it.
|
||
|
||
**What it exposed is bigger than the item.** 12 fps on a 55.4577 Hz raster is
|
||
4.6215 refreshes, so a frame gets **4 refreshes (72.13 ms) or 5 (90.16 ms)** and
|
||
**there is no 83.33 ms frame** — that figure is the mean slot, and 37.9% of slots
|
||
are 13.4% under it. The cadence was ALREADY in every host-paced result in
|
||
FINDINGS 49/51, because `stream.lua`'s tick is sampled at frame boundaries and
|
||
its gaps were always 4 or 5; nothing had named it. On the gate container it
|
||
costs 4 frames of 120 their idle against 1 for the nominal model. **It is not a
|
||
dropped frame** — the pace gate lets an overrun eat the next frame's idle and
|
||
the clock recovers — but it means every budget in this project is priced against
|
||
a slot 37.9% of frames do not get. 54.4.
|
||
|
||
**Also struck: MAME's raster runs 2.22% fast** (`refresh_mode()` builds the frame
|
||
period from `htotal - 8`), so the tree's "1/55.46 s granularity" was 1/56.69 s
|
||
throughout. No 68000 cycle figure moves — the CPU clock is unrelated to the
|
||
screen — but anything paced by the raster does. 54.5.
|
||
|
||
**P4. Real transport. P4b DONE, session 26 — FINDINGS 58. P4a DONE at the
|
||
transport level, session 27 — FINDINGS 59. What is now between this tree and M2
|
||
is THE RE-ENCODE BUNDLE under P2, because the channel refuses a windowed read
|
||
(59.4) and 117 of 120 records need one.**
|
||
~~Drive the MB89352 instead of a host file.~~ `src/player/scsi.i` selects a SCSI
|
||
target and issues READ(10) on the 68000, with no IOCS and no host in the
|
||
transfer path: **4,096 B from LBA 0 and 2,048 B from LBA 1000, both byte-exact**
|
||
against the host's copy of the same volume.
|
||
|
||
**This item was listed as blocked and was not.** Session 21 recorded "MAME's
|
||
`x68000` has no MB89352 path"; `-exp1 cz6bs1` instantiates one, and FINDINGS
|
||
32.4 had read that card's DMA glue back in session 9. The real gap was the 8 KB
|
||
`scsiexrom.bin` MAME needs to instantiate the card and **the player never
|
||
executes**; a blank placeholder on a separate rompath settles it. **B3 still
|
||
wants the real ROM's bytes** and is untouched by this.
|
||
|
||
**What is left is the half that decides the project**, and it is now two pieces:
|
||
|
||
~~**P4a. A DMAC configuration that HOLDS THE BUS.**~~ **DONE at the transport
|
||
level, session 27 — FINDINGS 59.** `src/player/dma.i` programs HD63450 channel 1
|
||
and takes the DATA IN phase: **the same 2,048 B off the disc three ways — PIO,
|
||
held, stealing — all three byte-exact.** 57.3's warning was met rather than
|
||
worked around: the evidence never reads `$EA0015`. **MTC is sampled by the
|
||
instruction after the one that starts the channel, and held it reads zero of
|
||
2,048** — the whole transfer happened between two instructions, because the
|
||
68000 did not execute in between — against the full count and 426 CPU loop trips
|
||
for the stealing configuration. Put the stealing registers in the held slot and
|
||
every byte still arrives and the gate goes **red**, which is what says the
|
||
counter can come out different (58.3's vacuous-counter trap, avoided
|
||
deliberately).
|
||
|
||
**Three bounds on the apparatus, read out of MAME's source and not inferred**
|
||
(59.2): the card has **no request line to the DMAC** (its flow control is
|
||
DTACK), so external request — the mode the `W`=5 and `W`=12 rows assume —
|
||
cannot be run; **single address** cannot be run either (only channel 0 has
|
||
device callbacks); and **only burst is modelled as held**. Of the four rows of
|
||
the ladder exactly one, dual address held, has a code path here, and it is the
|
||
one demonstrated. The slot pinout has `#EXREQ` at B36, so a real card plausibly
|
||
drives it — **that is now B3's sharpest form**.
|
||
|
||
~~**What is left of P4a is downstream of the container, not of the DMAC**
|
||
(59.4)~~ — **and that block is GONE as of session 28.** `sc_in_data` refused a
|
||
windowed read because a channel cannot drop the 300 B in front of a record; the
|
||
container is DLX5 now and no record asks for a window. `xfer.i`'s sector
|
||
arithmetic already degenerates correctly — `SC_WSKIP` is 0 and `SC_WKEEP` is the
|
||
whole record on every one of the 120 — so **what is left of P4a is the wiring:
|
||
which loop moves the bytes.** `dma_run.sh`'s windowed-read refusal stays as a
|
||
negative control rather than as a description of the container. **This is the
|
||
next item, and it is the last one before M2** (60.9).
|
||
|
||
**P4c (new, and it is a DESIGN CHOICE the tree had not named).** Auto-request is
|
||
charged **by time, not by byte** — the channel spends its share of the bus
|
||
whether or not a byte is there, so halving the delivery rate DOUBLES the CPU
|
||
cost of the same record. The MC68450's GCR sets that share: `BT`/`BR`, four
|
||
values, 50/25/12.5/6.25%. `tools/analysis/28_autorequest_cost.py` prices it
|
||
against an explicit rate; at 460 KB/s **only the 50% share carries this
|
||
container**, at 10.61 clk/B and 47.6% of a frame per record, against 40.4% for
|
||
the `W`=9 row and 391.8% measured for PIO. **If B3 comes back saying the real
|
||
card drives `#EXREQ`, the ladder applies and this is the fallback; if it does
|
||
not, this IS the cost model** and the GCR pair is a number the player has to
|
||
choose.
|
||
|
||
~~**P4b. `scsi.i` behind `ring.i`'s `XF_*` mailbox.**~~ **DONE, session 26 —
|
||
FINDINGS 58.** `src/player/xfer.i` answers the mailbox with a real READ(10) per
|
||
record: **120 records, 4,488,588 B, pixel-exact, out of the same 256 KB ring,
|
||
with a real mid-stream seek in a second pass**. The tiling is the SAME 18 wraps
|
||
and 14.7 KB mean hole that 49.4's host producer and 55.4's modelled transport
|
||
produced — a third transport, same placement, which is the assertion that
|
||
`ring.i` could not tell which side of the seam answered it. The change above the
|
||
seam is two `bsr`s, and the one in `ring_seek`'s quiet-wait is not optional:
|
||
with the transport inside the machine, that loop is the only thing that can
|
||
retire an outstanding request.
|
||
|
||
**What it cost is the finding, and it re-prices P4a.** `tools/bench/
|
||
xfer_cost.sh` subtracts the same 120 frames run twice and gets **87.28 clocks
|
||
per delivered byte** — against the 68000's own cycle table for the loop, which
|
||
says **87.15**. **0.2% apart**, so it is the instruction stream and not MAME's
|
||
device model, and it is therefore the first number this rig has produced that
|
||
survives leaving the emulator. At this container's 37,405 B mean record that is
|
||
**391.8% of a 12 fps frame**, and the machine's own V-DISP clock agrees from the
|
||
other end: **2.57 fps**.
|
||
|
||
W = 5 single address, bus HELD ............................ 22.4%
|
||
W = 9 dual address, held .................................. 40.4%
|
||
W = 12 single address, arbitrated .......................... 53.9%
|
||
W = 19 dual address, arbitrated -- the IPL ROM's own (52.5) . 85.3%
|
||
PIO 87 MEASURED, session 26 ................................ 391.8%
|
||
|
||
**So P4a is worth 4.6x the worst DMA configuration in this tree and 17.5x the
|
||
best**, where before this session it was worth 9 against 19. `W` itself **did
|
||
not move by one clock** and is still the largest open number — but what depends
|
||
on it just got much larger.
|
||
|
||
**One more thing P4a inherits (58.3).** A record is not a sector: 117 of 120
|
||
start part way into one. PIO absorbs that for free because the CPU is already
|
||
touching every byte and simply does not store the ones outside the window — a
|
||
property that **disappears the moment the DMAC takes over**, because a channel
|
||
writes a contiguous run and cannot drop bytes. The three ways out price as
|
||
+1.34% wire and no DMA (windowed PIO), +1.34% wire and **+5 clk/B of copy**
|
||
(bounce buffer, which is exactly the cost `aligned` was chosen over `split` to
|
||
avoid), or **+0.43% wire and zero clocks** (sector-aligned records in the
|
||
container). The last one wins on both axes and is a **re-encode**; see the
|
||
bundle under P2. **P4a should be attempted against a sector-aligned container,
|
||
not against this one.** *(Session 27: it was, in the only sense that mattered —
|
||
the transport now REFUSES the windowed case rather than being trusted not to
|
||
reach it, so the bundle is a precondition rather than a plan. 59.4. Session 28:
|
||
the container IS one — the realised wire cost is +0.48% against the +0.43%
|
||
predicted here, and 120/120 records start on a sector boundary. 60.1.)*
|
||
|
||
*(original item, still the standing description of the `W` question:)*
|
||
Drive the MB89352 instead of a host file. **Session 23
|
||
added a second axis to it:** `W` is the clocks stolen per delivered byte, and
|
||
55.3 measured that the player's own request loop gives away 3-7% of the pipe
|
||
before `W` is even asked about. A transport design has to answer both. The `W`
|
||
handshake — clocks stolen per delivered byte, bracketed 5..12 by MC68450 Fig
|
||
4-25 — is listed in "Decisions locked" as UNDECIDED and as the thing that
|
||
decides the project: `W<=6` fits 0/120 frames, `W=8` misses 47/120. It is a
|
||
property of how the player drives the SPC, **so it is ours to choose, not to
|
||
receive** (FINDINGS 42.4-42.6). B3 informs it.
|
||
|
||
**Session 20 promoted this to the project's biggest open number.** FINDINGS 52.5
|
||
found the only worked example of a disk DMA configuration on this machine — the
|
||
IPL ROM's own — sitting at **16..19 clk/B**, where the whole design fails at any
|
||
container size (`15_bus_occupancy.py` sweeps it). The per-byte ladder is 5 clk/B
|
||
single-address with the bus held, 9 dual-address held, 12 single-address
|
||
arbitrated, 16..19 dual-address arbitrated. **Getting the DMAC to hold the bus
|
||
is the difference between 9 and 19**, it is a property of how the player
|
||
programs the channel, and demonstrating a configuration that does it is P4's
|
||
first job rather than its last.
|
||
|
||
**Do not quote 42.4's `W <= 6` / `W = 8` sensitivity table for this.** It is in
|
||
clocks per WORD and FINDINGS 43 voided it; 52.5 cited it in byte units when
|
||
first written and strikes it.
|
||
|
||
~~**P5. Seek and branch.**~~ **DONE, session 23 — FINDINGS 55.**
|
||
`src/player/ring.i` fills the ring on the 68000: `aligned` placement, the
|
||
descriptor ring, a prefill policy, 51.2's slack rule as arithmetic the player
|
||
can run (`ring_may_seek`), and a seek that quiets the channel and re-addresses
|
||
the stream out of the index. It reproduces the host producer's tiling exactly —
|
||
18 wraps, 14.7 KB mean hole, pixel-exact — and the host now AUDITS every
|
||
placement instead of making it.
|
||
|
||
The index is a **container change**: DLX4 carries `nframes` u16 record lengths
|
||
in the scene header, because `aligned` needs a record's length before it fetches
|
||
it and walking the stream is precisely what a player cannot do. Frame payloads
|
||
are byte-identical to the DLX3 encode; the scene header goes 5,920 to 6,164 B.
|
||
|
||
**What it exposed is bigger than the item.** A channel only moves bytes while it
|
||
has a request and only the CPU can issue one, so the disc **stands still between
|
||
records** by an amount set by the player's loop rather than by the medium — and
|
||
no host-filled run could see it. At 488 KB/s in a 256 KB ring, a one-deep
|
||
request queue gives away **6.8% of the pipe and underruns 59 of 120 frames**; a
|
||
two-deep one gives away 3.4% and underruns none. The container's whole surplus
|
||
over the wire at that rate is 8.7%, so the player's own loop was spending most
|
||
of the slack 51.3 accumulates. **Prefill is the weaker lever** — six records of
|
||
it still leaves 24 underruns at depth 1 — and the fix costs no clocks and no
|
||
bytes. 55.3, 55.4.
|
||
|
||
**P5a (open, and it belongs with P4).** The two-deep queue is modelled as two
|
||
mailbox slots. On the machine it is two DMAC channels or one channel with a
|
||
chained descriptor array, and which of those is affordable is a `W` question.
|
||
|
||
**P7. Boot.** The player as an executable loading from the SCSI volume.
|
||
Buildable, and empty until P4: there is nothing to boot from yet.
|
||
|
||
---
|
||
|
||
## M3 — the vertical slice, and the completion target
|
||
|
||
**Exit criterion: one decision point, two outcomes, a death clip, with audio,
|
||
playing from disc on stock hardware.**
|
||
|
||
**P6. Audio — and it is the largest unpriced risk left in the project.**
|
||
MSM6258 ADPCM, 15.6 kHz mono, **7.8 KB/s**. That figure is in `ratectl.py`'s
|
||
budget and nowhere else: not extracted, not encoded, not interleaved into the
|
||
container, and **never priced on the bus**. Two reasons to treat it as a risk
|
||
rather than a task:
|
||
|
||
1. A second DMA consumer attacks **the bus** — the resource this project already
|
||
established is the binding one, at 86.7% occupied. Clock headroom says
|
||
nothing about whether it fits.
|
||
2. 7.8 KB/s is a *byte* figure. The last time a byte/word unit error went
|
||
unexamined in a delivery budget it cost the project a 2x error in every table
|
||
since FINDINGS 5 (session 14, the MB89352 being an 8-bit SPC).
|
||
|
||
~~**Price it before writing it**: add the ADPCM DMA stream to `15_bus.py` and see
|
||
what it does to the 86.7%.~~ **DONE, session 20 — FINDINGS 52.** It is in
|
||
`15_bus_occupancy.py` and the answer is **1.25%..1.48% of the frame**, about 4%
|
||
of what the decoder leaves. The per-byte cost is no longer a guess borrowed from
|
||
the disk: `tools/analysis/21_iplrom_dmac.py` reads the IPL ROM's own HD63450
|
||
configuration and finds ch3 dual-address, 8-bit port, cycle steal without hold,
|
||
external request — **16..19 clocks per byte**, where `11_cpu_budget.py` had been
|
||
charging audio the disk's 5. Both worries above resolve:
|
||
|
||
1. **The bus concern does not materialise.** A second DMA consumer at 7.8 kB/s
|
||
is not what a bus at 88% occupancy is short of.
|
||
2. **The unit was checked and is nearly right.** 15.6 kHz = 8 MHz ÷ 512 =
|
||
15,625 samples/s, 4 bits each, two to a byte = **7,812.5 B/s exactly**. The
|
||
7.8 was decimal kB being multiplied by 1024; 2.4% high, now derived from the
|
||
sample rate in `buscost.ADPCM_BYTES_PER_S`.
|
||
|
||
**What is still open in P6 is everything except the bus:** extraction, encode,
|
||
container interleave, and what a second stream does to `wire` — and therefore to
|
||
`pipe - wire`, and therefore to 51.3's refill climb. That last one is the
|
||
interaction to price next, and it is E2's question with a second consumer in it.
|
||
|
||
**E6. Container v2** — audio interleave, per-record index, scene table. Depends
|
||
on P6's answer and on P5's index.
|
||
|
||
**E7. A BYTE TARGET, AND IT COMES OUT OF THE BUS BUDGET RATHER THAN OUT OF
|
||
TASTE (new, session 27, FINDINGS 59.7; re-measured session 28, 60.7).** The
|
||
frame affords **6.69 clocks a byte** after the measured decode and the audio,
|
||
and a dual-address byte costs **9**. So *if* B3 comes back saying the card
|
||
cannot drive `#EXREQ`, the container has to reach **27,924 B a frame — 327 KB/s
|
||
of payload** to fit at 12 fps, where the DLX5 gate container delivers 37,585 B
|
||
and 440 KB/s: **35% too big.** (It was 6.74 / 328 / 34% against the DLX4
|
||
container. **The bundle moved the target by one KB/s and moved no conclusion**,
|
||
which is what a precondition is supposed to do.)
|
||
|
||
Three things make this less alarming than the number looks, and one makes it
|
||
worse:
|
||
|
||
- The gate container is **deliberately the heaviest thing the encoder emits**
|
||
(span-heavy, the 488 recipe, every block mode exercised). It is a test
|
||
fixture, not a shipping target.
|
||
- A lighter container **also decodes cheaper**, so the 68.5% decode term falls
|
||
with the byte term. 328 KB/s is the pessimistic reading of the lever.
|
||
- `rc_fr_singe_scsi_cpufit.dlx` already exists — the encoder has had a
|
||
CPU-fitting mode since session 11.
|
||
- **Worse:** `15_bus_occupancy.py` REFUSES to price the cpufit container,
|
||
correctly, because the C68K measurement it cross-checks against belongs to
|
||
the gate container. **So E7 starts with a harness re-run**
|
||
(`tools/bench/c68k/run.sh`) against whichever container is to be the target,
|
||
and until that is done "34% too big" is a statement about the fixture rather
|
||
than about the project.
|
||
|
||
~~**G1. Import the scene graph — early, because it is a measurement input.**~~
|
||
**DONE, session 24 — FINDINGS 56.** It was pulled ahead for exactly the reason
|
||
given, and it paid: **the worst gap between two consecutive decision points is
|
||
zero**, and 5.4% of the game's 612 branch transitions are. Two seeks can fall
|
||
back to back with no play between them, so 51.2's slack rule can be answered NO
|
||
by the content rather than by the buffer.
|
||
|
||
It does not break the design — a branch on an empty ring costs the prefill
|
||
(149.7 ms, 1.80 frame slots at 488 KB/s), not the climb — but it removes the
|
||
margin: at 488 KB/s in a 256 KB ring, **76% of this game's branch points arrive
|
||
before the ring has refilled**, and a 512 KB ring makes that 90%. **The ring is
|
||
not the lever; the surplus is.**
|
||
|
||
Two constraints on the input layer came with it: the arcade needs **eight
|
||
directions**, and the shortest input window is **98 ms** against a 72.13/90.16 ms
|
||
frame slot, so input cannot be polled on the frame tick (56.7).
|
||
|
||
**The cross-check plan was wrong and is struck.** The SNES chapters are
|
||
*derived* from DirkSimple, by their own README, so there is one transcription and
|
||
not two; the diff catches conversion errors only (56.2). **Nothing is vendored:**
|
||
`tools/import/scenegraph.py` is the one file coupled to those projects and it
|
||
writes this project's own `DLXSCENE1` schema into gitignored `tmp/`
|
||
(USER DECISION, session 24).
|
||
|
||
---
|
||
|
||
## M4 — the whole game
|
||
|
||
Listed for completeness; past M3 these are scope, not risk.
|
||
|
||
- **C1. Full-disc survey**, 22.8 minutes. Classify **content / menu / bonus** —
|
||
not menu vs content: the two largest streams are bonus material and look like
|
||
content by size, duration and bitrate alike (25.1). Run
|
||
`07_motion_survey.py` per stream first for a hot-window shortlist.
|
||
**Gated by E4, and parked with it (session 29).**
|
||
- ~~**E4. `H.build` k-means**, 51 s of a 55 s run, once per scene.~~ **PARKED,
|
||
session 29 (USER DECISION).** It was the thing to attack before C1. It builds
|
||
**VQ codebooks**, and a decoder-free packed player has no VQ — so this is
|
||
encoder work on the branch that is no longer being built on. It comes back if
|
||
and only if B2 goes MAME's way. C1 is gated by it and is parked with it.
|
||
- ~~**E2. `--spans all` as default.**~~ **MEASURED AND REFUSED, session 28 —
|
||
60.4.** It was "a recommendation, not a measurement" since 43.6.1 and it is a
|
||
measurement now: **+67% of the wire for +0.19 dB and one frame of 120.** It
|
||
IS the loaded lever on the byte side (44.3) — it is loaded the wrong way, and
|
||
E7 is why. What the entry predicted is exactly what it does: it raises `wire`,
|
||
which shrinks `pipe - wire`, which lengthens the refill climb. `need` stays
|
||
the default; the GATE container keeps `all` because it is a fixture.
|
||
- ~~**E3. Re-derive span selection jointly with `lam`** (39.3).~~
|
||
**IMPLEMENTED, MEASURED, NO-OP, session 28 — 60.5.** `--joint-spans` emits
|
||
byte-identical containers in all four `{need,all} x {greedy,joint}` cells and
|
||
at both lam floors, because **`lam` never leaves its floor on any of 120
|
||
frames**. Kept and defaulted off.
|
||
- **C2. Framing** — crop vs squash vs wide (FINDINGS 12). Needs an eyeball
|
||
against arcade reference, not a measurement. Cheap; blocks only final encodes.
|
||
- **C3. Disk image packaging**, ~1.09 GiB at the candidate rate.
|
||
- **G2/G3.** Branching, input windows, death clips, attract mode; playtest.
|
||
|
||
---
|
||
|
||
## Dependency summary
|
||
|
||
```
|
||
P4a DONE (59): the channel drives the data phase and
|
||
holds the bus -- 391.7% of a frame becomes 40..95%
|
||
│
|
||
B3 #EXREQ? ──┬─ YES ─> single address, 5 clk/B, 92.4% ── FITS ──┐
|
||
│ │
|
||
└─ NO ──> auto-request, 9 clk/B FLOOR, 110.4% ──> E7 byte target
|
||
(the frame affords 6.69; 59.7, 60.7) 327 KB/s
|
||
│
|
||
P2 re-encode bundle DONE (60): DLX5, records ARE sectors ─────────┤
|
||
E2 refused on measurement, E3 a no-op -- 60.4, 60.5 │
|
||
E7/E4/C1 PARKED session 29: encoder work waits on B2 ───────────┤
|
||
│
|
||
K1 palette-register DMA? ─> K2 packed container ─> K3 end to end ──┤
|
||
(61.9; K1/K2 survive a bad B2, K3 does not) │
|
||
P4a WIRING (the channel behind ring.i's mailbox) <- THE LAST ITEM ─┤
|
||
P1 P2 P3 P4b P5 P7, P6 bus cost (52), G1 scene graph (56) ────────┼─> M2 ─>
|
||
B1 seek+rate (sets HEADROOM, not fit) ─────────────────────────────┘ M3 ─> M4
|
||
B2 blanking ─┬─ NOT blanked ─> the DECODER-FREE PACKED player is live (61):
|
||
│ 55.2% of a frame at the 9 clk/B FLOOR, against
|
||
│ the codec's 110.4% -- and then B1 decides it,
|
||
│ because it wants 576 KB/s FIXED (61.4, 61.5)
|
||
└─ blanked ──────> a 27..113% duty strobe; there is no version of
|
||
it that is merely expensive (48.3, 61.6)
|
||
```
|
||
|
||
**Read that top-left branch as the project's live question.** Everything else
|
||
on the diagram is work; `#EXREQ` is a fact about a board nobody here has, and it
|
||
decides which of the two lower paths the player is on.
|
||
|
||
## Standing rules that apply to all of it
|
||
|
||
- **Green light first and last.** `./tools/bench/check.sh`, ALL GREEN, before and
|
||
after. **Never two MAME jobs at once** — session 18 did it, two `decode.lua`
|
||
runs shared a log file, and it produced a 0-byte log and 15 wasted minutes.
|
||
- **Name the layer.** Emulated, or real hardware. Every progress claim.
|
||
- **Label measured / estimated / folklore.** A rate with no provenance is
|
||
folklore even when it is plausible, and this project has already paid for that
|
||
twice.
|
||
- **No new default constants.** Rates stay explicit arguments. If a measurement
|
||
is not available, report the sensitivity across several rates rather than
|
||
picking one.
|