From 39954414f4ea397f47f597d9e7f75db9f400f4ca Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Tue, 18 Aug 2026 17:14:24 +0000 Subject: [PATCH] ship: pin the e106 ground truth as a multiset, not a set of names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `static_assembly_matches_runtime_capture` walks the capture's parts and looks each up in ours, so an extra static placement was originally invisible to it — that is how `e303_wep_01` sat here decoded 100x too large. `a21aa5f` closed that with a set-of-names check; BACKLOG.md still says otherwise, and it is wrong. One direction stayed open, though: a set cannot fail on a resource placed *twice* when the capture lists it once. That is not hypothetical — a duplicated instance is what a bad node walk emits, and the two legitimate duplicates here (`e106_eng_01`, `e303_wep_01`) are why the test had to special-case counts at all. So pin the whole multiset — 9 resources, 11 placements, against the capture's 8 dedup'd parts — which subsumes both hand-written counts and fails on an extra, a missing, or a duplicated placement. Refuted before believing: perturbing the expected table (declaring `e106_bdy_01` twice) fails with the real multiset on the left, so the assertion runs against the disc rather than skipping for want of SYLPHEED_ISO. The remaining `for want in &cap.parts` loop guards the table itself: editing it cannot quietly drop a part the runtime actually draws. --- crates/sylpheed-formats/src/ship.rs | 62 +++++++++++++++++++---------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/crates/sylpheed-formats/src/ship.rs b/crates/sylpheed-formats/src/ship.rs index 8abb5dd4..fe1cfc60 100644 --- a/crates/sylpheed-formats/src/ship.rs +++ b/crates/sylpheed-formats/src/ship.rs @@ -615,31 +615,51 @@ mod tests { } } } - // ── Extras: the direction this test could not previously fail in. ── + // ── Extras: the direction this test could not originally fail in. ── // The loop above walks the CAPTURE's parts and looks each up in ours, so - // a static placement with no counterpart was invisible to it — which is + // a static placement with no counterpart is invisible to it — which is // how a resource decoded 100x too large (`e303_wep_01`, 2026-08-12) sat - // here unnoticed. Pin the set instead: the capture legitimately misses - // repeated instances of a shared resource (vbase dedup), so `e303_wep_01` - // is expected; anything else appearing only in the static assembly is a - // regression. - let captured: std::collections::BTreeSet<&str> = - cap.parts.iter().map(|p| p.part.as_str()).collect(); - let extra: std::collections::BTreeSet<&str> = placed - .iter() - .map(|p| p.resource.as_str()) - .filter(|r| !captured.contains(r)) - .collect(); - let allowed: std::collections::BTreeSet<&str> = ["e303_wep_01"].into_iter().collect(); + // here unnoticed. Pin the whole **multiset** rather than the set of + // names: a set cannot fail on a *duplicated* instance of a resource the + // capture already lists, and duplication is exactly what a bad node walk + // produces. This subsumes the per-resource counts that used to be + // asserted separately below. + // + // Two deviations from the capture are expected, and only these two: + // * `e303_wep_01` x2 — a turret shared with `e101`, cross-mounted here. + // Absent from the capture because the F10 dump dedups by vertex base + // and both instances draw the one buffer. + // * `e106_eng_01` x2 — the mirrored engine nacelles, same reason. + let mut counts: std::collections::BTreeMap<&str, usize> = Default::default(); + for p in &placed { + *counts.entry(p.resource.as_str()).or_default() += 1; + } + let expected: std::collections::BTreeMap<&str, usize> = [ + ("e106_bdy_01", 1), + ("e106_bdy_02", 1), + ("e106_bdy_03", 1), + ("e106_bdy_04", 1), + ("e106_brg_01", 1), + ("e106_eng_01", 2), + ("e106_eng_02", 1), + ("e106_wep_02_01", 1), + ("e303_wep_01", 2), + ] + .into_iter() + .collect(); assert_eq!( - extra, allowed, - "static placements with no counterpart in the runtime capture" + counts, expected, + "the static assembly's placement multiset drifted from the e106 ground truth" ); - - // Multi-instance coverage the capture couldn't see (vbase dedup). - let count = |res: &str| placed.iter().filter(|p| p.resource == res).count(); - assert_eq!(count("e106_eng_01"), 2, "both engine nacelles placed"); - assert_eq!(count("e303_wep_01"), 2, "both shared turrets placed"); + // …and every name the capture lists must be in that table, so editing the + // table cannot quietly drop a part the runtime actually draws. + for want in &cap.parts { + assert!( + expected.contains_key(want.part.as_str()), + "{} is in the runtime capture but not in the expected multiset", + want.part + ); + } // NEITHER hull reflects: the twins' geometry is mirrored on the disc, // so both placements are proper rotations. This flipped on 2026-08-12 — // while both twins decoded to one buffer, `apply_twin_mirrors` had to