diff --git a/TODO.md b/TODO.md index 8c0c089..81d19d4 100644 --- a/TODO.md +++ b/TODO.md @@ -59,9 +59,10 @@ with MegaMek except where a deviation is registered. deviation, so the shape of the work is known. 10. **Retire `bridge/` for battle value.** Once 8 and 9 exist the JDK container leaves the build, which is `plan/unit-library.md`'s exit criterion. -11. **The equipment MegaMek synthesises while loading.** 5,534 units differ - from `MekSummary.equipmentNames` only because of it. It blocks equipment - filtering conformance and probably hides battle value bugs. +11. **The equipment MegaMek synthesises while loading.** Done bar a long + tail: 10,440 of 10,509 designs carry the list `MekSummary.equipmentNames` + holds. What is left is wiring it to `UnitFacets::loadout` and the + database. 12. **The nine registered disagreements.** Now that they are named they can be taken one at a time; the Super-Griffin and the Jabberwocky are the two most likely to be a rule rather than a broken file. @@ -394,13 +395,10 @@ The long pole, and the reason `helm-core` does no I/O. Tracked as - [ ] **The equipment MegaMek synthesises while loading** rather than reading from the file, which battle value counts. Recorded on `UnitFacets::loadout`. - Half done. `helm_core::equipment_list` models the part a Mek's header - lines imply - one structure entry, one armour entry, every heat sink - including the ones inside the engine, and a Clan design's structural - CASE - and designs whose list is MegaMek's went from 1,883 of 10,988 to - 4,860. `crates/helm-facet/tests/loadout.rs` holds the figure and reports - what makes up the rest, which is now infantry weapons, battle armour - gear and aerospace bays rather than anything Mek-shaped. + `helm_core::equipment_list` models what a design's header lines imply + and its slots do not carry, and `crates/helm-facet/tests/loadout.rs` + holds the figure. It was 1,883 of 10,988 before any of the loader was + modelled. Battle armour too: the jets are declared in ``, which nothing read, and the four anti-Mek attacks are invented for a suit @@ -410,8 +408,38 @@ The long pole, and the reason `helm-core` does no I/O. Tracked as magnetic clamp. Suits carrying MegaMek's list went from 276 of 1,188 to 1,184. - Infantry is untouched: a platoon's weapons are declared in a way of - their own, and all 1,792 differ. + **10,440 of 10,509 now agree.** 479 designs are left out of the + measurement rather than measured: their display name belongs to two + designs - an Arrow IV turret exists in an Inner Sphere and a Clan + version under one name - and nothing in the dump says which oracle row + is which. + + What the rest of the gap was, in the order it was closed: + + - A `.blk` line is a whole item and a `.mtf` line is one critical slot. + Dividing a `.blk`'s lines by the item's slot count read six field guns + as three, and it was wrong for every vehicle, every aerospace fighter + and every platoon at once. + - A platoon declares two weapons and MegaMek mounts one: the secondary + where more than one trooper a squad carries it, the primary + otherwise. Plus the three climbing attacks, which an + elephant-mounted platoon gets only two of. + - A design's structure and plate are read against its own tech base. + `Endo Steel` names two entries and taking the first gave 972 Clan + designs an Inner Sphere frame beside their own. + - A one-shot launcher's round, a Clan design's structural CASE where + the location holds something that actually explodes, heat sinks of + the tech base the *line* names, and one item where a frame or a set + of talons fills a dozen slots. + - A large aerospace unit fires in bays, marked `(B)` in its file. Which + sort of bay is a property of the weapon, so `DumpEquipment` carries + it now. + - The three spellings of a magazine's load, and a variable-size item's + count, which is its slot lines divided by the size on them. + + The 69 still differing are a long tail: 21 Clan designs whose CASE + count is still one out, a few variable-size cargo bays, and single + designs with their own reasons. Not yet wired to `UnitFacets::loadout` or the database. A synthesised entry has no location and the equipment table stores one row per mount diff --git a/crates/helm-core/src/metrics.rs b/crates/helm-core/src/metrics.rs index 5b4e5c7..40e2b4c 100644 --- a/crates/helm-core/src/metrics.rs +++ b/crates/helm-core/src/metrics.rs @@ -257,6 +257,15 @@ pub fn mounted_items<'a>(unit: &Unit, catalogue: &'a Catalogue) -> Vec<(&'a Equi // size is counted by dividing the slots, and one without a size - a hatchet // takes a slot per fifteen tons - is counted one per location. let mut slots: BTreeMap<&str, (i64, usize)> = BTreeMap::new(); + // Slots that state a size, counted on their own. A variable-size item + // fills a slot per ton, so how many the design carries is the lines + // divided by the size on them: a Lumberjack LM1A writes `Cargo:SIZE:1.0` + // eight times and has eight one-ton bays, while a MuckRaker GMMM-2 + // writes `Cargo:SIZE:11.0` twenty-two times and has two eleven-ton ones. + // + // A variable-*criticals* item - a hatchet, sized by the Mek's own + // tonnage - states no size and is one item however many slots it fills. + let mut sized: BTreeMap<&str, (i64, f64)> = BTreeMap::new(); for (location, lines) in &unit.criticals { let mut here: BTreeMap<&str, ()> = BTreeMap::new(); for line in lines { @@ -268,6 +277,11 @@ pub fn mounted_items<'a>(unit: &Unit, catalogue: &'a Catalogue) -> Vec<(&'a Equi if let Some(entry) = catalogue.resolve(&name) { let key = entry.internal_name.as_str(); slots.entry(key).or_default().0 += 1; + if let Some(size) = slot_size(part) { + let counted = sized.entry(key).or_insert((0, size)); + counted.0 += 1; + counted.1 = counted.1.max(size); + } here.insert(key, ()); } } @@ -318,6 +332,10 @@ pub fn mounted_items<'a>(unit: &Unit, catalogue: &'a Catalogue) -> Vec<(&'a Equi if entry.spreadable { return Some((entry, 1)); } + if let Some((lines, size)) = sized.get(name) { + let per_item = (size.ceil() as i64).max(1); + return Some((entry, (lines / per_item).max(1))); + } let count = match entry.criticals.filter(|c| *c > 0) { // Rounded up, the way `helm_bv::loadout` rounds it: a Gauss // rifle is seven slots and takes four on a superheavy. @@ -500,6 +518,16 @@ pub fn equipment_list<'a>(unit: &Unit, catalogue: &'a Catalogue) -> Vec<(&'a Equ items } +/// The size a slot line states for a variable-size item: `Cargo:SIZE:11.0`. +fn slot_size(slot: &str) -> Option { + let at = slot.to_ascii_uppercase().find(":SIZE:")?; + slot[at + ":SIZE:".len()..] + .split(|c: char| !(c.is_ascii_digit() || c == '.')) + .find(|part| !part.is_empty())? + .parse() + .ok() +} + /// The round a one-shot launcher arrives loaded with. /// /// A one-shot launcher's ammunition is part of the weapon rather than a bin in diff --git a/crates/helm-facet/tests/loadout.rs b/crates/helm-facet/tests/loadout.rs index a6a8372..acb6ab1 100644 --- a/crates/helm-facet/tests/loadout.rs +++ b/crates/helm-facet/tests/loadout.rs @@ -23,7 +23,7 @@ use std::path::PathBuf; /// Out of a smaller population than the library: 479 designs share a display /// name with another and are skipped, because nothing in the dump says which /// oracle row is which design. -const DIFFERING: usize = 83; +const DIFFERING: usize = 69; #[test] #[ignore = "needs a MegaMek install and a bridge dump; set HELM_MEGAMEK and HELM_BRIDGE"]