re(ui): the 60 nameless RATC children are frames, not children -- .tan decoded

Closes the reach caveat the `opt ` name fix left behind: 60 of 18 002 RATC
children carry no `opt ` block, and it was not established whether they lack one
or sit past our 128-byte window.

Neither. They are not children. `examples/ratc_optless_children.rs` re-runs
`ratc::parse`'s own guards over the disc and reports which one fired: all 60 are
"tag beyond the window", none is rejected by length, gap or charset, none is
child #0, and all 60 live in six bundles of one archive. Within a bundle the
distances back to the nearest tag are an exact arithmetic progression, step
60 600 -- ten different records finding the SAME tag, because there is only one.

Reading a bundle directly: children 1..10 are equal-size T8aD blocks under a
single `opt ` name, `pb_f15_eg_anm.tan`. `.tan` is a FRAME SEQUENCE. One block
declares the resource; its payload is a run of T8aD frames.

Disc-wide, over all 18 718 `opt ` names in all 33 paks: a RATC bundle names
exactly six kinds of resource -- `.t32` 14 756, `.rat` 3 311, `.prm` 367,
`.tbm` 224, `.sbo` 54, `.tan` 6. Six `.tan`, ten frames each = 60, the entire
population with nothing left over. The negative is closed, not narrowed.

Consequence recorded but deliberately not fixed: `ratc::parse` over-reports
there, listing a `.tan`'s frames as anonymous children. Nothing in the menu
milestone reads a `.tan` -- it occurs only in GP_READY_ROOM, which S1 ruled
out -- so no screen the port draws changes.

Also a METHOD entry for this container OOM-killing `slb_leading_segment_disc`
under default test parallelism (SIGKILL, no assertion; 8/8 pass with
--test-threads=1).
This commit is contained in:
Sylpheed RE agent
2026-08-29 07:39:09 +00:00
parent 56cc7acfc3
commit f817dd5939
9 changed files with 414 additions and 5 deletions

View File

@@ -85,9 +85,13 @@ five menu screens.
reproduces every one of them. A reading that fixed the 24 but disturbed the rest
would be a different rule, not this one.
🟡 **Reach.** 60 children (0.3 %) have **no** `opt ` block within 128 bytes and
still fall back to the scan. None of them is on the five menu screens. Whether
they genuinely lack the block or sit past the search window is not established.
**Reach — closed 2026-08-29.** 60 children (0.3 %) have **no** `opt ` block
within 128 bytes and fall back to the scan. **They are not children.** They are
the ten frames of the disc's only `.tan` resource, `pb_f15_eg_anm.tan`, in the
six language copies of one `GP_READY_ROOM` bundle — 6 × 10 = 60, the whole
population with nothing left over. One `opt ` block names the whole run, which is
why nine of the ten find no block of their own. None is on the five menu screens.
[`ratc-tan-frame-sequence.md`](ratc-tan-frame-sequence.md)
## What it changes in the composite

View File

@@ -0,0 +1,120 @@
# `.tan` — one name over a run of frames, and the 60 "nameless" children it explains
**Status:**`DECODED`, with a disc-wide check. This closes the 🟡 reach caveat
left open by [`ratc-child-names.md`](ratc-child-names.md): *"60 children have no
`opt ` block within 128 bytes and still fall back to the scan — whether they
genuinely lack the block or sit past the search window is not established."*
**Neither.** They are not children. They are the **ten frames of a single `.tan`
resource**, and the one `opt ` block that names the whole run sits up to 545 KB
behind the last of them.
## What was measured
[`examples/ratc_optless_children.rs`](../../../crates/sylpheed-formats/examples/ratc_optless_children.rs)
re-runs `ratc::parse`'s own guards over every child on the disc and reports, for
each rejection, *which* guard fired and whether a tag exists further back
([data](../data/ratc-tan-frame-sequence.txt)):
| | |
|---|---|
| RATC children scanned | 18 002 |
| with no accepted `opt ` block | **60** |
| of those, rejected by **length**, **gap** or **charset** | **0** |
| of those, rejected because the only tag is **beyond the 128-byte window** | **60** |
| of those, that are child #0 of their bundle | **0** |
| archives involved | **1**`GP_READY_ROOM.pak` |
| bundles involved | **6** — entries 26, 30, 159, 160, 1029, 1050, each exactly 706 609 B |
| children involved | **1…10 of each**, never 0 and never 11+ |
The distances are the tell. Within one bundle they are
```text
213, 60 813, 121 413, 182 013, 242 613, 303 213, 363 813, 424 413, 485 013, 545 613
```
— an exact arithmetic progression, step **60 600**. Ten different children all
find the **same** `opt ` tag, because there is only one. Nine of them do not have
a block that is merely far away; they have no block.
## What they are
Reading bundle 26 directly, without the Rust parser, the 15 "children" resolve:
| # | offset | kind | size | `opt ` at | name |
|---|---|---|---|---|---|
| 0 | `0x000444` | T8aD | 1 933 | 40 | `pbf15_energie_generator2.t32` |
| **1…9** | `0x000bd1` … | T8aD | **60 600** each | 213 … 485 013 | **`pb_f15_eg_anm.tan`** |
| **10** | `0x085e49` | T8aD | **60 632** | 545 613 | **`pb_f15_eg_anm.tan`** |
| 11 | `0x094b21` | T8aD | 17 523 | 32 | `pbf15_pd_inside2.t32` |
| 12 | `0x098f94` | T8aD | 75 068 | 35 | `pbenergie_generator.t32` |
| 13 | `0x0ab4d0` | T8aD | 4 697 | 28 | `pbf15_eg_eff.t32` |
| 14 | `0x0ac729` | RATC | 264 | 25 | `pb_s15_eg.rat` |
So the format is doing something perfectly ordinary that our scan had no concept
of: **`.tan` is a frame sequence.** One `opt ` block declares the resource, and
its payload is a run of equal-size `T8aD` blocks, one per frame. The name is on
the disc and always was. What was missing was the idea that one name can cover
more than one block.
`anm` in `pb_f15_eg_anm` is the authors' own abbreviation, and it agrees.
## The disc-wide check
Every `opt ` block in every RATC bundle in all 33 `dat/*.pak`, by the extension
it names
([`tools/re-capture/ratc_opt_name_census.py`](../../../tools/re-capture/ratc_opt_name_census.py)):
| extension | count | what it is |
|---|---|---|
| `.t32` | 14 756 | a `T8aD` sprite |
| `.rat` | 3 311 | a nested RATC leaf |
| `.prm` | 367 | a primitive |
| `.tbm` | 224 | — |
| `.sbo` | 54 | — |
| **`.tan`** | **6** | **a frame sequence** |
| | **18 718** | |
A RATC bundle names exactly six kinds of resource, and **`.tan` occurs six times
on the whole disc** — all of them `pb_f15_eg_anm.tan`, one per language copy of
the same bundle, each holding **10 frames**.
**6 × 10 = 60.** That is the entire population of opt-less children, with nothing
left over. The negative is closed, not narrowed.
## ⚠️ What this says about `ratc::parse`
The child list **over-reports**. `parse` finds children by scanning for the four
child magics, so a `.tan`'s ten frames are listed as ten anonymous children of the
bundle rather than as one named resource with ten frames. The disc's "18 002
children" is therefore 18 002 *magic-delimited blocks*, of which 60 are frames.
**Not changed here**, deliberately: nothing in the menu milestone reads a `.tan`,
and a rewrite of the child model is a bigger change than the one fact it would
buy. Recorded so that a later consumer of `.tan` knows the shape it needs.
## ❔ Not established
* **The frame timing.** Ten frames of the same size is a sequence; nothing here
shows the rate, whether it loops, or whether the frames are equal-duration. No
field was looked for.
* **The pixel layout of a 60 600-byte frame.** They decode as `T8aD` like any
other sprite as far as the magic goes; their dimensions were not read.
* **What `.tbm` and `.sbo` are.** They surfaced from the same census and are
recorded above as counts only.
* **The two `opt ` totals do not reconcile exactly** and are not forced to.
This census counts **18 718** blocks; the Rust audit in
[`ratc-child-names.md`](ratc-child-names.md) counts **17 942** children *with* a
block. They apply different guards — the Rust one additionally requires the
named thing to be one of the four child magics and to follow within 8 bytes,
which `.prm` / `.tbm` / `.sbo` (645 blocks) never satisfy. That accounts for
most of the 776 difference but not all of it, and the remainder was not chased.
Each number is reported as what its own script measured.
## Scope
`GP_READY_ROOM.pak` is **out of scope** for the menu milestone ([S1 is a
no-go](../ready-room-probe.md)), and `.tan` occurs in no other archive. **None of
the five menu screens contains a `.tan`**, so nothing the port draws changes.
This closes a caveat on a decode the port *does* depend on, rather than adding a
capability.