Put the ring on the 68000, and find the disc stops whenever the player is not asking
ROADMAP P5. The loader moved in session 21 and the frame clock in 22; the ring producer was the last policy living outside the machine. src/player/ring.i does `aligned` placement, the descriptor ring, a prefill, 51.2's slack rule and a seek, and the host keeps only the transport. It needed a container change. `aligned` asks whether the next record fits before the end of the ring -- a length asked BEFORE the record is fetched -- and every reader in this tree answered that by walking the frame stream, which is exactly what a player streaming off a disc cannot do. DLX4 carries nframes u16 record lengths in the scene header. Frame payloads are byte-identical to the DLX3 encode, so no fitted constant moves; the scene header goes 5,920 to 6,164 B. The producer reproduces the host's tiling exactly: 18 wraps, 14.7 KB mean hole, pixel-exact, a third independent implementation of the same policy. 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 the PLAYER sets, not 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; two-deep gives away 3.4% and underruns none. The container's whole surplus over the wire is 8.7%, so the player's own loop was spending most of the slack a branch point saves up. Prefill is the weaker lever: six records of it still leaves 24 underruns. Three silent bugs are recorded in FINDINGS 55.7 -- all produced wrong pixels or a desync rather than a fault -- plus a rig one: MAME renders a screen line by line, so snapshotting the frame the decoder finished in captures a tear that reads exactly like a decoder bug. check.sh gains the machine-owned ring and a seek with the decode after it. decode.bin is unchanged at 1,296 B and a host-filled run executes none of the new code, so every FINDINGS 49/51 figure stands. ALL GREEN before and after. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
+106
@@ -1,3 +1,109 @@
|
||||
# Status & next-session handoff — end of session 23 (2026-08-24)
|
||||
|
||||
## Session 23: the 68000 fills its own ring, and the player's request loop turns out to cost more than the medium does
|
||||
|
||||
**Green light first and last: `./tools/bench/check.sh` was ALL GREEN before any
|
||||
of this and ALL GREEN after**, 120/120 on both cores, no `TRUNCATED`, plus two
|
||||
new ring stages.
|
||||
|
||||
**ROADMAP P5 is DONE. FINDINGS 55.** P5 was the last M2 item buildable in this
|
||||
tree, and it is the third and last policy to move off the host: the loader went
|
||||
in session 21, the frame clock in 22, and the ring producer now.
|
||||
|
||||
**1. The container had to change, and it is the first format change since
|
||||
session 12.** `aligned` asks whether the next record fits before the end of the
|
||||
ring — a question about a record's length asked **before it is fetched** — and
|
||||
every reader in this tree answered it by WALKING the frame stream, which is
|
||||
exactly what a player streaming off a disc cannot do. **DLX4** carries
|
||||
`nframes` u16 record lengths in the scene header. The frame payloads are
|
||||
**byte-identical** to the DLX3 encode, asserted record for record, so no
|
||||
constant fitted to the gate container moves; the scene header goes 5,920 →
|
||||
**6,164 B**. `dlx.py` refuses a container whose index disagrees with its own
|
||||
walk. 55.1.
|
||||
|
||||
**2. `src/player/ring.i` reproduces the host producer exactly.** Same 18 wraps,
|
||||
same 14.7 KB mean hole, pixel-exact — a third independent implementation of
|
||||
`aligned` landing on the same tiling (55.2). The host now **audits** every
|
||||
placement instead of making it.
|
||||
|
||||
**3. THE ONE THAT MOVES SOMETHING: the disc stands still whenever the player is
|
||||
not asking.** A channel only moves bytes while it has a request and only the CPU
|
||||
can issue one, so there is a gap between every pair of records that is a
|
||||
property of the **player's loop, not the medium** — and no host-filled run could
|
||||
see it, so no rate table in this tree contains it. At 488 KB/s in a 256 KB ring,
|
||||
changing nothing but how many requests the player may have outstanding:
|
||||
|
||||
| queue | channel idle | underruns | slack ceiling | bound by |
|
||||
|---:|---:|---:|---:|---|
|
||||
| 1 request | **6.8%** | **59/120** | 2 | rate |
|
||||
| 2 requests | **3.4%** | **0/120** | 5 | ring |
|
||||
|
||||
This container's surplus over the wire at 488 KB/s is 8.7% of the pipe, and a
|
||||
one-deep request loop spends 6.8% of it on nothing — **most of the surplus
|
||||
51.3's lookahead is accumulated out of**. A second slot costs no clocks and the
|
||||
hardware has four channels (52.1). 55.3.
|
||||
|
||||
**4. Prefill is the weaker lever, and now it has a number.** Six records of
|
||||
prefill — half a second of black at every scene start — still leaves 24
|
||||
underruns at a one-deep queue; a second queue slot leaves none. **A prefill buys
|
||||
a one-off cushion a rate-bound pipe spends immediately; a queued request buys
|
||||
the rate back every frame.** The shipped policy is 2 records, and it is not 1
|
||||
because 51.2 measured that *n* resident records buy *n-1* frame times. 55.4.
|
||||
|
||||
**5. The slack rule and a seek are in the player.** `ring_may_seek` is 51.2's
|
||||
rule as arithmetic the player runs; `ring_seek` quiets the channel, takes record
|
||||
*j*'s disc address out of the index and empties the ring. Rehearsed as a second
|
||||
pass: 240 records, seek at 12.87 s, refill from empty, **0 underruns after it
|
||||
and the last frame of the second pass pixel-exact**. The seek shows up as the
|
||||
run's worst channel gap, **397.5 ms** — disc idle, not mechanical seek, which is
|
||||
still unmodelled. 55.5.
|
||||
|
||||
**6. Three silent bugs and one instrument correction**, all written up in 55.7,
|
||||
because every one of them produced wrong pixels or a desync rather than a fault:
|
||||
the read cursor's wrap rule not matching the writer's; the free-space test
|
||||
deciding a wrap before it knew whether the LIVE span was the wrapping one; the
|
||||
request queue gated on completion instead of retirement (only reachable above
|
||||
depth 1). The fourth is the rig's: MAME renders a screen line by line, so
|
||||
snapshotting the frame in which the decoder finished captures a **tear** — it
|
||||
looked exactly like a decoder bug and was not. The rig now settles one frame
|
||||
before capturing. No previously reported result is affected.
|
||||
|
||||
**New in the tree:** `src/player/ring.i` (the producer, the prefill, the slack
|
||||
rule and the seek); `tools/analysis/24_ring_owner.py` (an independent model of
|
||||
all of it, sharing no code with the rig). `encode.py`/`dlx.py` gain DLX4 and its
|
||||
index cross-check; `prep_stream.py` emits the index and checks it against the
|
||||
disk image; `load.i` accepts DLX3 or DLX4. `stream.s` gains the ring hooks and a
|
||||
polling wait loop; `stream.lua` becomes a **transport** built out of memory taps
|
||||
(exact issue and completion times — see 55.7 for why nothing in a tap may touch
|
||||
the memory space); `pace_run.sh` gains `DLX_RINGOWN`, `DLX_QDEPTH`,
|
||||
`DLX_PREFILL_FR`, `DLX_ITER`. `check.sh` gains two stages: the machine-owned
|
||||
ring, and a seek with the decode after it.
|
||||
|
||||
**`decode.s` and `frame.i` are unchanged**, `decode.bin` still 1,296 B at the
|
||||
same MD5, and a host-filled run executes none of the new code — so every
|
||||
FINDINGS 49/51 figure stands.
|
||||
|
||||
**Still open in P2:** unchanged — the encoder does not reserve a black entry
|
||||
(23.4).
|
||||
|
||||
**Next:** M2 has no item left that this tree can build. **P4** (drive the
|
||||
MB89352, settle `W`) still decides the project and still needs hardware or a
|
||||
MAME that models the SPC; 55.3 sharpens what to ask of it, because the ladder
|
||||
now has a second axis — the clocks stolen per byte AND the fraction of the pipe
|
||||
the player's own loop gives away. **G1** (import the scene graph) is what would
|
||||
let this tree ask the question 55.5 rehearsed but could not pose: what is the
|
||||
worst gap between two real decision points, and does the refill climb survive
|
||||
it. **P7** (boot from the volume) is buildable but empty until P4.
|
||||
|
||||
**A question 55.3 raises and does not answer:** the encoder is fitted to a pipe
|
||||
that delivers continuously. It does not, and by 3-7% depending on the player's
|
||||
queue. Whether the rate point should be set against the *delivered* rate rather
|
||||
than the nominal one is the same class of change as the reserved black entry and
|
||||
the short-slot question from 54.4 — a re-encode plus a re-measurement, and all
|
||||
three should be decided together.
|
||||
|
||||
---
|
||||
|
||||
# Status & next-session handoff — end of session 22 (2026-08-24)
|
||||
|
||||
## Session 22: the frame clock moves onto the 68000, and the 12 fps frame turns out not to exist
|
||||
|
||||
Reference in New Issue
Block a user