Files
Sylpheed/docs/re/structures/ratc-tan-frame-sequence.md
Sylpheed RE agent f817dd5939 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

5.6 KiB
Raw Blame History

.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: "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 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):

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 1GP_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

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):

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 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), 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.