Put sound on the wire, and find three LSBs are worth 25 dB

ROADMAP P6, everything in the item except the bus half session 20 closed.
tools/encoder/adpcm.py is an MSM6258 codec, tools/encoder/extract_audio.py
takes the same seconds of the same stream the frames come from,
tools/bench/verify_adpcm.py is the gate, tools/analysis/32_audio_wire.py the
container arithmetic.

There is no reference encoder -- ffmpeg has a decoder for this format and none
the other way -- so what is gated is the decoder the encoder runs INSIDE its
own nibble search, sample-exact against ffmpeg's over 4,268 nibbles. An
encoder that agrees with its own wrong decoder is what that catches. The Singe
window: 156,250 samples -> 78,125 B at 21.97 dB, which is 7,812.5 B/s to the
byte. Normalising the disc's -13.4 dBFS level moves the SNR 21.97 -> 21.97, so
the level is not a lever.

And the two published delta formulas are not the same codec. They differ by at
most 3 in 12-bit units; encode for one and decode on the other and the SNR
goes 21.97 -> -2.88 dB, the noise louder than the signal, because ADPCM is
recursive the way the video codec is temporally recursive. Which one the chip
runs is now P6a and it is a precondition on shipping any audio.

And audio is the first thing the packed branch's simplification has cost
anything for. A record has no index BY DESIGN, so audio cannot be per-record
without making records variable; it rides a fixed cadence (F, A), the obvious
F=1 wastes 57.3% of every audio sector, and the pick is F=11 A=14 -- 0.09%
padding, 14,336 B held, wire 582.0 -> 589.6 KB/s. The codec container, which
kept its index, pays zero.

The MAME experiment did not work and 65.5 says so: :okim6258 is there at
$E92001/$E92003, read out of the machine's own program map, and feeding it
from Lua recorded silence across control 0..3 x port C 0..15. The register
semantics were not guessed at further.

FINDINGS 65. check.sh ALL GREEN before and after, with a new stage.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-25 09:11:03 -07:00
parent 6f698ca226
commit f925a1dd9a
12 changed files with 1069 additions and 6 deletions
+181
View File
@@ -6768,3 +6768,184 @@ throughout) — and no MAME source tree was available on this machine to name it
third** that decides whether a DMAC-direct packed player shows a picture.
- **Whether B is buildable as described.** It is priced off a measured blit and
a measured ladder, and no line of it has been written.
---
## 65. Audio has an encoder, and the packed container's best property is what makes it cost (session 33)
**ROADMAP P6**, everything in the item except the bus half session 20 closed.
`tools/encoder/adpcm.py` is the codec, `tools/encoder/extract_audio.py` the
extraction, `tools/bench/verify_adpcm.py` the gate, `tools/analysis/32_audio_wire.py`
the container arithmetic. All of it is **host arithmetic and one emulator
introspection**; no board ran, and the one MAME experiment that was attempted
did not work — 65.5 says so rather than leaving it out.
### 65.1 There is no reference encoder, so the gate had to be built sideways
The X68000's ADPCM is an OKI MSM6258V: 4 bits a sample, two to a byte, 12-bit
signal word, and three rates that are 8 MHz over 512, 768 and 1024. **ffmpeg has
a decoder for the format (`adpcm_ima_oki`) and no encoder**, so there is nothing
to diff an encoder against.
What `verify_adpcm.py` gates instead is the thing that can actually be wrong:
the encoder runs a decoder **inside its own loop** to choose each nibble, and
that decoder is checked **sample-exact against ffmpeg's** over 4,268 nibbles.
An encoder that agrees with its own wrong decoder is exactly the failure mode a
round-trip test cannot see.
Two facts fell out of building it, and both were **measured rather than assumed**:
| | |
|---|---|
| nibble order | **HIGH NIBBLE FIRST** — reading low-first disagrees with ffmpeg on 3,285 of 4,268 samples, and that mismatch is carried as the gate's negative control |
| the step table | 49 entries, **BUILT** as `floor(16 * 1.1**k)` and checked against the published list, so a transcription slip is not one of the things that can be wrong |
On the window this project gates everything on (00223 @539.4 s, 10.000 s, the
same seconds as `tmp/fr_singe`): **156,250 samples → 78,125 B, SNR 21.97 dB**,
and 78,125 B / 10.000 s is **7,812.5 B/s to the byte**, which is 52's figure
arriving from the other direction.
**A negative worth having: the level is not a lever.** The disc's window peaks
at 13.4 dBFS, using 435 of the 12-bit word's 2,048. Normalising it — gain ×4.7,
one sample clipped — moves the SNR from **21.97 dB to 21.97 dB**. The step
table's adaptation covers the range, so there is no headroom win to collect and
no reason to touch the disc's level.
### 65.2 The two available references DISAGREE, and it costs 25 dB
The delta a nibble contributes has two forms in circulation:
'shift' delta = ((2*(n&7) + 1) * step) >> 3
'terms' delta = step/8 + (n&4)*step + (n&2)*step/2 + (n&1)*step/4,
each term truncated independently
`'shift'` is what ffmpeg computes — **verified sample-exact here, so that is a
measurement of the decoder that ships, not a reading of its source.** `'terms'`
is the OKI datasheet's own form, the one a chip builds out of shifts and adds,
and it is what MAME's `okim6258` is understood to compute. **That last clause is
NOT verified**: no MAME source tree is on this machine (64.4).
They differ **on 1,000 of 4,268 sampled nibbles, by at most 3 in 12-bit units**,
which reads like something nobody could hear. **It is not.**
| encoded | decoded | SNR |
|---|---|---:|
| `shift` | `shift` | **21.97 dB** |
| `shift` | `terms` | **2.88 dB** |
| `terms` | `terms` | 21.99 dB |
| `terms` | `shift` | 3.38 dB |
**The noise is louder than the signal.** Per-sample disagreement over the real
window: **max 257, mean 78.2**, against a source whose RMS is 74.4. A 3-LSB
formula difference becomes a 25 dB loss because **ADPCM is RECURSIVE** — the
delta is added to a running predictor and the nibble also moves the step index,
so a disagreement does not stay where it happens. It is the same shape as the
codec's temporal recursion, one dimension down: 64.1 used that recursion to make
one frame audit 120, and here the same property turns a rounding difference into
a broken stream.
**So "which formula does the MSM6258 run" is not a footnote. It is a
precondition on shipping any audio at all**, and it has to be answered before an
encoder's output is committed to a container.
### 65.3 The interleave, and why the obvious cadence is the wrong one
**A packed record is 49,664 B = 97 sectors and its address is `LBA0 + i*97`.
There is no index and none can be needed** — that is the format's whole claim
(63, 64.1). Audio is a stream at a rate with no arithmetic relationship to the
frame rate: at 15,625 Hz a 12 fps slot is **651.0417 B**, and the `.0417` is the
same remainder the frame clock carries (54), because 8 MHz / 512 / 2 / 12 has a
3 in the denominator that no power of two clears.
Give record *i* the audio belonging to slot *i* and the records become variable
length — and the moment records are variable length the format needs an index
and stops being the format. So audio rides a **fixed cadence**: every `F`
frames, `A` whole sectors, placed between records, leaving
LBA(i) = LBA0 + i*97 + floor(i/F)*A
which is still arithmetic. Choosing `(F, A)` is a rational approximation to
`15625/12288 = 1.271565755` from above, and **the obvious cadence is the worst
point in the space**:
| F | A | lump | needs | padding | wire adds | held (2 lumps) |
|---:|---:|---:|---:|---:|---:|---:|
| **1** — one lump a record | 2 | 1,024 B | 651.0 B | **57.29%** | **12.00 KB/s** | 2,048 B |
| 3 | 4 | 2,048 B | 1,953.1 B | 4.86% | 8.00 KB/s | 4,096 B |
| 7 | 9 | 4,608 B | 4,557.3 B | 1.11% | 7.71 KB/s | 9,216 B |
| **11** | **14** | **7,168 B** | 7,161.5 B | **0.09%** | **7.64 KB/s** | **14,336 B** |
| 81 | 103 | 52,736 B | 52,734.4 B | 0.003% | 7.63 KB/s | 105,472 B |
**F=11 is the pick.** It buys 57.2 points of padding for 12,288 B of RAM over
the naive cadence; the floor of the sweep buys the last 0.09 of a point for
91,136 B more, and on a machine where K4 already wants 99,328 B for two record
buffers that second trade is not one.
**The wire, then:** the packed container's sustained requirement was **582.0 KB/s
silent** and is **589.6 KB/s with sound** (+1.31%). A literal frame's bitrate is
geometry and cannot be talked down; the audio on top of it is 7.63 KB/s of
payload and can only be talked down by choosing a worse chip rate.
### 65.4 And this is the first price anyone has found for the packed branch's own simplification
The codec container **pays none of it**. `rc_fr_singe_scsi_span.dlx` already
carries an index and already has variable records (4,096..41,472 B, sector-aligned
since DLX5), so it can put exactly 651.0417 B of audio in record *i* and pad only
to the sector it was going to pad to anyway: **zero audio padding**, wire
440.4 → 448.1 KB/s.
"A record's length is geometry, so there is no index and none can be needed" is
what makes the packed player a page of arithmetic instead of a parser — and it
is **exactly** the property that makes a second stream at an unrelated rate cost
a cadence, a padding fraction and a 14,336 B buffer. It is a small cost and it
is not zero, and **nothing in FINDINGS 61-64 predicted it**. 64's risk list said
"a simplification that large usually hides something"; this is the first thing
it hid.
The bus half reproduces 52 exactly, which is why the tool prints it: 651.0 B a
slot at the IPL ROM's own channel-3 cost of 16..19 clk/B is **10,417..12,370
clocks = 1.25%..1.48% of a slot**. **The interaction 52 could not have had** is
with 64.2's write window: a DMAC-direct packed player holds the GVRAM window
open for the whole data phase, so an audio channel stealing the bus during that
phase makes the phase longer — audio costs **darkness**, not just clocks. It is
negligible against a dark fraction that is already 1.0, and it is not negligible
against K4's 27.3% paint. That is the third time this session the two players
have ranked differently on a column that is not clocks.
### 65.5 The experiment that did not work, stated rather than omitted
65.2's open question has an obvious apparatus: **MAME's x68000 has the chip**,
so it can be asked. Introspection got as far as fact and no further, and the
facts are worth keeping because the next attempt starts from them rather than
from folklore:
| | |
|---|---|
| device | **`:okim6258`**, shortname `okim6258` — it is there |
| registers | **`$E92001`** and **`$E92003`**, each one byte, read out of the maincpu program map by `tools/bench/probe_adpcm.lua` |
| also on the map | `$E9A000-$E9BFFF`, the PPI that carries ADPCM pan and the clock divider |
**Feeding the chip from Lua produced no audio.** `tools/bench/probe_adpcm2.lua`
writes a control byte to `$E92001` and nibble pairs to `$E92003`;
`probe_adpcm3.lua` sweeps PPI port C at `$E9A005` over all sixteen low-nibble
values with a loud burst under each. **Control 0..3 × port C 0..15: MAME's
`-wavwrite` capture is silent throughout, 0 of 567,360 samples non-zero.**
The register *semantics* are the gap — which control value starts playback,
whether port C needs the PPI's mode word set first, and whether the chip has a
clock at all until the divider is written. **None of that was guessed at further,
because guessing at it is how a rig produces a confident wrong answer.** The way
to do this is from 68000 code with the IPL ROM's own channel-3 DMAC
configuration, which `tools/analysis/21_iplrom_dmac.py` already reads out of the
ROM — the real design, and the one path in the machine that is known to be
correct because Sharp wrote it.
### 65.6 What this does not settle
- **Which delta formula the chip runs.** 65.2, and it is worth 25 dB.
- **Anything on a board.** No hardware ran. The `-wavwrite` silence is a
statement about an apparatus, not about a chip.
- **The container.** DLXP1 has no audio section; 65.3 is the arithmetic a
DLXP2 would be built from, and no byte of one has been written.
- **What audio does to a scene change.** The slack table is here, but 51.3's
refill climb with a second consumer through a real branch point is not.
+67 -4
View File
@@ -74,6 +74,28 @@ the CPU opens the window only for the measured 27.3% blit, so it is on screen
the clock cannot tell** — 46.9% of V-DISP edges lost, zero late frames reported,
the player believing 12 fps while the screen ran at 6.37. **The open item is now
K4.**
Amended end of session 33: **P6 HAS AN ENCODER AND A PRICE (FINDINGS 65).**
`tools/encoder/adpcm.py` is an MSM6258 codec whose in-loop decoder is gated
SAMPLE-EXACT against ffmpeg's `adpcm_ima_oki` — there is no reference encoder
for this format, so that is the only check available and it is the one that
catches an encoder agreeing with its own wrong decoder. The Singe window encodes
to **78,125 B at 21.97 dB**, which is 7,812.5 B/s to the byte and 52's figure
arriving from the other direction, and **normalising the disc's 13.4 dBFS level
buys 0.00 dB**, so the level is not a lever. Two things came with it. **(1) The
two published delta formulas disagree by at most 3 in 12-bit units and that is
worth 25 dB** — encode with one and decode with the other and the SNR goes from
21.97 dB to **2.88 dB**, because ADPCM is recursive the way the codec is
temporally recursive (64.1). Which one the chip runs is now **P6a** and it is a
precondition on shipping any audio. **(2) The packed container's own best
property is what makes audio cost.** A record has no index BY DESIGN, so audio
cannot be per-record without making records variable; it rides a fixed cadence
`(F, A)`, the obvious cadence F=1 wastes **57.3%** of every audio sector, and the
pick is **F=11, A=14 — 0.09% padding, 14,336 B held, wire 582.0 → 589.6 KB/s**.
The codec container, which already has an index and variable records, pays
**zero** padding. That is the first cost anyone has found for the packed
branch's simplification, and 64's risk list predicted there would be one without
knowing what.
**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
@@ -664,10 +686,46 @@ charging audio the disk's 5. Both worries above resolve:
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.
~~**What is still open in P6 is everything except the bus:** extraction, encode,
container interleave, and what a second stream does to `wire`.~~ **THREE OF THE
FOUR ARE DONE, session 33 — FINDINGS 65.** `tools/encoder/extract_audio.py`
takes the same seconds of the same stream the frames come from;
`tools/encoder/adpcm.py` encodes them (**78,125 B, 21.97 dB**, gated against
ffmpeg's decoder sample-exact by `tools/bench/verify_adpcm.py`);
`tools/analysis/32_audio_wire.py` is the interleave and the wire. **The packed
container's cadence is `F=11, A=14`** — 0.09% padding, 14,336 B held, 582.0 →
**589.6 KB/s** — and the naive one-lump-per-record cadence would have wasted
57.3% of every audio sector. **The codec container pays zero padding**, because
it already has the index the packed one deleted (65.4).
**What is left in P6 is the fourth: the refill climb with a second consumer
through a real branch point** (51.3, 55.4). The slack table is in
`32_audio_wire.py` — at 582.0 KB/s exactly, silent breaks even and sounded
starves — but a branch point has not been run with audio on the wire.
**P6a. WHICH DELTA FORMULA DOES THE MSM6258 RUN? (new, session 33, FINDINGS
65.2 — and it is a precondition, not a refinement.)** ffmpeg computes
`((2*(n&7)+1) * step) >> 3`; the OKI datasheet's form truncates per term. They
differ by **at most 3 in 12-bit units per sample** and, because ADPCM is
recursive, **encoding for one and decoding on the other costs 25 dB — the noise
comes out louder than the signal.** No encoder output can be committed to a
container before this is answered.
**It does not need a board.** MAME's x68000 HAS the chip: `:okim6258`, with the
68000 reaching it at **`$E92001` and `$E92003`** — both read out of the machine's
own program map by `tools/bench/probe_adpcm.lua`, not from folklore. What did
NOT work is feeding it from Lua: `probe_adpcm2.lua` / `probe_adpcm3.lua` swept
control 0..3 against PPI port C 0..15 and `-wavwrite` recorded silence
throughout (65.5). The gap is the register semantics, and the way to close it is
**from 68000 code with the IPL ROM's own channel-3 DMAC configuration**, which
`21_iplrom_dmac.py` already reads out of the ROM — the one ADPCM path in this
machine that is known-correct because Sharp wrote it. That is also the real
design, so it is not scaffolding.
**P6b. A CONTAINER WITH SOUND IN IT.** DLXP1 has no audio section. 65.3 is the
arithmetic a DLXP2 is built from and no byte of one is written. It waits on P6a,
because a container full of audio encoded against the wrong formula is 25 dB of
work to redo.
**E6. Container v2** — audio interleave, per-record index, scene table. Depends
on P6's answer and on P5's index.
@@ -794,6 +852,11 @@ B2 blanking ─┬─ NOT blanked ─> K3's DMAC-DIRECT player is the one: 54.9%
(64.2). Neither answer kills the branch and each
picks a different player.
B1 BURST rate (NEW, 64.2) ──> which of the two K3/K4 wins, if B2 blanks
P6 audio DONE bar one (65): encoder gated, cadence F=11/A=14, 589.6 KB/s
└─> P6a WHICH DELTA FORMULA? ─┬─ needs no board: MAME has :okim6258
worth 25 dB, so it is a │ at $E92001/$E92003 (65.5)
PRECONDITION not a tweak └─> then P6b, a container with sound in it
```
**Read that top-left branch as the project's live question.** Everything else
+171 -1
View File
@@ -1,4 +1,174 @@
# Status & next-session handoff — end of session 32 (2026-08-25)
# Status & next-session handoff — end of session 33 (2026-08-25)
## Session 33: audio gets an encoder, and the packed container's best property gets a bill
**Green light first and last: `./tools/bench/check.sh` was ALL GREEN before any
of this (`tmp/check_s33_start.log`) and ALL GREEN after** — the same stages, plus
one new one.
**FINDINGS 65. ROADMAP P6 is three-quarters done and has grown two sub-items.**
`tools/encoder/adpcm.py`, `tools/encoder/extract_audio.py`,
`tools/bench/verify_adpcm.py`, `tools/analysis/32_audio_wire.py`, and three
throwaway MAME probes kept because their failure is a finding
(`tools/bench/probe_adpcm*.lua`).
**Everything below is HOST ARITHMETIC plus one MAME introspection.** No board
ran, and the one MAME *experiment* attempted did not work — 65.5 says so.
**0. SESSION 32'S WORK WAS UNCOMMITTED AND ITS STATUS BLOCK HAD NO HANDOFF.**
Both fixed before anything new was written: the tree was re-gated ALL GREEN,
the handoff written, and the whole of session 32 committed as one change.
**1. THE ENCODER, AND THE GATE IT NEEDED INSTEAD.** ffmpeg has a DECODER for
this format (`adpcm_ima_oki`) and **no encoder**, so there is nothing to diff
against. What is gated instead is the decoder the encoder runs **inside its own
nibble search** — sample-exact against ffmpeg's over 4,268 nibbles. An encoder
that agrees with its own wrong decoder is what that catches.
| | |
|---|---|
| the window | 00223 @539.4 s, 10.000 s — **the same seconds as `tmp/fr_singe`** |
| encoded | 156,250 samples → **78,125 B, SNR 21.97 dB** |
| the rate | 78,125 B / 10 s = **7,812.5 B/s to the byte** — 52's figure, from the other end |
| nibble order | **HIGH FIRST, measured** — low-first mismatches ffmpeg on 3,285 of 4,268, and that is the gate's negative control |
| the level | disc peaks at **13.4 dBFS**; normalising ×4.7 moves the SNR **21.97 → 21.97**. Not a lever |
**2. THE HEADLINE, AND IT IS A THREE-LSB DIFFERENCE THAT COSTS 25 dB.** The two
published delta formulas — ffmpeg's `((2*(n&7)+1)*step)>>3` and the OKI
datasheet's per-term truncation — differ **by at most 3 in 12-bit units**.
Encode for one, decode on the other:
| encoded | decoded | SNR |
|---|---|---:|
| `shift` | `shift` | **21.97 dB** |
| `shift` | `terms` | **2.88 dB** |
**The noise comes out louder than the signal** (mean disagreement 78.2 against a
source RMS of 74.4). **ADPCM is recursive**, so a rounding difference does not
stay where it happens — the same property, one dimension down, that let 64.1's
gate audit 120 frames by comparing one. **Which formula the chip runs is
therefore a PRECONDITION on shipping any audio**, and it is ROADMAP P6a.
**3. AND THE PACKED CONTAINER'S BEST PROPERTY IS WHAT MAKES AUDIO COST.** A
packed record is 97 sectors and its address is `LBA0 + i*97` because a literal
frame's length is geometry — **no index, and none can be needed** (63, 64.1).
Audio at 15,625 Hz is **651.0417 B a slot**, and the `.0417` is the same
remainder the frame clock carries (54). Per-record audio makes records variable
length, which needs an index, which ends the format. So audio rides a **fixed
cadence** — every `F` frames, `A` whole sectors — and `(F, A)` is a rational
approximation to 15625/12288 from above:
| F | A | lump | padding | wire adds | held |
|---:|---:|---:|---:|---:|---:|
| **1 — the obvious one** | 2 | 1,024 B | **57.29%** | 12.00 KB/s | 2,048 B |
| **11 — the pick** | **14** | **7,168 B** | **0.09%** | **7.64 KB/s** | **14,336 B** |
| 81 — the floor | 103 | 52,736 B | 0.003% | 7.63 KB/s | 105,472 B |
**Wire: 582.0 KB/s silent → 589.6 KB/s with sound (+1.31%).** F=11 buys 57.2
points of padding for 12,288 B of RAM; the floor buys the last 0.09 of a point
for 91,136 B more, and K4 already wants 99,328 B.
**4. THE CODEC CONTAINER PAYS NONE OF IT.** It already has an index and already
has variable records, so it puts exactly 651.0417 B in record *i* and pads to
the sector it was padding to anyway: **zero audio padding**, 440.4 → 448.1 KB/s.
**That is the first price anyone has found for the packed branch's own
simplification.** 64's risk list said a deletion that large usually hides
something; this is the first thing it hid, and it is small — a cadence, a
padding fraction and 14,336 B.
**5. THE EXPERIMENT THAT DID NOT WORK, and the facts it did leave.** MAME's
x68000 **has the chip**`:okim6258`, reached at **`$E92001` and `$E92003`**,
both read out of the machine's own program map rather than from folklore, with
the PPI at `$E9A000-$E9BFFF`. **Feeding it from Lua produced silence**: control
0..3 against port C 0..15, `-wavwrite` capture **0 of 567,360 samples non-zero**.
The gap is register semantics and it was **not guessed at further**. The way to
close it is from 68000 code with the IPL ROM's own channel-3 configuration,
which `21_iplrom_dmac.py` already reads out of the ROM.
**RISKS IN THIS SESSION'S RESULT, stated rather than left to be found:**
- **The encoder is greedy, not optimal.** Each nibble is chosen by exhaustive
search over all sixteen minimising THIS sample's error; a nibble also moves
the step index, so a locally worse choice can pay later. 21.97 dB is a floor
for this format, not its ceiling, and no lookahead was tried.
- **21.97 dB is against the 12-bit word**, not against the disc's 16-bit PCM,
and the source was already resampled and downmixed to mono by ffmpeg. The
downmix matrix is ffmpeg's default and was not chosen.
- **P6a is open and it is worth 25 dB**, so every byte `adpcm.py` has produced
is provisional.
- **Nothing has played.** No audio has left an emulated machine, let alone a
board.
## HANDOFF — start here
**THE TREE IS ALL GREEN**, session 33's stage included.
### The work, in the order it should be done
**1. P6a — WHICH DELTA FORMULA, and it needs no board.** It is worth **25 dB**
(65.2), so every byte the encoder has produced is provisional until it is
answered, and it is the cheapest open item in the project by a distance. MAME's
x68000 has the chip and this session found where it lives — `:okim6258` at
**`$E92001`/`$E92003`**, PPI at `$E9A000-$E9BFFF`, read out of the machine's own
program map (65.5).
**Do NOT retry the Lua feed.** It was swept — control 0..3 × port C 0..15 —
and recorded silence, and the gap is register semantics that nobody here should
be guessing at. **Do it from 68000 code with the IPL ROM's own channel-3 DMAC
configuration**, which `tools/analysis/21_iplrom_dmac.py` already reads out of
the ROM: dual address, 8-bit port, cycle steal without hold, external request.
That is the one ADPCM path in this machine that is known-correct because Sharp
wrote it, and it is also the real design — so it is not scaffolding, it is P6b's
transport arriving early.
The discriminating stream is already worked out: **twelve loud nibbles to climb
the step index, then every nibble in turn**, whose two reconstructions differ
first at sample 3 (523 against 522). Capture with `-wavwrite` and compare against
`adpcm.decode(nibs, 'shift')` and `adpcm.decode(nibs, 'terms')`. **Watch the
clipping** — the sequence above saturates at 2,047 within six samples, which
destroys discrimination; build one that alternates sign to hold the signal
mid-range while the step index climbs.
**2. THEN P6b — DLXP2, a container with sound in it.** 65.3 is all the
arithmetic: cadence **F=11, A=14**, `LBA(i) = LBA0 + i*97 + floor(i/11)*14`,
14,336 B held, wire 589.6 KB/s. It waits on P6a, because a container full of
audio encoded against the wrong formula is 25 dB of work to redo.
**3. WHAT IS LEFT OF P6 AFTER THAT** is the fourth quarter: 51.3's refill climb
with a second consumer through a real branch point. The slack table is in
`32_audio_wire.py`; nothing has been run.
### What is still BLOCKED, so it is not picked up by mistake
**K4 — the packed player that is on screen — is conditional on B2**, a board
question. If buffer mode does NOT blank, K3's player is already on screen the
whole slot and K4's paint is 27.3% of a frame spent on nothing. **E7, E4 and C1**
are parked (61.8), and **P4a's wiring** is parked with the ring K3 deleted.
**The hardware list is unchanged and is the user's**: B1 (sustained AND the
data-phase BURST rate, 64.2), B2 (blanking — the five-minute half), B3 (`#EXREQ`),
B4 (a byte write to a palette register).
### Risks that are OURS, not hardware
1. **P6a is open and it is worth 25 dB.** Everything in `adpcm.py`'s output is
provisional.
2. **The encoder is greedy.** Exhaustive per-sample search, no lookahead;
21.97 dB is this format's floor here, not its ceiling.
3. **Nothing has played.** No audio has left an emulated machine.
4. **The packed branch's simplification has now cost something once** (65.4).
It was predicted in the abstract and it was small. It may not be the only one.
### Reproducing this session
./tools/bench/check.sh # ALL GREEN
python3 tools/encoder/extract_audio.py 00223 tmp/au_singe.raw 15625 539.4 10.0
python3 tools/bench/verify_adpcm.py tmp/au_singe.raw
python3 tools/analysis/32_audio_wire.py tmp/packed_singe.dlxp
**WHAT IS NEXT.** P6a: ask the chip which formula it runs, from 68000 code.
---
## Session 32: the packed player runs, and the write window turns out to be the frame