# RE knowledge index Confidence: βœ… `CONFIRMED` Β· 🟑 `PROBABLE` Β· ❔ `HYPOTHESIS`. See [README](README.md). Formats we've already reversed are, for now, **documented by their parser + disc round-trip tests** (the executable spec) rather than a prose file β€” the "Spec" column points there. Promote to a prose `structures/…md` file when a format needs behavioural notes beyond layout. ## Data structures / formats | Format | Conf. | Spec (parser + tests) | Notes | |--------|-------|-----------------------|-------| | IPFB `.pak` archive | βœ… | `sylpheed-formats/src/pak.rs` + `tests/pak_idxd_disc.rs` | header + 12-byte TOC, Z1/zlib payloads | | name-hash (TOC keys) | βœ… | `sylpheed-formats/src/hash.rs` | Barrett-reduction hash; recovers original paths | | IDXD object/table | βœ… | `sylpheed-formats/src/idxd.rs` + `tests/idxd_records_disc.rs` ([container](structures/idxd-container.md)) | **The binary record/index region in front of the string pool is DECODED** (2026-08-25), closing the parser's long-standing "not yet decoded" note. Uniform 16-byte records `{name_hash, name_off, field_begin, field_end}` sorted by hash and binary-searched, then a field count, 12-byte fields `{key, name_off, value_off}` sorted by key, a pool size, and the string pool; the trailing `pool_size == file_len - pool_base` identity makes the layout self-checking. Verified over the **whole disc** with **zero** failures: 7 750/7 750 objects, 190 782/190 782 records reproducing their stored `tag_hash`, 1 271 462/1 271 462 named fields reproducing their key β€” and `IXUD` is the same container with `ixud_hash`, UTF-16BE and all offsets in **chars** (1 104/1 104 objects, 628 165/628 165 fields). **Field names are stored on disc** β€” a field's middle word points at its own name β€” so nothing needs preimage search except the **504** field entries disc-wide that are hash-keyed with no name β€” which are only **42 distinct keys**, each in 12 places (the page for these was never written β€” the finding is in this row), and are provably unrecoverable from the hash alone; the other 1 485 073 nameless fields are *positional*, keyed by a literal integer (line slots, movie ids). ⚠️ **Two long-held beliefs WITHDRAWN**: the word at `0x08` is **not a schema hash**, it is record 0's `name_hash` (7 750/7 750) β€” the header has no type field at all, so an object's kind is known only from the caller that loads it; and the field's middle word is **not** an always-`0xFFFFFFFF` flags word. The first was caught by a test asserting that every movie id names a real record: `1005 -> STAGE10_PHASE01` failed because `tag_hash("STAGE10_PHASE01")` **is** `0x067025B9`, that table's supposed schema id. 🟑 the legacy value-before-key string-pool reader is now known to be an *approximation* of the real table, and every number derived from it is re-checkable but not yet re-checked | | XPR2 texture + cubemap | 🟑/βœ… | `sylpheed-formats/src/texture.rs` + [colour check](xpr2-colour-check.md) | de-tile + A8R8G8B8 and DXT1. **Channel order βœ… confirmed against the running game**: the Delta Saber's decoded atlas is orange-dominant (median saturated hue 23.3Β°, *zero* cool pixels) and the game renders the same hull at 9.3Β° β€” a red↔blue swap would sit at β‰ˆ200Β°. Exact fidelity (gamma/sRGB curve, premultiplied alpha, per-channel scale) is 🟑 untested, since a hue comparison cannot see it; cubemap face ordering ❔ | | T8aD 2D texture | βœ… | `sylpheed-formats/src/t8ad.rs` | **100 % of the disc decodes** (19 216/19 216, measured). The "~15 % deferred variants" were a wrong model, not a variant: a surface is a list of **arbitrary sub-rectangles**, each with a 16-byte header of `dst X, dst Y, width, height`, not a 256Γ—256 grid β€” `0x1c` is the **rectangle count**. Uncovered area stays transparent. **Colours βœ… CONFIRMED** ([k8888](structures/texture-color-k8888.md)) | | RATC bundle | βœ… | `sylpheed-formats/src/ratc.rs` | child listing confirmed. **"One level deep" is not a limitation β€” there is nothing deeper**: 2 859 bundles hold 18 002 children at depth 1 and **0 at depth 2**, with no parse failures. Nested RATC blobs are **leaf records that reference siblings by name** (`opt `, the sprite name): 3 311 leaves, all embedding sibling names, **10 144 of 10 148 references resolve**. The 4 that do not are one dangling asset β€” `pmbase.rat` β†’ `pmbase.t32` in `GP_STAGE_CLEAR.pak`'s four language builds, and `pmbase.t32` is **on the disc nowhere** | | LSTA sprite list | βœ… | `sylpheed-formats/src/lsta.rs` | A display list of inline elements: **T8aD sprites and `PRMD` primitives**. The `count` at `0x04` is **exact and counts both** β€” `count == T8aD + PRMD` for **64/64** lists on the disc, which retires the old "a few entries disagree" note (it compared sprites against a total including primitives). **All 1 281 sprite frames decode** after the T8aD rectangle-list fix | | IXUD subtitle + caption text | βœ… | `sylpheed-formats/src/ixud.rs` + `movie_subtitle.rs` ([container](structures/idxd-container.md) Β· [movie link](movie-subtitle-link.md)) | **The IXUD record/field table is decoded and wired in (2026-08-26)** β€” `IxudObject` mirrors `IdxdObject`; uniform 16-byte records, 12-byte fields, every offset in **chars**, and the word at `0x08` is record 0's hash, not a schema id. Verified disc-wide: **1104/1104** objects, **1476/1476** records, **628 165/628 165** named fields reproducing their `ixud_hash` (`tests/ixud_records_disc.rs`). **Caption text: 537 β†’ 8800 lines, which is 8800 of 8800 distinct keys.** Two steps β€” generalising the key parser from `MSG_DEMO_*` to all **eight** families (`ACRO ADAN ADPL BIRD DEMO RHIN TCAF` use `MSG____`, `VOICE` alone inserts a family letter) took 537 β†’ 8074; switching from **token adjacency to record fields** took it to 8800. ⚠️ An earlier "1.3 % of the game's text" figure of mine counted *occurrences across blocks* β€” the honest denominator is **8800 distinct keys**, so the real starting point was 6.1 %. The `DEMO` control shows why the field route matters: token adjacency finds 537 lines there, fields find **541** β€” it was dropping lines in the one family it was written for. | timed cues. **The movie↔subtitle↔voice link is solved β€” statically**, and as of 2026-08-25 read from the IDXD **record table** rather than scraped from the string pool: **104 cutscene slots binding 101 distinct movies**, 99 slots / 96 movies with a subtitle, 99 / 96 with a voice track, 22 / 22 with a telop. ⚠️ The previous counts (94 / 83 / 21) were the numbers of **distinct pool strings** β€” a repeat reference contributes no token, so 13 later `VOICE_D_450..454` references read as "no binding". **All 18 hokyu movies are bound**, not five. 93 of the 94 distinct subtitle members resolve; `SUBTITLE_S12B.tbl` resolves in none of the six languages β€” a dangling reference on the disc. The ~104 **script ids are no longer ❔**: they are literal positional field keys in `BASE_INFO`, each naming its record, and all 104 resolve. `movie_manifest::parse` now reads the record table; CSV regenerated by `examples/movie_map_csv.rs` | | Fonts (ttf/otf/ttc) | βœ… | `sylpheed-formats/src/font.rs` | standard OpenType, parsed via ttf-parser | | XBG7 mesh | βœ…/🟑 | `sylpheed-formats/src/mesh.rs` + `tests/mesh_disc.rs` ([xbg7](structures/xbg7-mesh.md)) | **6 294 resources, 6 209 decode (98.7 %), 82 searched-and-missed** (2026-08-12, up from 5 480 / 87.1 %). Five evidence-driven fixes got there: **distinct anchor assignment** (no two resources may claim one buffer β€” proved by a capture showing the container holds both mirrored `e106` hull halves), the connectivity cap replaced by a **winding-consistency gate at 0.70**, **structural requirements on pre-pivot sub-meshes** (index range, then exact pool coverage), and **filtering after the assignment** so a subset query cannot differ from the full decode. Validated against a runtime capture that names the file offset of every buffer the engine drew: **46/46 drawn buffers claimed, 45 anchored exactly**. **No real mesh now decodes differently in different containers** β€” all 89 remaining cross-container disagreements are interchangeable 24-vertex bounding boxes, which no anchoring rule can pin (monotone order re-tested and refuted). Remaining misses attribute to the degeneracy/extent gate (42), winding (31) and coverage (9); the first was probed and its "obvious" fix refuted. Every decoded sub-mesh covers its own vertex pool. **The `[index buffer][vertex buffer]` layout is now runtime-verified** (2026-08-13): with the F10 capture extended to log each draw's index buffer, all **42** drawn `Stage_S02` buffers match our decoded index count exactly, all 42 have their index union cover the pool exactly, and the 30 single-block cases all sit at `pad ≀ 3` β€” so `e106_eng_02_l`'s old rejection was the connectivity gate, not a misplaced index buffer. The `indices=` mystery was the capture keeping only the **first of several index batches** per buffer. **And comparing index VALUES found the biggest silent defect yet**: the anchor took the first `pad` that validated, so a block whose index data sits at pad 2 was read **one element late** β€” 76/93 captured runs matched, all 17 differences a one-element shift. Scoring pads by degenerate triangles + winding fixes it: **93/93** captured runs now match byte for byte, disc-wide degenerate runs **582 β†’ 1** (the grouped path had the same bug; and two resources were anchored on a degenerate lookalike earlier in file order), **590 of 8 850** sub-meshes re-wired with 10 vertex anchors moved, resources decoded unchanged at 6 209. Cross-container minority decodes 89 β†’ 96 β€” *because* the decoder improved: `_rou_f402_dead` now has a majority (32Γ—25Γ—8) so its seven wrong copies are named instead of hidden. One dirty run remains, blocked by distinct assignment on a 24-vertex box. **Then the descriptor gave up its last structural secret**: it declares a vertex layout **per sub-mesh** (`n201_01` β†’ strides 24/24/24/**28**, capture-confirmed), and grouped selection must prefer the candidate explaining the **whole** pool rather than the first whose pivot validates β€” together they take never-decoding resources **85 β†’ 47** (**6 247 / 6 294 = 99.25 %** decode), put `n201_01` on all four capture-proven offsets and raise the stage-05 capture oracle to **128/128**. The residual 47 is 30 pose/proxy composites (0.010-unit marker boxes), 6 `.DAT` particle composites, 8 damage/LOD variants and 3 props β€” not a threshold away | | Capital-ship part placement | βœ… | `sylpheed-formats/src/ship.rs` (static) + [runtime capture](ship-placement-runtime-capture.md) | Placement is **sound** (hull static-exact against the `e106` capture; cross-id mounting genuinely narrow, 2 pairs across 335 ships). The XBG7 mis-decode this row used to blame for "ships assemble wrong" β€” a shared turret ~100Γ— too large in some containers β€” is **fixed** (2026-08-12, the exact-coverage requirement): `e303_wep_01` now decodes 49Γ—23Γ—42 everywhere and places at Β±179 on the `e106` hull, and no real mesh disagrees across containers. A composite-node audit confirmed the assembler itself never applied a bad scale (all nodes scale 1.0, orthonormal). Still open: `static_assembly_matches_runtime_capture` walks capture parts only, so **extra** static placements cannot fail it | | 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). ⚠️ **CORRECTED 2026-08-26: the disc DOES carry a third of them.** Re-read through the [record table](structures/idxd-container.md), **1 514 of the 4 393** rows labelled `defaulted-on-disc` have a value on disc (2 879 genuinely absent) β€” measured here as an upper bound, since a field is counted when it appears in *any* record of the object; a per-record count gives ~1 448. The old reader could not name a record, so per-record fields read as absent. Spot-checked exactly: `wep_05`/`wep_60` `TriggerShotCount` = **4**, `wep_02` `Power` = **100.0**, `wep_60` `Power` = **1000.0** (refuting the recorded "C band β‰ˆ150…500" bracket), `wep_25` `MaximumRange` = **4000.0**, `wep_11/28/36/70` `LoadingCount` = **6/5/5/0**. The runtime capture's numbers all match the disc β€” what is withdrawn is the premise that it was reaching values the disc lacks. See [weapon-datasheet-runtime](weapon-datasheet-runtime.md) 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**. **Re-derived independently 2026-08-13 from the loader's own key strings** (`sub_82341A20`; the field name for each store is a string in the image): **159 fields**, agreeing with this solver on **25 of 25 shared offsets**, verified at **406 values matching the disc and 0 disagreeing** over 11 live objects spanning UNIT and VESSEL β€” landed as `data/unit_definition_layout.txt` + `sylpheed_formats::unit_layout` + a no-emulator test, with **121 defaulted fields** read out ([live-unit-definitions](live-unit-definitions.md)). ⚠️ **CORRECTED 2026-08-26:** those fields are **on the disc**. The ~30-field player-craft table is on disc at exactly the values the runtime "recovered", spread across the `Generic` / `Shield` / `Mass` / `SE` records β€” which is why a reader that could not name a record saw them as absent. And **"18 of 23 vessel records are missing at least one of `Size_X/Y/Z/HP`" is false: 0 of 114** objects with a `Generic.Type` (43 Craft + 71 Vessel) miss any of the four. See [unit-struct-runtime](structures/unit-struct-runtime.md) Unlike weapons, unit definitions are instantiated **per stage**, so coverage (21/110) grows by visiting missions β€” ❌ **the sibling-default rules are WITHDRAWN (2026-08-25)** β€” `Size_Y` is on disc for **114/114** unit tables and **differs from `Size_X` in 90**; the old reader missed it exactly when the two were equal (a string-pool dedup artefact, cross-tab `seen+equal = 0` for all three pairs), so the rule was re-deriving the condition that hid the field. It also predicts wrong twice β€” `UN_e104_ADAN_Carrier.DefencePoint` and `UN_e011_ADAN_Attacker_B_HF_Wayne.FCSRange`. Read the record table instead β€” [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. ⚠️ **DEMOTED 2026-08-18** β€” the declaration table is *not* the paint order: a per-draw capture of the running title screen ([ui-title-paint-order-capture](ui-title-paint-order-capture.md)) paints element 13 first and elements 0/1 late, and the visible screen composites two bundles. The rest of the table's reading stands. Previously claimed: 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 | | UI screen paint order | βœ… | [runtime screen object](structures/ui-screen-runtime.md) + [title capture](ui-title-paint-order-capture.md) | **SOLVED**: the game's screen object keeps a second, reordered list of its elements β€” the child array at `+0x30` β€” and that is the paint order, not the declaration table. Read live from guest memory (found by the item vtable `0x820b30b4`) and checked against the draw capture: the seven nameable elements sit at child slots 0, 6, 7, 13, 16, 17, 22, strictly ascending, exactly as captured; it also settles the one pair no static field could order. 🟑 deriving that order from the bundle β€” what the port needs β€” is still open | | Scripted input / profile traps | βœ…/🟑 | [canary-scripted-input-traps](canary-scripted-input-traps.md) | Why a scripted run appears unable to press β’Ά: **F10 opens the emulator menu bar**, and any Xenia UI makes `XamInputGetKeystrokeEx` return SUCCESS with an empty keystroke *before* any driver is asked (Canary now logs `[RE-INPUT] … swallowed by IsUIActive`); the title needs a **signed-in profile** (hence `--create_profile_if_none`); and a **FIFO trace consumer that exits stalls the emulator**, which reads exactly like a dead pad. 🟑 The main menu HAS been reached β€” β’Ά works, but only intermittently (1 in ~4), which is the open question | | Title-screen guest crash | βœ… | [title-crash-stl-tree](title-crash-stl-tree.md) | The guest throws **`std::out_of_range`** from its cache-manager flush (`sub_823070B0`, an STL map/set erase that builds `'invalid map/set iterator'`); the access violation after it is only the throw **returning**, because this build does not unwind guest EH. Trigger found and controlled: an **incomplete on-disc cache** (`~/.local/share/Xenia/cache/aab216c3`) throws ~100 s into a boot, a complete one never does β€” 2 runs each way. ❌ `mem_watch`, the handoff's suspect #1, is **eliminated**: cold cache + `--mem_watch=false` throws anyway | | 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 | Technique | Conf. | Spec | Notes | |-----------|-------|------|-------| | Scripted input without a virtual controller | βœ… | [`tools/re-capture/pad.py`](../../tools/re-capture/pad.py) + Canary `--hid=file` | The old `vgamepad` path created its device through `/dev/uinput`, which is **not namespaced** β€” a pad made inside the container registers with the HOST's input stack, so every scripted press leaked to the desktop. Canary now carries a header-only driver that reads pad state from a **text file** (`--hid=file --pad_file=…`): no kernel device, nothing leaves the container, and analogue values are exact. ⚠️ The trap: 360 menus poll **`XamInputGetKeystrokeEx`**, not `GetState` β€” with `GetKeystroke` stubbed the pad looks completely dead on a title screen while its own log shows the press arriving. Implemented edge-triggered, no auto-repeat (scripted input wants one event per press). Proven end to end: boot β†’ title β†’ main menu β†’ EXTRAS from the file alone | | Live guest-memory write | βœ… | [`tools/re-capture/gpoke.py`](../../tools/re-capture/gpoke.py) | Write companion to `gmem.py`, same guest-VA β†’ `/dev/shm` map; prints before/after per word. Used to confirm the challenge gate's cleared-stage mask on the running game | | Live guest-memory read | βœ… | [`tools/re-capture/gmem.py`](../../tools/re-capture/gmem.py) | Canary backs the guest address space with `/dev/shm/xenia_memory_*`; guest VAs map in through Xenia's fixed table. Full-RAM search ~0.2 s (sparse, `SEEK_DATA`). No debugger, no emulator patch, game keeps running | | IDXD object layout solver | βœ… | [`tools/re-capture/weapon_runtime.py`](../../tools/re-capture/weapon_runtime.py) | Scan RAM for a class's vtable β†’ enumerate its objects β†’ brute-force `(field, offset, encoding)` against the disc records. Accepts a binding only on **zero** contradictions. Generalizes to any IDXD-backed definition | | Live entity state, anchored on the definition | βœ… | [`tools/re-capture/own_state.py`](../../tools/re-capture/own_state.py) Β· [autopilot](autopilot-memory-driven.md) | An undamaged craft holds its definition's own numbers, so a *solved definition field* locates the matching live field without a value scan: definition `HP` (1500) β†’ **hull at `position+0x154`**, confirmed by a trace across a death (30/60/90 per hit, negative at 0). Reusable for any live counter whose maximum the definition carries | | Mission / escort state, every entity's hull | βœ… | [`tools/re-capture/mission_state.py`](../../tools/re-capture/mission_state.py) Β· [escort state](mission-escort-state.md) | `hull = position + 0x154` is a property of the **entity class**, not of the player object: at t=0 it equals each entity's own definition `HP` across 7 classes and 5 distinct HP values (turret 100, fighter 500, destroyer 10000, cruiser 30000, **ACROPOLIS 25000**), falls under fire (780 damage events in 240 s), goes negative at death, and the object then leaves the heap. So an escort objective is scoreable live β€” `UN_f101_TCAF_Acropolis` measured at 25000 β†’ 23038 over 240 s, attack starting only at tβ‰ˆ170 s. `REMAINING OB` counts objectives, not hostiles (012 on the HUD vs 118 live ADAN); its address **is found** β€” see [mission-objective-counter](structures/mission-objective-counter.md) β€” but it is **run-dependent** and must be re-derived per run (refuted as a constant 2026-08-26) | | In-flight control mapping | βœ…/🟑 | [`tools/re-capture/fire_probe.sh`](../../tools/re-capture/fire_probe.sh) Β· [controls](flight-controls-runtime.md) | Measured by holding each pad input and photographing the HUD ammo counters: **`RB` = nose gun** (6000β†’5956 in 4 s, ~11 rounds/s, HEAT rises), **`Y` = main mount** (missiles, 300β†’299), d-pad = **tactical map** overlay, nothing else moves a counter. No target-cycle input exists β€” the `TARGET` marker is present with nothing pressed, so targeting is automatic and a missile lock is **time-on-target**. That, not target choice or ballistics, is what caps lethality at 2 kills per 98 missiles | | Throttle β†’ speed law | βœ…/🟑 | [flight-speed-law](flight-speed-law.md) | **The throttle selects a TARGET SPEED**, it does not add thrust: no input settles at ~420 (`CruisingVelocity` 350), `RT` at ~1 530 (`MaximumVelocity` 1200), `LT` at ~125 (`MinimumVelocity` 100), and releasing either returns to cruise. `Acceleration`/`Deceleration` govern the convergence rate (measured ~440–560 units/sΒ² against 500/600). 🟑 measured world speeds run β‰ˆ1.2–1.3Γ— the definition numbers while the HUD shows the definition value exactly, so world units are a constant (~1.25) multiple of the definition's velocity unit | | Input β†’ dynamics calibration | βœ… | [`tools/re-capture/ctrl_probe.py`](../../tools/re-capture/ctrl_probe.py) Β· [`binq.py`](../../tools/re-capture/binq.py) | Hold each pad input in turn and measure the craft's speed as displacement/s of its own position triple β€” no speed field needed first. Settled the throttle: **`RT` accelerates, `LT` brakes, and the setting persists** (488 β†’ 1510 β†’ 174 units/s), overturning an earlier field-scan conclusion | ## Functions / code paths | Function | Conf. | Reimpl. | Summary | |----------|-------|---------|---------| | Achievement table + XAM read path | βœ…/🟑 | [achievements](structures/achievements.md) | The XEX's `XACH` resource (`.pe` `0x8FBCBC`, 36-byte records) defines **24 achievements summing to 1000G** β€” the retail total, which self-checks the stride and field offsets. `GamePart_Debriefing` (`0x8218CF38`–`0x82191B18`) does two things: it walks the on-disc `ACHIEVEMENTS_REQUIREMENTS` list (`tables.pak` #16, entries `ACHIEVEMENT01…24`, in achievement-id order β€” its `ShootDownAircrafts`/`ShootDownShips`/`ShootDownWeight`/`GetAllWeapons`/`GetAllAchievements` types line up with ids 19–24 exactly as `XACH` names them), evaluating each and setting a bit; **and it enumerates `XACHIEVEMENT_DETAILS` from XAM** β€” 36-byte records confirmed by the `0x38E38E39`+`srawi 3` divide-by-36, `dwId` at `+0`, `dwFlags & 0x00020000` (`…_ACHIEVED`) at `+32`, behind a waited-then-closed async handle. **So earned state comes from the console profile, not the 545-byte save** β€” and the masks it builds are `1 << dwId`, i.e. **bit = the 1-based id**. **The challenge missions do NOT gate on this** β€” an earlier claim here, refuted by finding `+80`'s sole writer: it is `GamePart_StageClear` setting `1 << stage`, so that word is a *cleared-stage* mask and the shared "24" was a coincidence. `GetAllAchievements`/`GetAllWeapons` are requirement **types**, not debug cheats | | Challenge-mission gate + stage-config switch | βœ… | [challenge-mission-gate](challenge-mission-gate.md) | **`GamePart_ChallengeMission` gates each of the six challenge missions on a CLEARED-STAGE bit**: `REQUIREMENT` absent or `"Always"` β†’ available, else `n = atoi(v)` and it tests bit `n` of singleton `+80` (`n < 24`) or bit `n-24` of `+1956` (`n β‰₯ 24`) β€” which is the disc's own stage numbering (story 1–16 and tutorial 18–23 below 24, challenge 24–29 above). Word A's **sole writer** is `0x821C1820` in `GamePart_StageClear`, doing `1 << (this+84)` where `this+84` is the stage number (it also indexes a 20-byte per-stage record array and the debriefing `STAGE` sprite list). So `TimeAttack` needs **stage 16** β€” the last story mission β€” and the rest chain off challenge stages 25–29. The six-mission table is on disc in `tables.pak` (schema `54a10697`). Separately, the stage loader picks its config section from a **mission-kind field at `object+144`**: `3` β†’ `EXTRA`, `5`/`6` β†’ `CHALLENGE`, anything else β†’ `FILE`; two further sites treat `{3,5,6}` as one class. Constructed as `0` (`sub_821783D8`) and only ever *cleared* inside the class, so the kind comes from the launching GamePart, **not** from the stage number β€” which is a mechanism (🟑, unproven) for why patching the save's stage field to a challenge stage kills the load. Same note carries the **GamePart id table** (`0x820A1630`, 29 ids, `GP_CHALLENGE` = 26, cross-checked against the image's own `RegisterToFactory<26, …>` text) and the disc's **three stage families** β€” `S01`–`S16` story, `S18`–`S23` tutorial, `S24`–`S29` challenge, plus `Test`, matching `weapon.tbl`'s 16 + 6 + 6 key set exactly. `GP_CHALLENGE.pak` holds **0 IDXD objects** β€” it is the menu screen; challenge missions reuse `GP_MAIN_GAME_E.pak`'s stage records | ## All RE notes (generated) Every file under `docs/re/`. **Search this table before starting a new investigation** β€” the index was previously hand-maintained and listed 20 of 43 files, which is how the same ground got covered twice. | Note | Title | Status | |------|-------|--------| | [`SESSION-2026-08-11.md`](SESSION-2026-08-11.md) | Session log β€” 2026-08-11 (autonomous run) | β€” | | [`arsenal-develop-economy.md`](arsenal-develop-economy.md) | The Arsenal develop economy, and what the save's blob indexes | β€” | | [`autopilot-knowledge-sources.md`](autopilot-knowledge-sources.md) | Where the autopilot's missing knowledge lives on the disc | 🟑 this is a map, not the knowledge itself β€” it says which files | | [`autopilot-memory-driven.md`](autopilot-memory-driven.md) | Memory-driven autopilot β€” build log and current state | 🟒 IT FLIES, KILLS AND SURVIVES β€” but it loses the mission anyway. | | [`canary-scripted-input-traps.md`](canary-scripted-input-traps.md) | Getting past the title screen in the container β€” three traps and one blocker | βœ… CONFIRMED for the three traps (each reproduced, and two of them | | [`challenge-mission-gate.md`](challenge-mission-gate.md) | Challenge / EX missions β€” the stage set, the GamePart graph, and the kind field | βœ… for the static structure (stage set, GamePart ids, the config-section | | [`dynamic-re-state-restore.md`](dynamic-re-state-restore.md) | The container's dynamic-RE state is not durable β€” how to rebuild it | βœ… CONFIRMED by rebuilding it (2026-08-23). Everything the dynamic | | [`flight-controls-runtime.md`](flight-controls-runtime.md) | In-flight control mapping β€” measured, not assumed | βœ… for the weapon bindings (ammo counters move), 🟑 for the rest (HUD | | [`flight-speed-law.md`](flight-speed-law.md) | The throttle is a TARGET-SPEED selector β€” measured against the definition (2026-08-13) | βœ… for the shape of the law, 🟑 for the unit scale. | | [`guest-stalls.md`](guest-stalls.md) | The guest stalls, often β€” and the probe now detects it | βœ… the stall witness works and is validated against independent | | [`live-unit-definitions.md`](live-unit-definitions.md) | Live unit definitions from a running Stage 02 (2026-08-12) | β€” | | [`mission-arrival-watch.md`](mission-arrival-watch.md) | Six runs, no arrival β€” and an accidental control | βœ… the deployment structure reproduces exactly; βœ… losses require the | | [`mission-clock-advances.md`](mission-clock-advances.md) | The mission clock is running β€” the runs were too short in *game* time | πŸ”΄ "the phase clock is stopped" is refuted; βœ… something advances | | [`mission-escort-state.md`](mission-escort-state.md) | Escort / mission state from guest RAM β€” every entity's hull | βœ… CONFIRMED (2026-07-30). Capture: tools/re-capture/mission_state.py, | | [`mission-freeze-and-ob-flag.md`](mission-freeze-and-ob-flag.md) | An in-mission freeze, and the first cut at what `REMAINING OB` counts | β€” | | [`mission-freeze-resume-spin.md`](mission-freeze-resume-spin.md) | The in-mission freeze β€” the resume-spin lead is DEAD, and the freeze is a guest-side spin | πŸ”΄ the reading this file was named for is REFUTED (2026-08-23, later | | [`mission-liveness-probe.md`](mission-liveness-probe.md) | A motion-independent live roster β€” and what `n` probably is | βœ… the enumeration method; βœ… the hunting pilot gets kills; πŸ”΄ "no | | [`mission-objectives-text.md`](mission-objectives-text.md) | The mission script in plain English β€” and why phase 1 never completed | βœ… the localised string tables are decoded and readable; βœ… the phase | | [`mission-outcome-stage02.md`](mission-outcome-stage02.md) | Why Stage 02 is never won β€” the escort sinks at ~11 minutes (2026-08-10) | βœ… measured, one 500 s run. The standing open item since 2026-07-29 was | | [`mission-per-record-strength.md`](mission-per-record-strength.md) | Per-record craft strength β€” the measurement works, the run does not reproduce | βœ… the per-record measurement is internally consistent; πŸ”΄ it does not | | [`mission-phase-deployment.md`](mission-phase-deployment.md) | Enemies come into play per PHASE, deployed at phase start | 🟑 strongly supported and internally consistent, one unexplained | | [`mission-phase-objectives.md`](mission-phase-objectives.md) | What each phase asks for β€” from the game's own objective text | βœ… pinned by a disc test (crates/sylpheed-formats/tests/phase_objectives_disc.rs). | | [`mission-phase-runtime.md`](mission-phase-runtime.md) | Where the runtime phase state is *not* | βœ… the stage tables are resident in guest RAM, so the static decode | | [`mission-wave-arrivals.md`](mission-wave-arrivals.md) | The arrival timetable, and what the entity table is really counting | βœ… the static arrival timetable is in Route_S.tbl; 🟑 the runtime | | [`movie-subtitle-link.md`](movie-subtitle-link.md) | The movie ↔ subtitle ↔ voice link βœ… (static) | β€” | | [`pilot-never-fires.md`](pilot-never-fires.md) | `pilot.py` never pulls the trigger β€” the proximal cause, measured | βœ… ROOT CAUSE FOUND AND FIXED (2026-08-23, later the same day) β€” the | | [`probe-harness.md`](probe-harness.md) | A shared probe harness β€” so the same lessons stop being re-learned | βœ… built and verified on a live run. | | [`remaining-ob-hunt.md`](remaining-ob-hunt.md) | Hunting REMAINING OB by correlation β€” method works, run did not finish | βœ… the correlation method is sound and demonstrated; πŸ”΄ the hunt is | | [`roster-to-craft-link.md`](roster-to-craft-link.md) | The 116 roster records and the ~300 live craft are not directly linked | πŸ”΄ a direct pointer link is refuted in both directions; βœ… the two | | [`ship-placement-capture-generalisation.md`](ship-placement-capture-generalisation.md) | Capital-ship placement β€” does the `e106` result generalise? (WIP, 2026-07-31) | 🚧 WIP, time-boxed session. Two results so far: a static audit across all | | [`ship-placement-runtime-capture.md`](ship-placement-runtime-capture.md) | Capital-ship part placement β€” runtime capture (ground truth) | βœ…βœ… STATIC ASSEMBLY IS EXACT β€” no captures needed anymore | | [`structures/achievements.md`](structures/achievements.md) | Achievements β€” the 24-entry table, and where the earned state comes from | β€” | | [`idxd-legacy-reader-audit.md`](idxd-legacy-reader-audit.md) | The legacy IDXD string-pool reader vs the real field table β€” what the old numbers got wrong | 🟑 shape CONFIRMED by hand (`FCSRange`, `ShieldRatio`, hangar `Model`); disc-wide rates are single-source | | [`structures/idxd-container.md`](structures/idxd-container.md) | The IDXD/IXUD container β€” record/field table, and the two beliefs it withdraws | βœ… CONFIRMED disc-wide, 7 750/7 750 objects and 1 271 462/1 271 462 named fields, zero failures | | [`structures/hud-glyph-quad.md`](structures/hud-glyph-quad.md) | The HUD's glyph quad β€” vtable `0x820B2A64` | βœ… CONFIRMED for the object layout and the atlas size, read live off | | [`structures/slb-data-offset.md`](structures/slb-data-offset.md) | `.slb` leading-stream offset is `first_riff % 2048`, not the constant 1392 | βœ… CONFIRMED by decoding β€” 85 of 140 sampled banks yield more audio (median 70Γ—), 54 identical controls. ⚠️ The *cause* is a segment-packing phase, not a header: `X = (cumulative .pNN start) mod 2048`. Wave boundaries are exact β€” `seek` magic at `data_at + declared_size`, **7 620/7 620** β€” and `Channels` must be read from `RIFF+49` (2.12 % are stereo) | | [`structures/sound-pak-contents.md`](structures/sound-pak-contents.md) | Census of `sound.pak`, and the limit of the leading-region rule | βœ… CONFIRMED, 5 135/5 135 names hash into the TOC, **9 519/9 519** entries accounted for, and a full 4 114-bank manifest (408.3 min of audio) computed from PsuedoBytesPerSec without decoding; ⚠️ leading-region rule holds for 1 571/4 382 eng and 0/5 100 jpn | | [`structures/sound-cue-table.md`](structures/sound-cue-table.md) | The cue index in `tables.pak` β€” message id -> cue -> sound id -> `.slb` bank | βœ… CONFIRMED, 1 326/1 338 script message ids bind to a bank; SOUNDS and FILES agree on the same 12 absentees, 0 orphan files | | [`structures/cutscene-message-table.md`](structures/cutscene-message-table.md) | Cutscene dialogue β€” speaker, portrait, on-screen seconds, audio cue per page | βœ… CONFIRMED, field count = 9Β·PageCount+2 for all 7 PageCounts, 1 252/1 252 caption keys match, 138 ids close both ways | | [`structures/preset-message-rules.md`](structures/preset-message-rules.md) | The reactive-chatter rule table β€” 64 events, the (message, Yes/No) pair payload, the `Sperkers` roster | βœ… CONFIRMED, the seven fields read off `sub_82213980`, phase tables merge non-additively (224 variants discarded); 8 firing sites share one entry, Probability is a per-cent roll against rand*100.0; Yes = one-shot line; queue is Priority-ordered and capped at 128; the cooldown is Interval + Fluctuation*rand held in the lingering queue node; the 14 player-facing events are voiced by the player set alone (13/15 Katana tables, 0/129 others) (three are seconds Γ—60, the Yes/No element is a u32 mask, hence the 32 clamp); MessageCountΒ·2 == positional count in 9 216/9 216; five naming routes union to 144/144 (six names predicted from the speaker roster before hashing, control 0/4); 2 388/2 405 message ids bind to a cue | | [`structures/mission-scoring.md`](structures/mission-scoring.md) | Mission scoring and the S/A/B/C/D rank thresholds β€” 24 tables x 3 difficulties | βœ… CONFIRMED, one 22-field schema over 72 records; difficulty moves 10 of 22 fields and never the RankScore thresholds; βœ… all 24 named stage\StageParameter_S.tbl (+ _Tutorial shared by six, + _Test) | | [`structures/stage-settings-table.md`](structures/stage-settings-table.md) | The per-stage settings object β€” Camera rigs, Player limits, Difficulty_* damage record | βœ… CONFIRMED, 24 settings objects vs 29 resource objects both using Phase_1/2/3; Camera is 13/14 fields constant; GravityFactor non-zero in 4 stages; IsBoss16Enable identifies one object; the Phase block is 94 fields of which 68 never vary between phases; all 24 objects named StageParameter_S | | [`structures/isl-mission-timer.md`](structures/isl-mission-timer.md) | The mission timer β€” two sequential countdowns, not a limit and a warning | βœ… CONFIRMED β€” read from `sub_822639B8`; the threshold reading is | | [`structures/archive-naming.md`](structures/archive-naming.md) | Which archives the disc can name β€” 100% for menu paks, **0.0%** for the six 2D paks and `GP_READY_ROOM` | βœ… CONFIRMED β€” 6027 names Γ— 16 prefixes, bimodal; unnamed entries split by content β€” three UI paks have ZERO unnamed IDXD (artwork only), DefTables is the only real data gap (1295 LOD/motion tables); **route 2 = the `Enumerate` declaration tables** closes DefTables to 1413/1425 IDXD (99.2 %), 12 left; XPR2 manifests refuted as a naming source | | [`structures/stage16-boss.md`](structures/stage16-boss.md) | The Stage 16 boss end to end β€” object, loader `sub_82315AE8`, collision | βœ… CONFIRMED β€” 19/19 of the loader's names are `Guardian`/`Core` fields; 5 disc fields have no string in the image (two same-length controls do) | | [`structures/leaderboard-screen.md`](structures/leaderboard-screen.md) | The leaderboard screen β€” `sub_8219E560`, 12 panel elements, 4 board scopes, 6 modes | βœ… CONFIRMED β€” 18 names = 13 records + 5 fields; all 12 `Detail_*` records named, zero residual; ⚠️ 12 `Detail*` records and 12 `Detail*` fields are DISJOINT | | [`structures/result-screens.md`](structures/result-screens.md) | The debriefing and pilot-record screens β€” `STAGE_RESULT` 21 fields, `OVERVIEW` 9, `EX_OVERVIEW` 7 | βœ… CONFIRMED β€” the compiled key lists `sub_822814D8` (24/24) and `sub_8227A3A0` (21/21) match the `tables.pak` records exactly; `2+21+1=24`, `5+7+9=21` | | [`structures/player-tuning-tables.md`](structures/player-tuning-tables.md) | **`PlayerParams`** β€” the player parameter table: `Booster` flight model, special attacks, 8 analog curves, the S16 boss | βœ…βœ… `sub_822F9498` is `PlayerParams`'s loader, not the unit loader; **the analog block's reader SOLVED β€” `sub_821A6CF0`, base `0x820A1630`**, via the new base-solver (261 name-block loaders image-wide after the `r0` correction, control 217/226 on `sub_82341A20`); 🟑 nothing selects `Booster` | | [`structures/hud-config.md`](structures/hud-config.md) | The in-game HUD configuration β€” 16 records, 419 asset paths, and a per-stage `ResourceTable` | βœ… CONFIRMED β€” 0 of 419 config paths resolve as pak entries | | [`structures/mission-script-manifest.md`](structures/mission-script-manifest.md) | `Stage\script.tbl`'s 11 non-`MISSION` fields, and the mission dialogue table they name | βœ… CONFIRMED β€” 1 of 7 values resolves (`message\MissionDialogMessage.tbl`, 200 records), controls in the same sweep | | [`structures/stage-numbering-and-player-craft.md`](structures/stage-numbering-and-player-craft.md) | The disc's stage numbering (S01–S16, S18–S29) and the player's craft per stage | βœ…βœ… CONFIRMED β€” two independent files close on 6 + 15 + 1 + 6 = 28 | | [`structures/hangar-loadout-system.md`](structures/hangar-loadout-system.md) | The Hangar loadout system β€” loadout β†’ per-slot allow-list β†’ arsenal item | βœ… CONFIRMED β€” 60/60 slot refs, 70/88 item refs (18 = the `No_Equipment` sentinel) | | [`structures/arsenal-item-weapon-chain.md`](structures/arsenal-item-weapon-chain.md) | The 59-of-131 arsenal question β€” an item names a hardpoint slot, not a weapon | βœ…βœ… CONFIRMED β€” 0/59 `PlayerWeapon` are a `Weapon.ID`, 59/59 are a `Turret_NNN` slot | | [`structures/unit-substructure-records.md`](structures/unit-substructure-records.md) | The destructible-subsystem model β€” a unit's `Turret`/`Bridge`/`Hatch`/`ShieldGenerator`/`Thruster` sub-records | βœ… CONFIRMED β€” 835 turrets, 835/835 `WeaponID` resolve, `StructureCount` control 667/684 | | [`structures/unit-datasheet-static.md`](structures/unit-datasheet-static.md) | The static unit datasheet and AI flight model β€” `Generic`, `Maneuver`, `Effect` | βœ… CONFIRMED β€” 394 Generic + 114 Maneuver + 114 Effect records read; AA_/AV_ are one interleaved block (AV at X, AA at X+8), selection πŸ”΄ BLOCKED for static RE (4 routes, all controlled) | | [`structures/weapon-datasheet-static.md`](structures/weapon-datasheet-static.md) | The static weapon datasheet β€” `Weapon`, `Shell`, `AssortMissileParam` | βœ… CONFIRMED β€” 131 Weapon + 131 Shell + 9 AssortMissileParam records read | | [`structures/isl-timers.md`](structures/isl-timers.md) | A ScriptPhase owns 32 stopwatches, and they count seconds | βœ… CONFIRMED β€” the advance is read from `sub_822710D0`, the unit from | | [`structures/isl-message-dialogue-link.md`](structures/isl-message-dialogue-link.md) | Mission scripts as dialogue β€” built-in 64 -> message id -> caption text | βœ… CONFIRMED total, 2 683/2 683 call sites across all 28 stages resolve, no residue | | [`structures/mission-objective-counter.md`](structures/mission-objective-counter.md) | `REMAINING OB` β€” the mission's own objective counter, in RAM | βœ… CONFIRMED for one Stage 02 run: a big-endian u32 whose value | | [`structures/movie-subtitles.md`](structures/movie-subtitles.md) | Movie subtitles & the movie ↔ mission ↔ text chain | β€” | | [`structures/asteroid-fields.md`](structures/asteroid-fields.md) | `AsteroidGroup_00N` β€” the asteroid-field tables a `Phase_N.AsteroidDefinition` names | βœ… 10 objects/pack, 384 group records, 3 fields each (`AsteroidModelName`, `AsteroidFrameName`, `EnumAsteroid` = **the frame record's `FrameCount`, 57/57**, and the frame holds **`index, quaternion(x,y,z,w), position(x,y,z)`** per asteroid β€” 37 518 items, unit norm 37 518/37 518); `Enum` counts while `Enumerate` names a table (54/60); the join resolves as `name_hash("stage\" + name)` 9/9; πŸ”‘ a tenth `Frame_Alpha_S01_*` object nothing references β€” it miscounts AND its 7 frames have no placement records; πŸ”‘ **S28_p1 has an asteroid volume but no definition** | | [`structures/mcol-collision.md`](structures/mcol-collision.md) | `MCOL` β€” same container and map parameters as `REGN` | 🟑 OPENED β€” `POF0` at `data_size+16` 11/11, bbox pad words 11/11, `extent == maxβˆ’min` 11/11, and bbox + cell-size distributions identical to `REGN` (2/6/3 and 2/9); everything past `0x40` ❔ | | [`structures/regn-map-grid.md`](structures/regn-map-grid.md) | `REGN` β€” a per-map spatial grid (and `MCOL` beside it) | βœ… CONFIRMED for the header, which self-checks on all 11 objects on | | [`structures/savegame-format.md`](structures/savegame-format.md) | Save file (`savedata`) β€” container βœ… exact, 3 fields named βœ…, rest ❔ (2026-08-11) | βœ… CONFIRMED for the container and the chunk layout β€” parsed off the | | [`structures/sound-slb.md`](structures/sound-slb.md) | Sound bank audio β€” `sound.pak` / `.slb` / XMA1 | β€” | | [`structures/stage-definition-table.md`](structures/stage-definition-table.md) | Stage definition table and the squadron (`UnitGroup`) roster | βœ… for the record vocabulary and the stageβ†’table wiring; | | [`structures/stage-mission-tables.md`](structures/stage-mission-tables.md) | The stage table set β€” phases, routes, sub-objectives and AI parameters | βœ… the table set and how the stage record reaches it, validated across; **`AIParams` disc-wide: 23 objects, one shared 34-profile roster (782 records), loader `sub_8233C368`; `Type`β†’field-count holds except the two `_Test` templates** | | [`structures/texture-color-k8888.md`](structures/texture-color-k8888.md) | Texture colour interpretation β€” `k_8_8_8_8` (32bpp UI/HUD textures) | β€” | | [`structures/ui-composable-bundles.md`](structures/ui-composable-bundles.md) | A screen build is not the only thing `compose` can draw | βœ… CONFIRMED by measurement over the disc, with the artifact to | | [`structures/ui-focus-and-effect-elements.md`](structures/ui-focus-and-effect-elements.md) | `_eff` glow layers are not focused-state records | βœ… CONFIRMED by measurement over all 965 screen builds on the disc, | | [`structures/ui-paint-order-key.md`](structures/ui-paint-order-key.md) | The paint order comes from a layer key in the T8aD sprite header | βœ… CONFIRMED on both screens whose paint order has been measured β€” | | [`structures/ui-prm-primitives.md`](structures/ui-prm-primitives.md) | `.prm` elements are untextured full-screen quads, and `kind` bit `0x10` says so | βœ… CONFIRMED statically across all 965 screen builds on the disc. | | [`structures/ui-rat-layout.md`](structures/ui-rat-layout.md) | `.rat` β€” the UI element layout / animation record | βœ… CONFIRMED for placement (2026-07-28). The retail UI can be | | [`structures/ui-resting-pose.md`](structures/ui-resting-pose.md) | A keyframe is the start of a ramp, not a pose that is held | βœ… CONFIRMED against the framebuffer capture of the running title | | [`structures/ui-screen-runtime.md`](structures/ui-screen-runtime.md) | The UI screen object at runtime β€” found in live guest memory | βœ… CONFIRMED for the object's identity and its element array (five | | [`structures/unit-group-table.md`](structures/unit-group-table.md) | `stage\UnitGroup_S.tbl` β€” the per-stage squadron roster | βœ… container format and field semantics, validated across all 28 stage | | [`structures/unit-struct-runtime.md`](structures/unit-struct-runtime.md) | Runtime `Unit` struct (craft / vessel definitions) β€” read from live guest memory | β€” | | [`structures/weapon-struct-runtime.md`](structures/weapon-struct-runtime.md) | Runtime `Weapon` / `Shell` structs β€” read from live guest memory | β€” | | [`structures/xbg7-mesh.md`](structures/xbg7-mesh.md) | XBG7 β€” mesh geometry (inside XPR2 model containers) | β€” | | [`title-crash-stl-tree.md`](title-crash-stl-tree.md) | The title-screen crash is an STL `map`/`set` erase on a bad iterator | βœ… CONFIRMED β€” the guest throws std::out_of_range from an STL | | [`ui-paint-order-third-permutation.md`](ui-paint-order-third-permutation.md) | A third measured paint order β€” tool built and validated, screen not reached | βœ… the reader works and is CONFIRMED against both previously | | [`ui-quad-class-foothold.md`](ui-quad-class-foothold.md) | The guest's UI quad class β€” a foothold found from the capture's vertex layout | 🟑 PROBABLE for the identification below (it is a static read, but | | [`ui-title-paint-order-capture.md`](ui-title-paint-order-capture.md) | The title screen's paint order, measured from the guest's draw submissions | βœ… CONFIRMED β€” the order in which the running game paints the title | | [`upstream-baseline.md`](upstream-baseline.md) | A stock-upstream baseline runs Stage 02 crash-free | βœ… CONFIRMED β€” upstream canary_experimental + only the pad | | [`weapon-datasheet-runtime.md`](weapon-datasheet-runtime.md) | Weapon DATA SHEET β€” runtime capture (Route B) | 🟑 first dynamic capture, 2026-07-28. The Arsenal's Gallery Mode panel is a | | [`xpr2-colour-check.md`](xpr2-colour-check.md) | XPR2 colours: channel order βœ… confirmed against the running game | β€” |