diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index 8f39380..6444286 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -22,7 +22,7 @@ Promote to a prose `structures/โ€ฆmd` file when a format needs behavioural notes | XBG7 mesh | ๐ŸŸก/โ” | `sylpheed-formats/src/mesh.rs` + `tests/mesh_disc.rs` ([xbg7](structures/xbg7-mesh.md)) | weapons/props: declaration-driven variable stride (36 models), GPU-confirmed. **Stage containers: 5662 sub-models across 22 stages** via content-anchored grouped pools (`stage_models`). Quantized hero bodies (DeltaSaber `f004`) still declined | | 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 โ€” [values](captures/unit-runtime-fields.csv) | +| 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) | | 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 | ## Runtime / dynamic-capture technique diff --git a/docs/re/structures/unit-struct-runtime.md b/docs/re/structures/unit-struct-runtime.md index 57b4836..04f3413 100644 --- a/docs/re/structures/unit-struct-runtime.md +++ b/docs/re/structures/unit-struct-runtime.md @@ -272,3 +272,55 @@ take-off, ~12 minutes in mid-combat, and after GAME OVER. 14 objects, the same is no need to play it, and no need to survive it. Stages captured so far: `Ttrl` (BASIC CONTROLS), Stage 02. + +## A defaulted unit field is not a global constant โ€” some inherit from a sibling + +**Confidence: ๐ŸŸก for `Size_Y`, โ” for the rest. Analysis 2026-08-10, offline, from +[`captures/unit-runtime-fields.csv`](../captures/unit-runtime-fields.csv).** + +The coverage limit above (21 of 110 units, growing only with story progress) is +worth attacking from the other side first: *if* a field the disc leaves unset +always took the same runtime value, the 21 captured units would pin that default +for all 110 and no further missions would be needed. + +**It does not.** Restricting to the 150 values that are both โœ… CONFIRMED and +come from a field the disc leaves defaulted, only 6 of 24 fields have a single +value across every unit that defaults them (`HP`โ†’10, `MassScore`โ†’0, +`MaximumVelocity`โ†’0, `RadarRange`โ†’0, `DestroyMotionTime`โ†’0, `Size_Z`โ†’0.1). The +other 18 take several distinct values โ€” so the default is computed per unit. + +Where from? For each defaulted value, ask which *other* field of the same unit +holds exactly that value. Counting only cases where the value is **non-zero** +(otherwise `0 == 0` inflates every pair) and checking that the two fields are at +**different offsets** (so the match is not the layout solver aliasing them): + +| defaulted field | takes the value of | support | independent units | +|---|---|---|---| +| `Size_Y` (`0x034`) | `Size_X` (`0x030`) | 9/9 | **7**, 6 distinct values | +| `Size_Radius` (`0x050`) | `min(Size_X, Size_Z)` | 4/4 | 4, 3 distinct values | +| `FCSRange` (`0x2a4`) | `RadarRange` (`0x2a0`) | 4/4 | 2 | +| `DefencePoint` (`0x2bc`) | `AttackVesselPoint` (`0x2b4`) | 6/6 | 2 | + +`Size_Y โ† Size_X` is the one to trust: seven unrelated ships (`e105` 600, +`e106` 300, `e108` 80, `e201` 300, `f101` 400, `f105` 700, `f106` 200) each omit +`Size_Y` on disc and each shows its own `Size_X` at runtime. When both fields +*are* on disc they differ freely (14 distinct `Size_Y` values against 13 of +`Size_X`), so this is a default rule, not one value stored twice. + +`Size_Radius`'s formula is **not yet separable**: `min(Size_X, Size_Z)` and "the +median of the three axes" fit all four units identically. `UN_e010_ADAN_Attacker_S` +is what rules out the simpler `Size_Radius โ† Size_X` (X=100, Y=40, Z=50, radius +**50**). The last two rules rest on two independent units each and are โ” โ€” +recorded so they can be falsified, not relied on. + +**Why it matters for the reimplementation:** filling a missing `Size_Y` with `0` +or with a global constant gives the game's largest hulls a wrong lateral extent +(`f105` 700, `e105` 600, `f101` 400 โ€” all defaulted on disc). Applied across the +disc, the rules recover **65 (unit, field) values in units that have never been +visited**: `Size_Y` in 21 of the 21 units that omit it, `Size_Radius` in 22 of 26, +`FCSRange` in 14 of 56, `DefencePoint` in 8 of 60. + +**How to falsify:** the rules predict a specific number for units in stages not +yet captured. Load any new stage, snapshot, and compare โ€” one disagreement kills +the rule. That test is also the cheapest use of the next mission that gets +loaded, whatever it is loaded for.