diff --git a/crates/sylpheed-formats/examples/verify_fieldmap.rs b/crates/sylpheed-formats/examples/verify_fieldmap.rs index fc48421..4dd5556 100644 --- a/crates/sylpheed-formats/examples/verify_fieldmap.rs +++ b/crates/sylpheed-formats/examples/verify_fieldmap.rs @@ -6,16 +6,11 @@ use sylpheed_formats::idxd::IdxdObject; use sylpheed_formats::pak::PakArchive; // Offsets read out of sub_82341A20's own key strings (stfs stores only). -const MAP: &[(usize, &str)] = &[ - (48, "Size_X"), (52, "Size_Y"), (56, "Size_Z"), (80, "Size_Radius"), - (64, "Color_R"), (68, "Color_G"), (72, "Color_B"), - (88, "HQRatio"), (92, "ShieldRatio"), (96, "ThrusterRatio"), - (116, "ResistanceToOptics"), (120, "ResistanceToShell"), - (124, "ResistanceToExplosion"), (128, "ResistanceToPlayer"), - (132, "ResistanceParalyze"), - (672, "RadarRange"), (676, "FCSRange"), (680, "FiringRange"), - (692, "AttackVesselPoint"), (696, "AttackCraftPoint"), (700, "DefencePoint"), -]; +// Offsets and names recovered from `sub_82341A20` itself: each key is built as +// `addi r4, r30, -N` (r30 = 0x82088f94, so the name is a string in the image), +// and the value lands in the first store into the object after the accessor +// call. 125 float-typed fields. +const MAP: &[(usize, &str)] = &[(48, "Size_X"), (52, "Size_Y"), (56, "Size_Z"), (64, "Color_R"), (68, "Color_G"), (72, "Color_B"), (80, "Size_Radius"), (84, "HP"), (88, "HQRatio"), (92, "ShieldRatio"), (96, "ThrusterRatio"), (116, "ResistanceToOptics"), (120, "ResistanceToShell"), (124, "ResistanceToExplosion"), (128, "ResistanceToPlayer"), (132, "ResistanceParalyze"), (156, "MinimumVelocity"), (160, "MaximumVelocity"), (164, "CruisingVelocity"), (168, "Acceleration"), (172, "Deceleration"), (176, "AV_PitchPlus_Max"), (180, "AV_PitchPlus_Min"), (184, "AA_PitchPlus_Max"), (188, "AA_PitchPlus_Min"), (192, "AV_PitchMinus_Max"), (196, "AV_PitchMinus_Min"), (200, "AA_PitchMinus_Max"), (204, "AA_PitchMinus_Min"), (208, "AV_Yaw_Max"), (212, "AV_Yaw_Min"), (216, "AA_Yaw_Max"), (220, "AA_Yaw_Min"), (224, "AV_Roll_Max"), (228, "AV_Roll_Min"), (232, "AA_Roll_Max"), (236, "AA_Roll_Min"), (248, "SideThrustVelocity_Max"), (252, "SideThrustAcceleration"), (256, "MaximumBank_Normal"), (260, "YawDragFactor"), (264, "PitchDragFactor"), (268, "RollDragFactor"), (272, "DragFactorThreshold"), (276, "ArterBurner_Vc"), (280, "ReverseThrust_Vc"), (284, "ArterBurner_Acc"), (288, "ReverseThrust_Acc"), (292, "AccPitchFactor"), (296, "DecPitchFactor"), (300, "AV_AxisMode_Max"), (304, "AV_AxisMode_Min"), (308, "AA_AxisMode_Max"), (312, "AA_AxisMode_Min"), (316, "PowerCutConsumeShield"), (320, "PowerCutDeceleration"), (324, "AB_ConsumeShield_Begin"), (328, "AB_ConsumeShield"), (332, "AB_AV_PitchPlus"), (336, "AB_AA_PitchPlus"), (340, "AB_AV_PitchMinus"), (344, "AB_AA_PitchMinus"), (348, "AB_AV_Yaw"), (352, "AB_AA_Yaw"), (356, "AB_AV_Roll"), (360, "AB_AA_Roll"), (368, "SideRoll_Time"), (372, "SideRoll_Length"), (380, "BarrelRoll_CountMinimum"), (384, "BarrelRoll_CountMaximum"), (388, "BarrelRoll_Time"), (392, "BarrelRoll_Radius"), (400, "TurnAttack_CutoffRatio"), (404, "TurnAttack_DoubleRatio"), (408, "CutoffTimeMin"), (412, "CutoffTimeMax"), (416, "TurnAttack_DoubleTimeMin"), (420, "TurnAttack_DoubleTimeMax"), (428, "Turn_AngularVelocity"), (432, "TurnAway_Time_Minimum"), (436, "TurnAway_Time_Maximum"), (444, "BoostAway_Time_Minimum"), (448, "BoostAway_Time_Maximum"), (456, "HoldPosition_LengthMin"), (460, "HoldPosition_LengthMax"), (464, "HoldPosition_MinimumTime"), (468, "HoldPosition_MaximumTime"), (472, "HoldPosition_SideRatio"), (476, "HoldPosition_BackRatio"), (480, "HoldPosition_CutoffRatio"), (484, "HoldPosition_CancelTime"), (492, "Slalom_CutoffRatio"), (496, "Slalom_TurnCount_Min"), (500, "Slalom_TurnCount_Max"), (508, "Through_CutoffRatio"), (512, "Through_AngleMinimum"), (516, "Through_AngleMaximum"), (520, "Through_Time1Max"), (524, "Through_Time1Min"), (528, "Through_Time2Max"), (532, "Through_Time2Min"), (536, "Through_LengthMinimum"), (540, "Through_LengthMaximum"), (548, "SolidCutoff_Ratio"), (552, "SolidCutoff_LengthMin"), (556, "SolidCutoff_LengthMax"), (560, "HomingResistAdjustment"), (564, "UsingChaffRatio"), (568, "MaxValue"), (572, "ChargeDelay"), (576, "ChargeDelay_Break"), (580, "ChargeSpeed"), (584, "Delay"), (588, "DelayAdjustment"), (624, "DestroyMotionTime"), (628, "DryMass"), (632, "GrossMass"), (664, "LowerHPThresholdRatio"), (668, "SELength"), (672, "RadarRange"), (676, "FCSRange"), (680, "FiringRange"), (692, "AttackVesselPoint"), (696, "AttackCraftPoint"), (700, "DefencePoint")]; fn main() { let mut a = std::env::args().skip(1); @@ -50,7 +45,12 @@ fn main() { let Some(got) = w.get(off) else { continue }; match o.get_f32(key) { Some(want) => { - if (want - got).abs() <= want.abs() * 1e-4 { agree += 1 } else { + // Angle fields are stored in RADIANS at runtime and degrees on + // disc, so a match is either the raw value or its conversion. + let rad = want * std::f32::consts::PI / 180.0; + let ok = (want - got).abs() <= want.abs() * 1e-4 + || (rad - got).abs() <= rad.abs() * 1e-4; + if ok { agree += 1 } else { disagree += 1; println!(" MISMATCH {id} {key}: disc {want}, live +{off} = {got}"); } diff --git a/docs/re/captures/stage02-defaulted-field-runtime-values.txt b/docs/re/captures/stage02-defaulted-field-runtime-values.txt new file mode 100644 index 0000000..f34b261 --- /dev/null +++ b/docs/re/captures/stage02-defaulted-field-runtime-values.txt @@ -0,0 +1,105 @@ + +map check: 154 fields agree with the disc, 0 disagree + +runtime value where the disc record DEFAULTS the field: + AA_AxisMode_Max 0 (5 units) + AA_AxisMode_Min 0 (5 units) + AA_PitchMinus_Max 0.0628319 (5 units) + AA_PitchMinus_Min 0.0628319 (5 units) + AA_PitchPlus_Max 0.0628319 (5 units) + AA_PitchPlus_Min 0.0628319 (5 units) + AA_Roll_Max 0.0628319 (5 units) + AA_Roll_Min 0.0628319 (5 units) + AA_Yaw_Max 0.0628319 (5 units) + AA_Yaw_Min 0.0628319 (5 units) + AB_AA_PitchMinus 0 (5 units) + AB_AA_PitchPlus 0 (5 units) + AB_AA_Roll 0 (5 units) + AB_AA_Yaw 0 (5 units) + AB_AV_PitchMinus 0 (5 units) + AB_AV_PitchPlus 0 (5 units) + AB_AV_Roll 0 (5 units) + AB_AV_Yaw 0 (5 units) + AB_ConsumeShield 0 (5 units) + AB_ConsumeShield_Begin 0 (5 units) + AV_AxisMode_Max 0 (5 units) + AV_AxisMode_Min 0 (5 units) + AV_PitchMinus_Max 0.0628319 (5 units) + AV_PitchMinus_Min 0.0628319 (5 units) + AV_PitchPlus_Min 0.0628319 (5 units) + AV_Roll_Max 0.0628319 (5 units) + AV_Roll_Min 0.0628319 (5 units) + AV_Yaw_Max 0.0628319 (5 units) + AV_Yaw_Min 0.0628319 (5 units) + ArterBurner_Acc 2 (5 units) + ArterBurner_Vc 2 (5 units) + AttackCraftPoint 0.1, 0.5 (5 units) + AttackVesselPoint 0.1 (2 units) + BarrelRoll_CountMaximum 0 (5 units) + BarrelRoll_CountMinimum 0 (5 units) + BarrelRoll_Radius 0 (5 units) + BarrelRoll_Time 0 (5 units) + BoostAway_Time_Maximum 0 (5 units) + BoostAway_Time_Minimum 0 (5 units) + ChargeDelay 1 (5 units) + Color_B 1 (1 units) + Color_G 1 (1 units) + CutoffTimeMax 0 (5 units) + CutoffTimeMin 0 (5 units) + DecPitchFactor 30 (5 units) + DefencePoint 0.1, 0.25 (4 units) + Delay 0 (3 units) + DelayAdjustment 0, 2 (5 units) + DragFactorThreshold 0.5 (5 units) + DryMass 10000 (2 units) + FiringRange 0 (5 units) + GrossMass 10000 (5 units) + HQRatio 0.2, 1 (4 units) + HoldPosition_BackRatio 0 (5 units) + HoldPosition_CancelTime 0 (5 units) + HoldPosition_CutoffRatio 0 (5 units) + HoldPosition_LengthMax 0 (5 units) + HoldPosition_LengthMin 0 (5 units) + HoldPosition_MaximumTime 0 (5 units) + HoldPosition_MinimumTime 0 (5 units) + HoldPosition_SideRatio 0 (5 units) + HomingResistAdjustment 0 (5 units) + LowerHPThresholdRatio 0.3 (1 units) + MinimumVelocity 0 (4 units) + PitchDragFactor 2 (5 units) + PowerCutConsumeShield 0 (5 units) + PowerCutDeceleration 0 (5 units) + ResistanceToExplosion 0.5 (3 units) + ResistanceToPlayer 1 (5 units) + ResistanceToShell 0.1 (3 units) + ReverseThrust_Acc 0.25 (3 units) + RollDragFactor 2 (5 units) + SELength 2000 (5 units) + ShieldRatio 1 (1 units) + SideRoll_Length 0 (5 units) + SideRoll_Time 0 (5 units) + Size_Y 200, 300, 400, 600, 700 (5 units) + Slalom_CutoffRatio 0 (5 units) + Slalom_TurnCount_Max 0 (5 units) + Slalom_TurnCount_Min 0 (5 units) + SolidCutoff_LengthMax 0 (5 units) + SolidCutoff_LengthMin 0 (5 units) + SolidCutoff_Ratio 0 (5 units) + Through_AngleMaximum 0 (5 units) + Through_AngleMinimum 0 (5 units) + Through_CutoffRatio 0 (5 units) + Through_LengthMaximum 0 (5 units) + Through_LengthMinimum 0 (5 units) + Through_Time1Max 0 (5 units) + Through_Time1Min 0 (5 units) + Through_Time2Max 0 (5 units) + Through_Time2Min 0 (5 units) + ThrusterRatio 0.2, 1 (4 units) + TurnAttack_CutoffRatio 0 (5 units) + TurnAttack_DoubleRatio 0 (5 units) + TurnAttack_DoubleTimeMax 0 (5 units) + TurnAttack_DoubleTimeMin 0 (5 units) + TurnAway_Time_Maximum 0 (5 units) + TurnAway_Time_Minimum 0 (5 units) + Turn_AngularVelocity 0 (5 units) + UsingChaffRatio 0 (5 units) diff --git a/docs/re/live-unit-definitions.md b/docs/re/live-unit-definitions.md index 9cc5006..eca5d81 100644 --- a/docs/re/live-unit-definitions.md +++ b/docs/re/live-unit-definitions.md @@ -387,3 +387,42 @@ matches the loader's own miss value. off-by-one column in the dump reader — `f[3]` is the u32, `f[4]` the float — and decoding one by hand (`1128792000` = `0x43480000` = 200.0) showed the map had been right all along. + +## ✅ The whole map verified — 154/154 — and 101 defaulted fields read out + +Anchoring the key string to the **first store after the accessor call** (rather +than to the nearest store) makes the extraction exact: **159 fields**, of which +**125 are floats**. `verify_fieldmap.rs` now checks every one of them against the +live objects: **154 field values agree with the disc record, 0 disagree** across +five capital ships. + +Getting there needed one rule, and the ten last "mismatches" are what taught it: + +``` +AV_PitchPlus_Max disc 3.6 live +176 = 0.0628319 ( = 3.6° in radians) +MaximumBank_Normal disc 60 live +256 = 1.0472 ( = 60° in radians) +``` + +**Angle fields are degrees on disc and radians at runtime** — the incidental +observation from the previous pass, now confirmed as an exact conversion across +every angle field and every unit. + +With the map verified, the dump answers the brief's question for **101 fields** +that these records leave defaulted +([`captures/stage02-defaulted-field-runtime-values.txt`](captures/stage02-defaulted-field-runtime-values.txt)). +The shape of the answer: + +- **Angle limits default to 3.6°** (`0.0628319` rad) — every `AA_*`/`AV_*` + `Pitch/Roll/Yaw` `Min`/`Max`, identical across all five units. +- **Afterburner and turn-attack fields default to 0** (`AB_*`, `TurnAttack_*`, + `AA_AxisMode_*`, `AV_AxisMode_*`). +- **`FiringRange` defaults to 0**, matching the loader's own miss value exactly. +- **The ratio family is per-unit, not constant**: `HQRatio` 0.2 or 1, + `ThrusterRatio` 0.2 or 1, `AttackCraftPoint` 0.1 or 0.5, `DefencePoint` 0.1 or + 0.25, `DelayAdjustment` 0 or 2 — so these are *runtime values for units that do + not set the field*, not a single global default. +- **`Size_Y` takes that unit's own `Size_X`** (200/300/400/600/700). + +That is the `…Ratio` / `…Count` family the mission brief named, answered from the +running game, with the field identities read out of the title's own code rather +than guessed.