re: a unit field left unset on disc is not a global constant — Size_Y inherits Size_X
Attacks the 21/110 unit-coverage limit from the cheap side: if a defaulted field always took one runtime value, the captured units would pin it for all 110. Only 6 of 24 confirmed defaulted fields behave that way. The other 18 vary per unit, so the default is computed. Asking which OTHER field of the same unit holds that value — counting only non-zero cases, and checking the two fields sit at different offsets so the layout solver cannot be aliasing them — gives four rules. Size_Y <- Size_X is solid: seven unrelated ships (e105 600, e106 300, e108 80, e201 300, f101 400, f105 700, f106 200) each omit it on disc and each shows its own Size_X live, while the two fields differ freely when both are on disc. Size_Radius fits both min(X,Z) and the median of the three axes and cannot yet be separated; e010_ADAN_Attacker_S is what rules out the simpler Size_X rule. FCSRange and DefencePoint rest on two independent units each and ship as HYPOTHESIS. Applied across the disc the rules recover 65 (unit, field) values in units that have never been visited. Falsification test recorded: load any uncaptured stage and compare one predicted value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 |
|
| 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 |
|
| 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 |
|
| 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 `<name>.t32` sprite has a `<name>.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 |
|
| UI screen layout (`.rat`) | ✅/🟡 | [ui-rat-layout](structures/ui-rat-layout.md) | One pak per UI screen; each RATC = one (context × language) build; every `<name>.t32` sprite has a `<name>.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
|
## Runtime / dynamic-capture technique
|
||||||
|
|||||||
@@ -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.
|
is no need to play it, and no need to survive it.
|
||||||
|
|
||||||
Stages captured so far: `Ttrl` (BASIC CONTROLS), Stage 02.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user