Files
Sylpheed/docs/re/structures/ratc-tan-frame-sequence.md
Sylpheed RE agent 1a82ade187 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).
2026-08-29 07:39:09 +00:00

121 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `.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.