From f65387143e7b3c234ea27585e6b1a48cda67707a Mon Sep 17 00:00:00 2001 From: "Claude (auto-RE)" Date: Tue, 11 Aug 2026 21:58:21 +0000 Subject: [PATCH] re: the UI screen gap is closed -- the bundle also carries every element's placement Immediately after the declaration table the RATC bundle holds a placement region, one group per element: a header of (u32 element index, u32 keyframe count) followed by that many 40-byte keyframes carrying scale/tint/X/Y. The element index is stated in the header, so the correspondence is read, not inferred from order. Tutorial pause bundle: 11 groups for 11 elements, every header index and count matching the blocks present. The values are self-evidently right -- three menu buttons at X=546 exactly 70 px apart (288/358/428), title (540,119), message (451,545), and the eff* sprites carrying multi-position fly-ins. Cross-checked: pgp_ttrl_btn10.rat places its own sprite at (546,288), identical to its inline group, so the inline region is the same placement data and it covers the eff*/deli*/msg elements that have no record of their own. So a screen is fully reconstructible from its bundle alone: element list and order from the declaration table, placement and animation from this region, plus the sprites, with "opt " supplying focused states. This retires the caveat added earlier today that only the order was known. Co-Authored-By: Claude Opus 5 (1M context) --- docs/re/INDEX.md | 2 +- docs/re/structures/ui-rat-layout.md | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index e9f0fa4..edeb4bd 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -24,7 +24,7 @@ Promote to a prose `structures/…md` file when a format needs behavioural notes | Weapon fields defaulted on disc | ✅ | [runtime struct](structures/weapon-struct-runtime.md) · [DATA SHEET route](weapon-datasheet-runtime.md) | **Solved.** Canary maps guest RAM into `/dev/shm`, so the parsed `Weapon`/`Shell` objects are readable live; their layout is solved against disc ground truth (zero contradictions over 100+ records). All 126 weapons, exact numbers, no story progress needed — [4 393 values](captures/weapon-runtime-fields.csv) the disc does not carry. Supersedes the letter-bucket limit of the DATA SHEET route, which now serves as the independent cross-check | | Unit (craft/vessel) fields defaulted on disc | ✅/🟡 | [runtime struct](structures/unit-struct-runtime.md) | The parsed `unit\UN_*.tbl` definition object, vtable `0x820af844`, ≥`0x380` bytes, one per unit — **discovered, not assumed** (`unit_discover.py`), and distinguished from the spawned-entity class `0x820af030` by being one-per-ID and byte-constant within a run. Across runs only pointer words move — `--crosscheck` proves **no reported field offset is run-dependent** (two words, `+0x2c8`/`+0x2d0`, are stage-dependent and remain unidentified). 27 fields ✅ (21 units, 7 runs); the `Maneuver` block is **schema declaration order, 4 bytes/field, base `0x9c` with a two-slot gap after `AA_Roll_Min`** (29 anchors, 0 conflicts), which also pins 5 fields *no* disc record ever values. Angles are **radians at runtime, degrees on disc**. Unlike weapons, unit definitions are instantiated **per stage**, so coverage (21/110) grows by visiting missions — but a defaulted field is **not** a global constant: `Size_Y` provably inherits `Size_X` (7 independent units, 6 distinct values), and three more sibling rules are recorded ❔, recovering 65 values in units never visited — [values](captures/unit-runtime-fields.csv) | | Arsenal develop economy | ✅/❔ | [arsenal-develop-economy](arsenal-develop-economy.md) + [conditions](captures/arsenal-develop-conditions.csv) | The Arsenal reads `weapon.tbl` (item ids, in the 8-category display order) and `strings.tbl` (names, descriptions, and a **"Conditions to obtain"** block per item) out of `GP_HANGAR_ARSENAL.pak`. All **60** conditions are extracted: gates are stage completion, a predecessor item, or an **ace kill**; costs run 3 000–350 000 P and **20 items are free** once gated. `weapon.tbl`'s first record reproduces the in-game DATA SHEET exactly (Range D / Power E / Speed – / Weight 0.3 = Light / 4000 P) — later records are unreadable from the string pool alone because IDXD **dedupes repeated values**. Used to identify the save blob's index space, now **solved**: the blob follows **`strings.tbl`'s** order — the display order *plus* the cut items only the localisation file lists (`Adhesive Mine B2A`, `Ballista GSH`, …) — pinned by four hand-written probe saves (9 Stiletto, 21 Falcon, 39 Tomahawk, 48 Jamming System) and closing exactly at index 53. `weapon.tbl`'s id list is **not** the index space; that it is also 54 long is a coincidence, and the two agree only to index 32. The retail save's five unexplained owned entries are the cut items, shipped owned and never rendered | -| UI screen layout (`.rat`) | ✅/🟡 | [ui-rat-layout](structures/ui-rat-layout.md) | One pak per UI screen; each RATC = one (context × language) build; every `.t32` sprite has a `.rat` **layout record** (BE u32; 1280×720 design space; scale/tint/X/Y, keyframes for animated elements, `opt ` link to the focused state). **The tutorial PAUSE menu and the title main menu both rebuild pixel-accurately from the disc.** `loop1.rat` is decoded — it is a **looping sprite animation**, not a composition. The **screen's draw list is the RATC bundle's own declaration table** (elements in back-to-front order, including the `eff*`/`deli*`/`msg` sprites that have no `.rat`, and excluding focused button variants reached via `opt `); its entry also carries a **parent element index** at `+32`. Still open: the **X/Y position** of the elements that have no `.rat` — the declaration entry has a pivot but no position | +| UI screen layout (`.rat`) | ✅/🟡 | [ui-rat-layout](structures/ui-rat-layout.md) | One pak per UI screen; each RATC = one (context × language) build; every `.t32` sprite has a `.rat` **layout record** (BE u32; 1280×720 design space; scale/tint/X/Y, keyframes for animated elements, `opt ` link to the focused state). **The tutorial PAUSE menu and the title main menu both rebuild pixel-accurately from the disc.** `loop1.rat` is decoded — it is a **looping sprite animation**, not a composition. The **screen's draw list is the RATC bundle's own declaration table** (elements in back-to-front order, including the `eff*`/`deli*`/`msg` sprites that have no `.rat`, and excluding focused button variants reached via `opt `); its entry also carries a **parent element index** at `+32`. **A screen is fully reconstructible from its bundle**: the placement region right after the declaration table gives every element a keyframe group (header = element index + keyframe count, then 40-byte blocks of scale/tint/X/Y), including the `.rat`-less sprites — verified 11/11 on the tutorial pause bundle, with `pgp_ttrl_btn10`'s inline (546,288) matching its own record exactly | | Save file (`savedata`) | ✅/❔ | [savegame-format](structures/savegame-format.md) + [`tools/re-capture/savegame.py`](../../tools/re-capture/savegame.py) | `GDHA` container, zlib payload, chunk stream (`GDAA` / phase name / `GHAD` 122 B progress block / 16×20 B slot table / trailer). **Container and layout read off the title's own serializer `0x822C00E8` and verified by a byte-identical round-trip**; the whole save is 545 B. Payload offsets are also the live save object's offsets (`save+8` GHAD, `save+136` slots). A second save made in-game names **Points** (+24), **flight time in ms** (+4) and **clear ratio %** (+8) off the game's own Details panel; the payload is a **pure function of game state** (same state saved twice = byte-identical, only the header FILETIME and its uninitialised pointer padding move), and the 16 `SHAB` records are **not** the UI's 20 save slots. Difficulty vs stage is undecided — three fields hold 2. **A third save, taken after developing exactly one Arsenal weapon** (Light Machine Gun MG I, 4000 P), moves exactly three things: `+24` Points 4101→101 (which **separates it from `+28`**, that did not move), `+8` clear ratio 5→6 (so the ratio counts *collection*, not only stages), and two entries of the 54-byte blob — `2→4` for the item bought and `0→2` for the successor the game announced as newly developable, giving the blob its alphabet ✅ *0 locked / 2 developable / 4 developed* (only the `4`s are stored — `2` is re-derived at load). **Saves can also be written back**: three derived header fields (length at `+0x30`, payload length at `+0x8c`, `adler32` at `+0x8e`) are all that stand between a parse and a hand-written save that the title loads, and [`savegame_edit.py`](../../tools/re-capture/savegame_edit.py) re-wraps a real save byte-identically. That turned the blob's index space from blocked-on-story-progress into four probe saves — see the [economy note](arsenal-develop-economy.md) | ## Runtime / dynamic-capture technique diff --git a/docs/re/structures/ui-rat-layout.md b/docs/re/structures/ui-rat-layout.md index f1f8c7a..b84646c 100644 --- a/docs/re/structures/ui-rat-layout.md +++ b/docs/re/structures/ui-rat-layout.md @@ -196,9 +196,32 @@ recording them because a static-only reading would have shipped them: `btn00/01/04/05.rat`, `msg`, `deli1…4`, `eff30…33`, `loop1.rat`. Note what it does **not** list: the focused button variants, which are reached through each base record's `opt ` link — so the table is the screen's element set, not a resource inventory. -- **What is still missing is only the position** for the elements that have no `.rat`: - the declaration entry carries a pivot but no X/Y. So *what* is drawn and *in what order* - is answered; *where* the `eff*` / `deli*` / `msg` sprites land is not. +- **And the position is there too — the gap is closed.** Immediately after the + declaration table the bundle carries a **placement region, one group per element**: + + ``` + group header: u32 element index (0,1,2… — the correspondence is stated, not inferred) + u32 keyframe count + then `count` keyframes, 40 bytes apart, each holding the familiar block: + u32 scale X = 100 u32 scale Y = 100 + u32 tint = 0xffffffff + u32 X u32 Y (+ a trailing time field) + ``` + + For the tutorial pause bundle this yields 11 groups for 11 elements, and every + group's header index and count match the blocks actually present. The values are + self-evidently right: the three menu buttons sit at X=546, **70 px apart** + (288 / 358 / 428), the title at (540,119), the message at (451,545), and the + `eff*` sprites carry multi-position fly-ins. + + **Cross-checked against the records themselves:** `pgp_ttrl_btn10.rat` places its + sprite at **(546,288)** — identical to its inline group. So the inline region is + the same placement data, and it covers the `eff*` / `deli*` / `msg` elements that + have no record of their own. + + A screen is therefore fully reconstructible from its bundle alone: **element list + and order** (declaration table) + **placement and animation** (this region) + + sprites, with `opt ` supplying focused states. - The same method should now unroll the other screens directly; `GP_HANGAR_ARSENAL.pak` (789 T8aD + 510 RATC) is the big one, and the ARSENAL `DATA SHEET` panel documented in [weapon-datasheet-runtime.md](../weapon-datasheet-runtime.md) is a ready-made oracle for it.