Skip to content

D38: recover an AKAI sample displaced by blocks lost inside a partition (#35) - #81

Merged
bmxcode merged 1 commit into
mainfrom
d38-akai-in-partition-recovery
Sep 5, 2026
Merged

bmxcode merged 1 commit into
mainfrom
d38-akai-in-partition-recovery

Conversation

@bmxcode

@bmxcode bmxcode commented Sep 5, 2026

Copy link
Copy Markdown
Owner

D38: recover an AKAI sample displaced by blocks lost inside a partition (#35)

What this does

An AKAI rip can drop a run of 32 KB container blocks from inside a partition rather than from the blocks a partition header sits on. No header goes missing, so the partition table's declared == present arithmetic reads clean and the disc looks whole — while a tail run of one volume's samples has slid forward and now decodes as somebody else's audio. ADR-0027 refused these (104 samples across nine discs) and named them; ADR-0028 recovered the sibling case (a displaced partition header) but deferred this one.

D38 recovers it. When a sample's payload at its declared position is not the file its entry placed, the real bytes are searched for backward from the declared position, in the container's storage unit, for a header restating this entry's own name and word count (size == words*2 + header_len), floored at the start of the file's own partition. Found, the file is read from there; not found, it stays refused and named exactly as before.

102 of the 104 recover. Alpha Dance II's AC.DRUMLOOPS goes from 1 of 22 written to 22 of 22. The collection's refused-mismatch count falls from 104 to 2.

Why this is not blind recovery

Issue #35 asked for three measurements first; they are what make this buildable.

  • The displacement is always a whole number of container blocks (32 768 B for these .mdx images) — one or two blocks on most, 134 on Library.3's namesake.
  • The allocation map does not notice, so there is no cheaper detector. The map is in the intact partition header and describes the disk the rip was made from: every displaced file's chain is still exactly as long as its declared size demands. The payload-header check (ADR-0027) is the only structure that sees the gap. AKAI: a rip can lose a run of blocks inside a partition, and the table sees nothing #35 hoped a short chain would be a cheaper check; it is not.
  • Exactly one position per recovered file passes the anchor across the whole collection, and none coincides with any other intact entry's bytes. The anchor is ADR-0027's name test — "which file is this?" — which had zero positives on real data until now; it is the load-bearing confirmation here, backed by the word-count identity as an independent structure.

The safety is the partition floor, and Library.3 is the standing proof: its one refused record's only backward match is a different volume's 20 CHINA2-R in the previous partition — same name, same size, intact where it sits. Its bytes belong to that file; the floor refuses to leave the partition, so the record stays refused rather than recovered wrongly. This is exactly the failure ADR-0027 exists to prevent, caught on real data.

What still is not recovered (named, not hidden)

  • AMG - Loop Soup's one directory record whose start block lands mid-sample — no header exists earlier to find.
  • AKAI.S3000.Sound.Library.3's 20 CHINA2-R — the cross-partition namesake above.

Displaced programs are not relocated: a program's payload carries no name to confirm a relocation by, so the anchor that makes this safe does not exist for them.

Shape

  • sample/akai.py: is_placed_here(payload, name, size, s3000) — the anchor, read from the header alone; reused by parse.
  • fs/akai.py: displaced_sample() (the backward, floored search) and AkaiBackend.placement() (declared position + 0 on the fast path, the recovered offset + distance otherwise). read_file routes samples through it, so the WAV path, --keep-originals and the stereo joiner all get the real bytes.
  • fs/base.py: optional placement hook on Backend (AKAI is the only implementer).
  • extract.py/cli.py/batch.py: Extracted.displaced per file (the displacement grows down a volume rather than being one constant), a recovered summary line, and a manifest recovered total.

Verification

  • ruff check / ruff format --check / pytest -q / editable install + --version all green.
  • Disc tests pin the recovery per disc (_AKAI_PAYLOAD and _AKAI_SHORT gain a recovered column), assert a recovered WAV's PCM is the disc's own bytes from the displaced offset, and keep the collection-wide invariant no sample is written whose payload header names a different file — now holding non-trivially over 102 real recoveries. Synthetic unit tests cover the fast path, a not-found residual, the partition floor (the Library.3 shape), and that programs are not relocated.

Corrects ADR-0028's loose "103 found intact" figure: one of those was the Library.3 cross-partition namesake, which is not a safe recovery — the safe count is 102.

Closes #35.

🤖 Generated with Claude Code

…on (#35)

An AKAI rip can drop a run of 32 KB container blocks from *inside* a partition
rather than from the blocks a partition header sits on. No header goes missing,
so the partition table's `declared == present` arithmetic reads clean and the
disc looks whole -- while a tail run of one volume's samples has slid forward
and now decodes as somebody else's audio. ADR-0027 refused these (104 samples
across nine discs) and named them; ADR-0028 recovered the sibling case (a
displaced *partition* header) but deferred this one.

D38 recovers it. When a sample's payload at its declared position is not the
file its entry placed, the real bytes are searched for backward from the
declared position, in the container's storage unit, for a header restating this
entry's own name and word count (size == words*2 + header_len), floored at the
start of the file's own partition. Found, the file is read from there; not
found, it stays refused and named exactly as before.

102 of the 104 recover. Alpha Dance II's AC.DRUMLOOPS goes from 1 of 22 written
to 22 of 22. The collection's refused-mismatch count falls from 104 to 2.

Not blind recovery -- the three measurements #35 asked for are what make it
buildable:

- The displacement is always a whole number of container blocks.
- The allocation-map chain does not notice the gap: it is in the intact header
  and describes the disk the rip was made from, so every displaced file's chain
  still matches its declared size. There is no cheaper detector; the payload
  check is the only structure that sees this.
- The name+word-count anchor matches exactly one position per recovered file
  across the collection and never coincides with another intact entry's bytes.
  The anchor is ADR-0027's name test -- "which file is this?" -- which had zero
  positives on real data until now.

The safety is the partition floor. Library.3 is the standing proof: its one
refused record's only backward match is a different volume's 20  CHINA2-R in the
previous partition -- same name, same size, intact where it sits. Its bytes
belong to that file; the floor refuses to leave the partition, so the record
stays refused rather than recovered wrongly. This corrects ADR-0028's loose
"103 found intact" figure to 102.

The two that stay refused are named, not written: Loop Soup's mid-sample record
and Library.3's cross-partition namesake. Displaced programs are not relocated:
a program's payload carries no name to confirm a relocation by.

Shape: is_placed_here (sample/akai.py) is the anchor; displaced_sample and
AkaiBackend.placement (fs/akai.py) do the floored backward search, and read_file
routes samples through it so the WAV path, --keep-originals and the stereo
joiner all get the real bytes; an optional placement hook on Backend
(fs/base.py); Extracted.displaced per file plus a recovered summary line and
manifest total (extract.py/cli.py/batch.py). New ADR-0045; docs/formats/akai-fs.md,
docs/README.md, CHANGELOG.md and the ADR index updated.

Closes #35.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bmxcode
bmxcode merged commit 14aec5c into main Sep 5, 2026
2 checks passed
@bmxcode
bmxcode deleted the d38-akai-in-partition-recovery branch September 5, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AKAI: a rip can lose a run of blocks inside a partition, and the table sees nothing

1 participant