re: audit the legacy IDXD reader against the real field table — and fix a test that encoded its error
With the record table decoded there is finally a ground truth to check the
old string-pool reader against. It infers `key -> value` from pool adjacency,
which is a consequence of how records are written, not a rule of the format.
Verified by hand against the disc, with an independent parser:
* `FCSRange` = 500000.0 — the module docs' own canonical example of a field
"left at its default" that "omits the value string".
* `ShieldRatio` = 1.0, where `tests/pak_idxd_disc.rs` asserted None with the
comment "a defaulted/omitted field must be None". That test encoded the
false belief; it now keeps the None as a deliberate characterisation of the
legacy reader, with the true value asserted beside it.
* `get_raw("Model")` on GP_HANGAR_ARSENAL returns the first record's model for
every record — silent corruption, not an absent value. New test pins four
records that disagree with it.
The cause is the flat API having no way to name a record: only 548 of 6325
objects have one. `HP` on the DeltaSaber answers 1000.0, the hull, while 63
Turret_* records each carry their own 100.0 (measured — a first draft said 34,
taken from a report rather than from the disc).
Disc-wide rates are recorded as single-source and labelled as such: get_raw
52% wrong, typed getters 38% miss, but 100% correct on single-record objects.
Also records a negative result: the 504 unnamed field keys were NOT recovered.
A 572464-string dictionary and 73191 variants gave 0/42. The key deltas do
prove the preimage ends with the two decimal digits.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
@@ -80,8 +80,54 @@ fn deltasaber_craft_stats() {
|
||||
assert_eq!(obj.get_i64("Turn_AngularVelocity"), Some(100));
|
||||
assert_eq!(obj.get_f32("ChargeSpeed"), Some(800.0));
|
||||
assert_eq!(obj.get_f32("RadarRange"), Some(500000.0));
|
||||
// a defaulted/omitted field must be None, not a neighbouring key
|
||||
// ❌ NOT a defaulted field — this assertion used to claim it was. The record
|
||||
// table says ShieldRatio = 1.0; the legacy string-pool reader simply cannot
|
||||
// see it. Kept as a *characterisation* of that reader, with the truth beside
|
||||
// it, so the blind spot stays visible instead of being read as "no value".
|
||||
assert_eq!(obj.get_f32("ShieldRatio"), None);
|
||||
let generic = obj.record("Generic").expect("Generic record");
|
||||
assert_eq!(generic.get("ShieldRatio"), Some("1.0"));
|
||||
// Same for the module docs' canonical "field with no value".
|
||||
assert_eq!(generic.get("FCSRange"), Some("500000.0"));
|
||||
|
||||
// The values above are real, but each lives in a *different* record, which
|
||||
// the flat API cannot express: these three are Generic / Maneuver / Shield.
|
||||
assert_eq!(generic.get("HP"), Some("1000.0"));
|
||||
assert_eq!(obj.record("Maneuver").unwrap().get("Acceleration"), Some("600.0"));
|
||||
assert_eq!(obj.record("Shield").unwrap().get("ChargeSpeed"), Some("800.0"));
|
||||
|
||||
// And `HP` is not one number: 63 turret records carry their own (all 100.0).
|
||||
let turrets = obj
|
||||
.records()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|r| r.name.starts_with("Turret_"))
|
||||
.count();
|
||||
assert_eq!(turrets, 63);
|
||||
assert_eq!(obj.record("Turret_000").unwrap().get("HP"), Some("100.0"));
|
||||
}
|
||||
|
||||
/// The legacy reader returns one object-wide answer for a per-record field, and
|
||||
/// it is silently *wrong* rather than absent. The hangar's weapon → model map is
|
||||
/// the clearest case: every record reads back as the first one.
|
||||
#[test]
|
||||
fn legacy_reader_flattens_per_record_fields() {
|
||||
skip_without_disc!(root);
|
||||
let arc = PakArchive::open(root.join("dat/GP_HANGAR_ARSENAL.pak")).unwrap();
|
||||
let bytes = arc.read_by_hash(0x8f72_ddde).expect("entry present").unwrap();
|
||||
let obj = IdxdObject::parse(&bytes).unwrap();
|
||||
|
||||
// One answer for the whole object …
|
||||
assert_eq!(obj.get_raw("Model"), Some("rou_f001_wep_33_hangar"));
|
||||
// … while the records disagree with it and with each other.
|
||||
for (record, model) in [
|
||||
("Designator_LH", "rou_f001_wep_59_hangar"),
|
||||
("Laser_Mine_B9L", "rou_f001_wep_29_hangar"),
|
||||
("Saber_LG1", "rou_f001_wep_14_hangar"),
|
||||
("Chaff_Flare_Dispencer", "rou_f001_wep_71_hangar"),
|
||||
] {
|
||||
assert_eq!(obj.record(record).unwrap().get("Model"), Some(model));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user