Files
Sylpheed/docs/re/structures/ratc-child-names.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

142 lines
6.6 KiB
Markdown
Raw 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.
# A RATC child's name is stated by an `opt ` block, not by the bytes before it
**Status:**`DECODED`, with a disc-wide check. This fixes a decoder defect that
silently dropped the **full-resolution background from all five menu screens**.
## The field
Immediately before nearly every child of a `RATC` bundle — 17 942 of the disc's
18 002 — sits an `opt ` block:
```text
"opt " | BE32 length | name | NUL | 3 bytes | <child magic>
```
Two children of the main menu bundle (`GP_TITLE.pak` entry 8), raw:
```text
opt 00 00 00 0a p t b a s e . t 3 2 00 0e 10 a4 T8aD
opt 00 00 00 0b p t e f f 0 5 . t 3 2 00 38 41 58 T8aD
^^^^^^^^
"8AX"
```
This is the **same `opt ` block** `ui_layout::opt_link` already decodes for a
button's focus link. Nothing new had to be discovered to read it — only noticed.
## The defect it fixes
`ratc::parse` named each child with `name_before`: the last printable run in the
96 bytes before the child's magic. That is a heuristic, and it is *usually* right
`ptbase.t32`'s three trailing bytes are `0e 10 a4`, not printable, so the scan
walks back to the real name.
But `pteff05.t32`'s trailing three bytes are `38 41 58`, which is `"8AX"` in
ASCII. The scan takes those, the child is registered under the name `8AX`, the
element that declares `pteff05.t32` matches nothing in the sprite table, and
`compose` drops it through
```rust
let Some(sprite) = el.sprite.as_ref() else { continue };
```
*before* the arm that records a `missing` sprite. So the screen's background
vanished with **no diagnostic at all**: `screen render` reported "all resolved".
⚠️ `8AX` was carried in `docs/` as though it were a name the game uses — the old
text read "the `T8aD` behind its `opt ` link is registered under the name `8AX`".
It is not a name. It is three bytes of the preceding record's payload.
## The disc-wide check
[`examples/ratc_child_names.rs`](../../../crates/sylpheed-formats/examples/ratc_child_names.rs)
compares the two readings for every RATC child in all 33 `dat/*.pak`
([data](../data/ratc-child-name-audit.txt)):
| | |
|---|---|
| RATC children scanned | **18 002** |
| carrying an `opt ` block | 17 942 |
| scanned name **agrees** with it | **17 918** |
| scanned name **differs** | **24** |
Every one of the 24 is the same failure: a 3-byte printable tail beating a real
name.
| scanned | actual | count | where |
|---|---|---|---|
| `8AX` | `pbbg.t32` | 12 | `GP_READY_ROOM` |
| `8AX` | `pteff05.t32` | 4 | `GP_TITLE` 5, 6, 8, 9 — the menus |
| `8AX` | `pmbase.t32` | 4 | `GP_STAGE_CLEAR` |
| `8AX` | `pteff04.t32` | 2 | `GP_TITLE` 4, 7 — the title |
| `'OX` | `po_keys_win1.t32` | 2 | `GP_OPTIONS` |
⚠️ **What I checked about the 24, precisely.** That the recovered name is the one
the bundle actually wants is verified for the ten `GP_TITLE` and `GP_STAGE_CLEAR`
cases: on the title screens `pteff04.t32`/`pteff05.t32` are *declared elements*
that previously resolved to nothing and now resolve, and `pmbase.t32` is the
target of `GP_STAGE_CLEAR`'s long-standing dangling reference (below). For the 12
`GP_READY_ROOM` (`pbbg.t32`) and 2 `GP_OPTIONS` (`po_keys_win1.t32`) cases I
checked only that no element in those bundles is left unresolved afterwards —
which is consistent with, not proof of, the same story. None of the 14 is on the
five menu screens.
**The control is the 17 918 the heuristic already got right**: the `opt ` reading
reproduces every one of them. A reading that fixed the 24 but disturbed the rest
would be a different rule, not this one.
**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
Resolving the name makes the element resolve, so `compose` now draws it. On all
five port screens ([before/after](../data/ratc-name-fix-render-effect.txt)):
| build | screen | mean brightness | high-frequency detail |
|---|---|---|---|
| 4 | title | 72.77 → 72.81 | **×1.15** |
| 5 | main menu (JP) | 42.73 → 42.74 | **×1.27** |
| 6 | `EXTRAS` (JP) | 44.08 → 44.09 | **×1.27** |
| 8 | main menu | 41.57 → 41.58 | **×1.30** |
| 9 | `EXTRAS` | 42.99 → 43.00 | **×1.29** |
The brightness is unmoved and the detail is up by a quarter — which is exactly
the signature of *the same artwork at twice the resolution* replacing a 2×
upscale, and not of new content appearing. That it *should* be the full-res art
was settled separately and against the running game, in
[`ui-8ax-fullres-background.md`](ui-8ax-fullres-background.md); this page only
supplies the name that lets the renderer find it.
**Nothing is covered.** The background is opaque and full-screen, and on the
menu it paints 4th of 16, so the worry is real. Measured at each element's
resting rect, before vs after: `ptbtn01` sd 59.98 → 60.03, `ptbtn03` 63.40 →
63.45, `ptbtn05` 66.54 → 66.58, `ptframe1` 51.16 → 51.20, `ptmsg` 56.11 → 56.12.
Everything survives; only the two `loop*` elements paint beneath it, and those
are excluded from the default composite anyway.
⚠️ **Both backgrounds are now drawn**`ptbase.t32` upscaled 2×, then the
full-res one opaquely over it. Correct output, wasted fill. The port should draw
only the full-res one, and take its *timing* from `ptbase`'s element, which is
the one carrying the keyframes.
## What is NOT decoded
**No declaration word points at the child.** Before reading the bytes I tested
whether the 60-byte element declaration indexes the T8aD child table. Its unread
words are `+28`, `+36`, `+44` and `+56`; the control is the resolved elements,
whose child index is known. On the main menu, of 13 controls the words reproduce
the child index **1, 0, 0 and 1** times — and both 1s are the trivial index-0
case. There is no pointer; the association is by name, and the name is the `opt `
string. [`decl_word_probe.rs`](../../../crates/sylpheed-formats/examples/decl_word_probe.rs)
✅ Incidental, from the same probe: **`+44` is a button ordinal.** It is `1…5` on
exactly the five `ptbtn0N.rat` elements of the main menu, in order, and `1` on
every other element. Not needed for anything open, and recorded rather than
chased.