diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index 74db474..8cd04e5 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -23,9 +23,9 @@ Promote to a prose `structures/โ€ฆmd` file when a format needs behavioural notes | Capital-ship part placement | ๐ŸŸก | `sylpheed-formats/src/ship.rs` (static) + [runtime capture](ship-placement-runtime-capture.md) | hull placement static-exact; external parts approximate statically. **Runtime capture** (Canary F10 โ†’ VS-constant WorldView) gives ground truth โ€” validated on `e106` destroyer; not yet baked into the viewer | | 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 โ€” the `No_Equipment โ€ฆ Wep_83` run is exactly **54**, the save blob's length) 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 | +| 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` (screen-level draw order) not yet decoded | -| 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*. Which item each index is remains open | +| 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/savegame-format.md b/docs/re/structures/savegame-format.md index 38c6604..8cb5ca7 100644 --- a/docs/re/structures/savegame-format.md +++ b/docs/re/structures/savegame-format.md @@ -99,11 +99,13 @@ enumerated states, **16 of the 54 non-zero** (ten 4s, six 2s): 02 00 00 00 02 00 04 00 00 00 00 00 04 00 00 00 00 00 04 04 04 00 00 00 00 00 00 ``` -54 matches no count we have already established (22 stages, 110 units, 126 -weapons, ~61 Arsenal entries), so **do not** assume which list it indexes. The -develop differential below identifies *what the values mean* without settling -*which list the index runs over* โ€” the two are separate questions, and only the -first is answered. +The two questions here โ€” *what the values mean* and *which list the index runs +over* โ€” are separate, and were answered separately: the develop differential +below settles the first, and four hand-written probe saves settle the second (the +index space is `strings.tbl`'s item order; see +[the economy note](../arsenal-develop-economy.md)). The `54` length matches no +count established elsewhere, and in particular it is **not** `weapon.tbl`'s +equally-54-long id run โ€” that coincidence cost real time. ### The develop differential: one weapon, three fields