TODO #
State: the library reads and the MCP answers questions about it. 10,988 units parse with no failures, all of them carry MegaMek's computed figures, and six tools expose the Meks over MCP. What is weak is what can be asked: the filters describe a unit's paperwork — tonnage, era, role — and say almost nothing about how it fights. Battle value and construction are still borrowed from the Java bridge.
The epics these belong to are in headquarters: plan/unit-library.md,
plan/unit-search.md, plan/unit-rules.md.
Several items below were prompted by reading MekBay, MegaMek's own web client, for what it lets a person filter on. It is GPL-3.0 and helm is not, so what was taken is the vocabulary and the shape of the problem, never code.
What to do next, in order #
Twenty things, ranked. The tiers are the argument: the first finishes what
plan/unit-rules.md already promises, the second widens what helm can score at
all, and the third is what makes any of it reach a player. Everything below
appears again in its own section with the detail; this is the order to take
them in and the reason for it.
Finish what unit-rules promises. The epic's exit criterion is a force's battle value computed the same way in the browser and on the server, agreeing with MegaMek except where a deviation is registered.
- Read C3 networks from a
.muland score them. The arithmetic is written; nothing reads which unit is whose master, sohelm forcescores every unit as though it were alone and a networked lance is reported five percent light. - Double verification across the wire. Named in the epic: the same crate
compiled twice, agreeing. The smoke test checks a few designs; the proof is
the whole library scored in Node and natively with no disagreement. It is
the reason
helm-bvdoes no I/O, and it is currently unproven. - Battle value for a varied unit.
Conditionmodels damage and not variation. A player swapping an LRM bin for inferno rounds, or changing an omni pod, is exactly what the epic says players will do - and it is the largest gap between what helm computes and what the product needs. - Pin the rounding. Also named in the epic. Battle value is full of half-point steps and nothing proves the two builds round alike, or that a total does not depend on the order floats were added in.
- Settle the two compensating explosive errors. B-Pods against CASE II ammunition: both sides are wrong and the fresh figure is right by luck. Wants two oracle designs before either can be corrected.
Widen what helm can score.
- Vehicle battle value.
CombatVehicleBVCalculatorupstream. Scenarios already carry tanks -MercBrawl,example.mul- andhelm checkanswers "cannot be scored" for every one of them. - Battle armour and infantry. The same argument with different
calculators, and the reason
LegAttackandSwarmMekare synthesised. - C-bill cost. The second of the three figures borrowed from the bridge, and what anything economic needs.
- The Alpha Strike conversion. The third. It already has a registered deviation, so the shape of the work is known.
- Retire
bridge/for battle value. Once 8 and 9 exist the JDK container leaves the build, which isplan/unit-library.md's exit criterion. - The equipment MegaMek synthesises while loading. 5,534 units differ
from
MekSummary.equipmentNamesonly because of it. It blocks equipment filtering conformance and probably hides battle value bugs. - 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.
Make it reach a player. These belong to epics in headquarters as much as to helm.
- Decide how a design reaches the page, and build it. The one open
decision, and everything in
plan/unit-search.mdwaits behind it. - Compile
helm-facetto wasm and filter with it in the browser. The epic says it outright: run the same predicate, do not reimplement. Two filter implementations is how "filtering means the same thing everywhere" dies. - The redistribution question. MegaMek's data is CC BY-NC-SA and serving an index of it to browsers is redistribution. It needs a decision and an attribution mechanism before 13 ships.
- Provenance on every record. Which MegaMek, which producer, which
RULES_VERSION. The constants exist and nothing writes them into the database or into an ATProto record, so a stored figure cannot be told from one computed against a different MegaMek. - The attribution and the repair list on a match page.
plan/after-action.md. The pieces are built and cross the wasm boundary; what is missing is the screen that reads them. - Damage that persists between matches.
plan/campaign.md. The mechanical half exists; storing a force's condition as a record and reading it back is the rest, and the construct-write-read round trip supports it. - Formation validation.
list_formationsandvalidate_formationagainst the Campaign Operations blueprints - "is this a legal Fire Lance". Validation before generation, and it needs no faction data: it is role and count, both of which helm holds. - Ingest
data/forcegenerator/. 40 era files of per-faction availability weights,factions.xml, and 61 faction rulesets that are a small DSL of their own. This is the foundation for force generation, faction filtering and era-correct scenarios, and nothing above it should be attempted until it exists.
Derived combat metrics #
The highest-value gap. These are what make a filter describe a fighting
machine rather than a spec sheet, and every one is computable from data helm
already holds: unit_equipment joined to the equipment catalogue, which
carries heat, damage and ranges per weapon.
-
The maximum is two points a location for every point of internal structure, except the head, which takes nine whatever it is bolted to and twelve on a superheavy — so it is not twice the structure total. Front and rear plate share a torso's allowance rather than each having one. The figure is in no unit file, so it cannot be conformed against MegaMek the way a battle value can. What holds it is that no design may exceed it: over all 4,294 Meks none does and 815 sit at exactly 100, which is the check both ways round — a maximum set too low would put designs over it, and one set too high would leave nothing at the top. This is declared data rather than derived from the catalogue, so unlike the five above it the column is filled whether or not a build had one. It is null for everything that is not a Mek, the table being a Mek's.
Filters the data already supports #
Cheap: the columns exist and the MCP does not offer them.
Equipment filtering #
-
Two upstream behaviours are reproduced on purpose and pinned by tests so nobody tidies them away. `Ultra` matches `ultraa` with two As, which is not a typo: it is matching `ISUltraAC5`, where "ultra" runs into the "AC". And a class leaf **sums quantities across matching entries** where a name leaf does not, so two medium lasers and a large laser satisfy "at least 3 energy weapons" while no name leaf asking for 3 would.
Damage at range #
-
This is **maximum** damage where `firepower` is average, and the two are not interchangeable: a full LRM 20 salvo is 20 here and 12 there. The first version scored every ammunition-fed weapon at zero and nothing looked broken - an Atlas came out at 20 instead of 72, because a `.mtf` weapons block lists no ammunition and the lookup therefore never matched. Ammunition is now found in the critical slots, where a `.mtf` actually puts it. It moved the count of Meks meeting the Assault Lance criteria from 786 to 1,765, so it was not a rounding error. Two more of the same shape came out of the formation oracle. Artillery was scored at zero, because a weapon was recognised by having an average damage and artillery has a sentinel instead of one - MegaMek asks `instanceof WeaponType`, and so does `EquipmentEntry::is_weapon` now. And an omni's ammunition was invisible: a `.mtf` writes `(OMNIPOD)` into the slot itself, so the name in the slot is not the name of the thing in it. `helm_core::strip_slot_markers` is that cleaning, shared with `helm_bv::loadout` rather than written twice. -
Two more of the same shape came with it. A weapon whose damage is a sentinel rather than a number - a Snub-Nose PPC does ten, eight and five by band - was counted as nothing, so `EquipmentEntry::damage_at` reads the bands the way `WeaponType.getDamage` does. And a one-shot launcher arrives loaded: its rounds are part of the weapon rather than a bin in a slot, so MegaMek makes the ammunition while it reads the design and helm found none, which scored a Firestarter FS9-C's six rocket launchers at zero. Measured by the formation oracle: 179 damage disagreements of 42,453 requirement checks, now 5. -
Deliberately not hardened armour, which costs a Mek a point in the battle value calculation and nothing in what MegaMek reports about the design. The two figures are not the same figure. -
That took the formation oracle's damage disagreements from 5 to 0, and the test no longer forgives any: 42,453 requirement checks over 477 Meks, and every figure helm computes for them is MegaMek's. -
Deliberately not the same figure `helm_bv` works out. Battle value runs under a setting that says `noModularArmor`, so a Koshi (Mist Lynx) Z is scored jumping six there and reported jumping five here. Two figures, both right, and a test each.
Force value #
-
A `.mul` records the network as pointers rather than as a list - a slave names its master's `c3UUID`, and a C3i unit names every other member in `<c3iLink>` elements - so `helm_force::c3_bonuses_linked` turns the pointers into networks and pays each one for its own members. A force can hold more than one: two lances with their own masters are two networks, and a chain of masters under a master is one. `helm force` reports the share in a column of its own. A Gunslinger GUN-2ERD and a Malice MAL-YZ wired together are 2,423 and 2,982 apart and +270 each on the network, which is five percent of both. A Peacekeeper carrying a slave set with no master in the force is paid nothing, because a set with nobody to talk to is not a network. -
The table is transcribed rather than derived - there is no formula behind it - and reading it the wrong way round is easy: `[3][4]` is 1.32, not the 1.44 at `[3][3]`. My own first test expectations were wrong that way and the implementation was right.
Battle value #
The long pole, and the reason helm-core does no I/O. Tracked as
unit-rules in headquarters' plan/.
-
Two things fell out. The tech-base marker is spelled in three places - `XL (Clan) Engine`, `XL Engine(IS)`, `IS Endo Steel` - and reading only the first misses 116 designs. And patchwork armour writes its type in front of the points, `LT armor:Clan Standard(Clan):26`, which the parser rejected as non-numeric: all five patchwork designs were reading as almost unarmoured, a Zeus-X at 28 points where MegaMek says 247. Total armour now agrees with MegaMek on all 10,749 designs it reports one for. -
A Shadow Hawk SHD-2H is 1,064 as built and 1,069 with acid rounds in its SRM bin. Both figures are MegaMek's, and both are now helm's. `bridge/DumpMul.java` is the oracle, and it is the first one that reads a file rather than shooting a design itself - which makes it the harness for anything expressed in a `.mul` rather than derived from a `.mtf`. The fixtures in `crates/helm-bv/tests/mul/` are this repository's own, because MegaMek ships no file that varies a unit. An unknown name in a slot is ignored rather than treated as an empty one: a newer MegaMek's spelling should cost nothing, and the design's own answer is a better guess than a hole. An omni pod swap - different equipment rather than different rounds - should ride the same mechanism and has not been checked. -
Java's `Math.round` sends a half up; Rust's `f64::round` sends it away from zero. Those are the same thing until a figure is negative, and every figure rounded today is positive - so the rule is written down once in `helm_core::round_half_up` rather than assumed at eleven call sites, and a term that comes out negative later cannot quietly disagree by a point. The test measures rather than asserts a limit: how many designs sit within a millionth of a boundary, how many exactly on one, and whether any of those is a *point* away from MegaMek - which is what a rounding disagreement looks like. None is. The only near-boundary design that differs is the Uni ATAE-70, and it is 43 out because its file allocates one Thumper's slots to two Thumpers. -
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. Battle armour too: the jets are declared in `<slotless_equipment>`, which nothing read, and the four anti-Mek attacks are invented for a suit that can climb a Mek. Which can is `BattleArmor.canMakeAntiMekAttacks`: lighter than a heavy suit, not a quad, and with two hands free - two armoured gloves or two basic manipulators, or one battle claw, or a 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. Not yet wired to `UnitFacets::loadout` or the database. A synthesised entry has no location and the equipment table stores one row per mount with one, so switching the facet over is a change to what a search returns and wants its own evidence. -
[~] Defensive battle rating. 4,235 of 4,294 Meks match MegaMek exactly. Armour at 2.5 a point and internal structure at 1.5, each through its own type multiplier, plus the gyro at half a ton, defensive equipment at catalogue value, minus 15 a slot for ammunition that can cook off, all through a movement factor of 1 + TMM/10.
Every multiplier was fitted against MegaMek across the library rather than transcribed. Three came out worth commenting: an Inner Sphere XL engine is 0.5 where a Clan XL is 0.75, because the Inner Sphere version dies with either side torso; Ferro-Fibrous is 1.0 like standard plate, since it buys more *points* and those are already counted; and CASE is better modelled as "carries it anywhere" than "carries it in that location", because a design that fits CASE fits it where the ammunition is. What is left is a long tail of defensive equipment - 1,655 designs, most within 20%. Counting equipment per critical slot rather than per item was worth 200 designs on its own: a large shield fills seven slots and was being paid for seven times. -
[~] Offensive battle rating. 4,193 of 4,294 match. Weapons at catalogue value, halved once the design runs out of heat, plus ammunition capped by the weapons that eat it, plus the Mek's own mass, all through the speed factor.
The weapon ordering is the part worth knowing: cold weapons first whatever they are worth, and only then by descending battle value. A machine gun costs nothing to fire, so it never competes for the heat budget, and sorting on value alone charges it as though it did. -
[~] Battle value end to end. 4,166 of 4,294 designs match MegaMek exactly.
helm bv-report --clustersgroups what is left by what the designs have in common, which is how each of the remaining rules was found - the ranked list of worst offenders only ever shows whatever is rarest. The two ratings are summed and the total rounded once, which is the last place a half point can change an answer. -
It is one calculation and not two. MegaMek has a `BVCalculator` and a subclass per unit type, and `CombatVehicleBVCalculator` changes five things: the plate is worth less for being on a vehicle, the frame is a tenth of the tonnage in each location that wears armour, the tonnage counts for half, there is no heat budget to run out of, and only the front hull has a say in which way the machine faces. So `Mek` became `Machine`, it carries a `Chassis`, and the five places that differ ask it. What the oracle found that reading the rules would not have: a trailer with a stealth system is not hard to hit, because a machine that cannot move gets no movement modifier at all; a superheavy hull numbers its locations differently and MegaMek's facing rule is written against the numbers rather than the locations, which is in `crates/helm-bv/UPSTREAM.md`; and a vehicle's ammunition carries no explosive penalty - none of the 1,256 reports has the line. -
- **LAMs** (23 designs) are declined rather than scored, by decision: lance.blue will not field them. They need their own movement modes - airmech and fighter - feeding both the speed factor and a heat efficiency three points higher, and a LAM scored as a Mek is wrong rather than approximately right. Reinstating them is deleting the guard in `battle_value` and writing those modes. -
`crates/helm-wasm/smoke.mjs` drives it from Node, which is the only thing that exercises the interface a browser actually calls through: cargo build -p helm-wasm --target wasm32-unknown-unknown --release node crates/helm-wasm/smoke.mjs <equipment.jsonl> <design.mtf> An Atlas AS7-D comes back 1897 fresh, 1564 with its torsos stripped, 2504 flown by a veteran and 1631 by a green crew. -
`bridge/DumpDamaged.java` is the oracle, because a `.mtf` describes a design as it leaves the factory and nothing else. Six designs in six states each, and the rows are worth reading rather than only passing: | what was done to it | Atlas AS7-D | Locust LCT-1V | |---|---|---| | nothing | 1897 | 432 | | plate stripped | 985 | 208 | | one torso opened | 1714 | 390 | | half a torso's frame gone | 1877 | 426 | | right arm shot off | 1719 | 402 | | magazines run dry | 1890 | **451** | Running a Locust dry *raises* its battle value: an empty bin no longer explodes, and the defensive penalty that removes is worth more than the rounds were. Reasoning from the rules would have got that backwards, and two of these six states found real bugs - a heat sink shot out of an arm is cooling the `heat sinks:` line still counts, and a hit anywhere in a multi-slot weapon destroys the whole weapon rather than the slot. **Widened, and it found three more.** Nine designs in fourteen states - 126 rows - covering the systems whose loss changes a figure: engine, gyro, heat sinks, jump jets, arm actuators, MASC, and the leg joints. Six states were added at once and three of them disagreed straight away: - **An engine critical is five points of heat a turn**, and MegaMek takes it off the heat efficiency after the movement heat rather than through it: `mekHeatEfficiency -= getEngineHits() * 5`. A standard fusion engine fills six centre torso slots and all six count. helm charged nothing, and an Atlas with its engine hit was 1897 where MegaMek says 1631. - **Jets that have been shot off are not jets the design never had.** The `jump mp:` line is the fallback for a design carrying no jets this crate can resolve, and a Catapult whose jets were destroyed fell through to it and was scored as still jumping four. - **A hatchet is never dropped for heat.** MegaMek builds its heat list from `getTotalWeaponList`, which a physical weapon is not in - it is scored with the equipment at full value. A vibroblade *is* in it, added by name with the heat its motor makes. helm halved every melee weapon once a design ran out of budget, which no fresh design in the library is far enough over to show. One of the six was the oracle's own fault rather than helm's: destroying "every heat sink" also destroyed the ones inside the engine, which fill no critical slot, cannot be shot out in the game, and have no way to be written into a `.mul`. An oracle for a state helm can never be asked about is not an oracle. It now damages only what a critical hit could reach. 126 of 126 rows agree, and the undamaged library is unchanged at 4,252. **Widened again, this time across designs.** `bridge/DumpDamaged.java` takes `every:N` and samples the library rather than naming designs, which is how the shapes and construction a hand-picked list never covers get asked about. At `every:40` that is 1,638 states, and it found two more: - **A weapon too big for one location** has the rest of itself in the next one, and it is one weapon. A Victor VTR-9Ka's autocannon fills nine slots of an arm and one of the torso beside it; blowing the arm off left helm counting a whole autocannon that was no longer there, worth 465 points of offence. The halves are now paired - but only when there are exactly two of them and they account for the whole item. A Chameleon Light Polarization Shield is spread over half a design a slot at a time *by construction*, and pairing on the name alone took a Scarecrow's shield out with its arm. - **An engine only runs hot when it is hit if it is a fusion or fission plant.** An internal combustion engine takes the critical and makes no extra heat, so a SecurityMech with its engine shot out is worth what it was. -
- **A blown-off arm holding a big gun**, +8 to +9 defensive on a Cerberus MR-V3, an Ebony MEB-12, a Huron Warrior and a Thunder Hawk TDK-7S. All four carry a Gauss Rifle or a TSEMP Cannon in the arm, which suggests an explosive penalty MegaMek keeps charging for a weapon that is gone. - **Modular armour on a stripped design**, +97.5 on an Enforcer III ENF-7D - exactly 39 points of plate at 2.5. MegaMek counts modular armour as part of the location's armour, so stripping the location takes it too; helm adds it on top of a location it has already zeroed. - **A Devastator DVS-X10 MUSE EARTH's movement gear**, hundreds of points out on a hip or leg actuator hit and a few points out on a gyro hit. An XTR one-off, so it is likely a movement system helm reads and MegaMek recomputes differently once the legs are damaged. - **A Scarecrow UCU-F4 run dry**, 3 points of defence. The smallest of them and probably an explosive penalty on a magazine one of us empties and the other does not. **Three more, all the same mistake in different clothes: reading what a design was built with where MegaMek reads what is left, or the reverse.** - A **torso-mounted cockpit** counts its centre torso's plate a second time, for keeping the pilot alive rather than the Mek - and it has to be the plate that is still there. A stripped Enforcer III ENF-7D was being paid twice for armour it no longer had, 97.5 points of it. - An **armoured system slot** is worth something, and stays worth it when the slot is shot out: the plating is on the machine whether or not what it was protecting still works. helm stopped counting, so a Banshee BNC-11X with its arm blown off came out 36 light. - **Modular armour** goes the same way, which is the opposite of what it looks like: MegaMek pays for the pack whether or not the location holding it is still attached. And one that was not a rule at all: a Banshee writes `Hip (ARMORED)`, and the leg-damage rule matched the bare name. The design walked as though its hip were whole and came out three hundred points heavy. -
Same format and same reader, so nothing downstream changes: | | bridge dump | shipped | |---|---|---| | raw | 6.0 MB | 1.7 MB | | gzipped | 0.32 MB | 0.09 MB | | parsed in a browser | 216 ms | 88 ms | Checked by scoring the whole library from it: 4,252 of 4,294 designs match MegaMek either way, which is the same figure to the design. That check is a test now rather than something done once. The saving is only safe while the fields it drops are fields the rules never read, and "never read" is a claim about every rule in the crate rather than the ones somebody remembered - so every design is scored twice, once from each catalogue, and the two have to agree. The list of kept fields moved to `helm-bridge` beside the reader that makes it true. -
`helm force <force.mul>` scores one. Two files MegaMek itself wrote: | | fresh | from the file | |---|---|---| | Shadow Hawk SHD-2H | 1064 | 738 | | Griffin GRF-1N | 1272 | 694 | | Orion ON1-VA (untouched) | 1328 | 1328 | Parsed by hand like the other MegaMek formats. Three things the format does that a reader has to know: slots are numbered from one where everything else here counts from zero, `points` is sometimes a word rather than a number - `Destroyed` is none left and `N/A` is a location that never had any - and an ejected MekWarrior is written as another entity in the same file, so a force has to tell people from machines. -
Two things make the figures steady enough to assert on. Every task is timed in each round rather than one task to completion, so a busy minute costs all of them a round instead of making one look slow. And each keeps its fastest round, because a shared machine has a floor and no ceiling. Run-to-run drift is then under a tenth, against a gate of a fifth. Nine operations now, not five: the repair list, the upload check, and both ways of wearing a design down. The two searches are the expensive ones by a long way - wearing to a figure is 21 times a design read and a battle is 37, because each is dozens of scorings rather than one - which is worth having recorded before anything tries to run one per frame. Held only in release: an unoptimised build is several times slower and the two sets of figures are not comparable, so a debug run prints them and checks nothing. `HELM_PERF_BLESS=1` re-records, and a commit that moves a figure should say why.
How a design reaches the page #
The one integration question still open, and the only thing on this list that
wants a decision rather than work. A .mul names a design by chassis and
model and carries nothing about what is in it, so the page needs the designs
from somewhere regardless.
Measured rather than guessed. About three quarters of a .mtf is prose -
overview:, history:, deployment: - and stripping it leaves every one of
the 4,294 battle values identical:
| how | per design | whole library |
|---|---|---|
.mtf as shipped |
8.9 KB median | 31.9 MB zip |
.mtf with the prose stripped |
2.6 KB median | 0.56 MB gzipped |
| a minimal JSON of the fields the rules read | 2.0 KB median | 0.45 MB gzipped |
Which makes "ship the whole library and look up by chassis and model" the option that retires the question instead of answering it: 0.56 MB beside the 0.09 MB catalogue is under 0.7 MB for everything a page could ask about, and it can be fetched in the background while somebody browses.
The JSON form saves 0.6 KB a design and costs a format: a new encoder in
helm, a new decoder in helm-wasm, and both able to drift from the .mtf
parser that conformance actually tests. Not worth it at that price.
What MegaMek keeps units in #
Four readers, and that is the whole set - megamek/common/loaders in 0.51.0:
| format | what it is | count |
|---|---|---|
.mtf |
one Mek design, line-oriented text | 4,294 |
.blk |
every other unit type, from tanks to warships | 6,694 |
.mul |
a force in a state, XML - not a design format | interchange |
units.cache |
a Java-serialised index: name, tonnage, BV, cost | 1 |
Older MegaMek had HeavyMetal Pro importers (.hmp, .hmv); they are gone.
MegaMekLab reads and writes .mtf and .blk and has no format of its own, so
it is not a fifth thing to support.
What the web UI needs from a .mul #
The format lance.blue works in, so these are the jobs it has to do. Written down because the first of them changes what the writer has to be, and that is not obvious from any one of them.
-
Storing a player's file and handing it back would have been data loss with no error and no way to notice. Each unit now keeps the element it was read from, and writing starts there and changes only what the model says - so a camo, a portrait, an `externalId` and any tag a later MegaMek adds all survive. A location says `Destroyed` rather than a nought this crate invented, and a slot still says which equipment is in it. -
Most of the checks are about what the *reader* forgave. A location index it cannot place is skipped, an unparseable figure is left out, more structure than a design has is clamped when it is scored - all right for a reader, since a force that is nine tenths readable is worth reading, and all wrong for an upload, where the nine tenths get stored and nobody is told about the last tenth. Two of the checks can only be answered from the document, because by the time it is a model the evidence is gone. Three more since: six wounds is a dead MekWarrior rather than a hurt one, a slot marked as an empty magazine that holds a laser is a file describing something that cannot be, and a slot shot out that was empty to begin with is one written against a different design. The magazine one matters most because it is silent - the reader drops the slot either way, so the design comes out missing a weapon nobody shot. A complaint says whether it is about the file or about us. A force of tanks is a good file helm cannot score yet, and refusing it would be charging a player for our own gap - so only the file's own faults fail the command. The guard against the obvious failure - a check that refuses good files - is every `.mul` the install ships: 99 machines, no complaint about any of them. Four name a design MegaMek does not ship, which is a fault in MegaMek's own `example.mul` and is written up in `UPSTREAM.md`. Not reachable from a page yet: the check needs the design to compare against, and how a design gets to the page is the decision still open below. Everything here is `helm-core` and `helm-unitfile` only, so it crosses when that is settled. -
A rewrite is an *edit* of the document that arrived, never a rebuild of one from the model. Four separate losses came from getting that wrong - dropped attributes, dropped `<force>` elements, a rewritten version string, reordered locations - and each was silent. The conformance test now compares the whole document element for element and attribute for attribute across every `.mul` the install ships, because comparing the *model* only proves the parts helm understands survive, which is the easy half. What the metadata has to carry, so a stored figure can be told from one computed against something else: `helm_bv::CONFORMED_TO_MEGAMEK`, the MegaMek these rules agree with, and `helm_bv::RULES_VERSION`, the build that produced the figure. A conformance test now refuses to let either drift from what `conformance.txt` was actually measured against. -
It bisects on plate and then on the frame under it, both spread evenly across the locations, because armour is most of the defensive rating and is the one term the figure moves smoothly with. It touches no magazine and shoots nothing out: an empty bin stops being worth anything *and* stops exploding, so that lever does not have a direction and a search cannot bisect on it. One point of frame is left everywhere - at nought a location is off the machine and takes its weapons with it. The bisection moves every location at once, so it lands within a step that is worth thirty points on a large design; a bounded pass taking single points off the thickest location closes that. Over a sample of the library at three targets each: 67 of 243 reachable targets hit exactly and the worst miss is 5, against 28 and 15 before the refinement. A point of armour is worth five to eight points of battle value, so a target between two achievable figures cannot be hit at all - `missed_by` says so rather than leaving the caller to compare. Of 321 targets tried, 78 were below what stripping a machine can reach. Most of a Mek's battle value is what it carries rather than what protects it, so "half of what it is worth" is not always a condition that exists. **Battle damage, rather than an even shave.** `helm wear ... --battle` damages a design the way a fight does: fire lands where MegaMek's own 2d6 hit table sends it, so the torsos take most of it and the head one roll in thirty-six; it clusters, because a gunner with the range keeps shooting the same spot; it arrives in the sizes weapons throw, a machine gun's 2 and a medium laser's 5 and an autocannon/20's 20 rather than a smooth distribution; and when it gets through the plate it takes equipment with it on MegaMek's critical roll - nothing on 7 or less, one component on 8-9, two on 10-11, three on 12. This is not a combat simulation and does not want to be. There is no attacker, no range and no to-hit roll: what is borrowed is the shape of damage, which is what makes two machines at the same figure look like two machines rather than two spreadsheet rows. Nothing it produces is a mission kill. No location is destroyed - the frame stops at one point, so no limb is ever blown off - the head is left alone below its plate, the engine takes at most one hit of the three that destroy it and the gyro one of the two, and magazines are never hit, that being an explosion and a dead crew without CASE. 180 battles across the library are checked against every one of those. Guns *do* come off, and should: shooting one off is often the shortest way down to a figure, and an Atlas that lost its autocannon and kept its lasers is a more interesting unit than one with evenly thinned plate. What is refused is the end of that - nothing left to fire, or less than a quarter of what it could throw. A leg joint turned out to be the sharpest lever of the lot, and the least obvious one. A hip has no battle value of its own, so shooting one off looked free - and it is not free at all, because it halves what the Mek walks and movement feeds the defensive factor through the target movement modifier and the offensive factor through the speed factor. helm was not modelling that: a wrecked hip changed nothing. MegaMek takes an Atlas AS7-D from 1897 to 1630 for one hip, and to 1289 for the three actuators below it. The rule is now written and the damaged oracle carries two more scenarios to hold it: a destroyed hip halves the walk rounding up, two hips stop the Mek, every other leg actuator costs a point, and a leg with a wrecked hip does not also pay for its remaining actuators. 40 of 40 oracle rows agree. A location is only ever damaged behind plate that was breached. The pass that puts armour back when a hit overshoots was patching up locations it had already been shot through - 62 of 1,760 breached locations, a Javelin with one point of frame left behind eight points of armour. It now skips any location with a bent frame or a wrecked component, and a test over the library holds it there. The hits land in the sizes they land in, so the figure steps past the target as often as onto it. The even search's single-point pass finishes the job, in both directions: plate off the thickest location when the machine is still worth too much, and back onto the least damaged one when a hit overshot. Every seed tried lands within a point of armour's worth. -
Nothing is costed. What a repair takes in time, parts and C-bills is a campaign's question; what is missing is a fact about two files. Writing it found a real bug in the `.mul` writer. The key for rear plate was built as `RT` plus the location's first letter, which is right for the three torsos and sends a left *arm* to `RTL` - the left torso's rear. Reading was harmless, since MegaMek writes no rear plate on a limb. Writing was not: a force with a damaged torso came back with rear armour tags on two limbs that never had any, and `Bot_North.mul` gained a whole `<location index="5">` it did not have. The round trip over the shipped files missed it because none of them has rear damage. The test that holds the mapping is exact: strip a design to nothing and the repair list has to come to the armour and structure it was built with. Over all 4,294 Meks, every one does - which is what says the keys are right for a quad's four legs as well as a biped's arms. -
The terms do not add up to the figure by themselves - each half is a sum through a factor, and damage moves both - so the split needs a convention: an additive term is valued at the factor the machine ended up with, and a factor against the subtotal it started from. That is the one split with no cross term left over, and it is what lets a page draw a bar per term and have the bars sum to the drop beside them. A Griffin GRF-1N that came out of a match at 694 from 1272 reports -310 to its weapons, -257 to its plate and -12 to its frame, which is a repair list in the order it matters. Two things the sweep found. Wrecking a torso or a leg moves the *speed* factor and not the movement factor, because what it destroys is jump jets: 1,159 designs of 4,294 against 306. And a test over the damaged oracle rows alone never exercised either, so a split that valued a factor at the wrong moment passed it - the sweep over the library is what closes that, and both wrong splits were tried on purpose to see the guard fire. -
| | the book | helm | |---|---|---| | MON-66 Mongoose | DBR 433.3, OBR 325.08, BV 758 | 433.3, 325.1, 758 | | TDR-10SE Thunderbolt | DBR 954.85, OBR 1052.98, BV 2008 | 954.9, 1053.0, 2008 | | WI-DM DemolitionMech | DBR 328.2 | 328.2 | Armour, structure, gyro, defensive equipment, the explosive penalty, both defensive factors and both speed factors agree. That is the rules, MegaMek and this crate arriving at the same numbers by three routes. Two places the book and MegaMek genuinely differ, where helm follows MegaMek because that is what conformance means: the book gives a cockpit modifier only for the small cockpit, where MegaMek also has torso-mounted, small command console, drone, interface and VRRP; and the book describes a design's *armament* where MegaMek scores everything with a battle value, so a DemolitionMech's wrecking ball and rock cutter count. The one real disagreement is written up in `crates/helm-bv/UPSTREAM.md`. -
- **Injection into real files.** Put an unknown attribute, element, comment and text run at every position of every real `.mul` and assert each survives. The generator makes plausible documents; this proves the files that actually exist tolerate whatever a later MegaMek adds. - **A named list of what the writer may touch.** A test asserting it only ever modifies `points`, `index`, `isDestroyed`, `isHit`, `shots`, `gunnery`, `piloting`, `hits`, `chassis`, `model`, `type` and `version`. That turns "do not lose data" from a habit into a check. - **Deliberately break the guards.** The performance gate was proved to fire by making something slower on purpose; nothing has proved the losslessness guards fail when they should. - [x] **Construct, write, read.** Every round trip started from a file somebody else wrote. `helm wear ... --write worn.mul` builds a `Mul` in code and writes it, which is the case a player hits when helm hands them a damaged machine to load into MegaMek - and a test wears eleven designs down, writes them out, reads them back and checks each is worth what it was worn to. -
Its fresh penalty is -2 either side, and the -2 is not the same -2. helm charges two B-Pods, one in each leg, and charges nothing for the machine gun ammunition because CASE II protects it. MegaMek charges the ammunition and nothing for the B-Pods - which is consistent with its own `countAsOffensiveWeapon`, where a B-Pod is excluded by name. Emptying the bin then removes the whole of MegaMek's penalty and none of helm's. Two changes are needed and neither can be made alone: the fresh figure is right by luck, and correcting one side without the other breaks four thousand designs that currently agree. What would settle it is oracle coverage of each separately - a design with B-Pods and no ammunition, and one with CASE II ammunition and no pods. -
[~] Zero unregistered disagreements. Ten designs of 4,294 still differ, and here they are by name. None is a line unit; all are one-offs, joke designs or IndustrialMeks, which is why they have survived this long - but a disagreement nobody has written down is one nobody can pick up.
| design | out by | what it carries | |---|---|---| | Uni ATAE-70 ArtilleryMech | off -43.0 | a Thumper | | Super-Griffin GRF-2N-X | off +41.2 | LRM 10, experimental | | Jabberwocky JAW-67 MilitiaMech | off +30.8 | LRM 5, industrial | | UrbanKnight UM-DKX | off +15.5 | industrial | | Tessen TSN-X4R 'Rapunzel' | off -11.7 | one-off | | Vulpes VLP-1DX 'Beast' | off -6.5 | one-off | | Atlas AS7-00 (Jurn) | def -6.0 | a named character's | | Hybrid Rifleman RFL-3N (Sneede) | def -3.6 | one-off, two variants | | Hercules HRC-LS-9004 | def +1.3 | Ultra AC/10 and an AMS | The ArtilleryMech is settled, and it is not our arithmetic. Its file declares two Thumpers and allocates fifteen critical slots between them, which is one Thumper's worth: the design cannot be built as written. MegaMek scores two and helm reads the criticals and finds one, which is the 43 points exactly. Written up in `crates/helm-bv/UPSTREAM.md`. -
| what changed | ns per unit | a lance | |---|---|---| | the pilot | 2 | free | | armour or damage | 73,000 | 0.29 ms | | the whole loadout | 216,000 | 0.87 ms | Reseating a pilot recomputes *nothing* - battle value is the design's figure times a number from a 9x9 table - so the interactive case that sounded most expensive is the one that costs nothing at all. Repairing changes the armour but not the loadout, so the expensive half is skipped. Measured in wasm too, running the identical work under Node 24 from a `cdylib` with plain `extern "C"` exports and no bindgen: the arithmetic is **1.02x** native and reading a design is **1.31x**, the difference being string work. So a lance re-scores in about 0.3 ms in a browser and a company in under a millisecond, which is inside a frame either way. Measured again through the finished boundary, from Node against the real module rather than a sketch of one: | what a page does | cost | |---|---| | instantiate the module | 3 ms | | load the shipped catalogue | 88 ms, once | | score a design from its `.mtf` text | 253 us | | re-score a *held* design, damaged | 326 us | | re-crew a *held* design | **1 us** | Holding the design is what makes the last row true: reading the `.mtf` is most of what scoring costs, and a crew only scales a base that has already been worked out. Without a handle the same re-crew was 432us, which is the promise of the native benchmark being thrown away at the boundary. A lance re-crews in 4us and re-scores for damage in 1.3ms. Three things came out of writing it. Asking "is this location exposed" per item rather than per location made the cheapest rating cost more than reading the design; the loadout's mountings were rebuilt on every call when both ratings ask several times each; and `has_flag` walked every mounting's flag list for a question with eight distinct answers. Together those were 3x. -
`helm wire-check` writes what a page would be handed - the catalogue, every design as text, and this build's answer for each - and `crates/helm-wasm/wire.mjs` scores all of them through the boundary and compares. Every design twice, fresh and in one condition that carries plate, frame, a wrecked slot and an empty magazine, since a `Condition` crosses as JSON and is its own chance to disagree. 4,262 designs, 8,524 figures, none of them differing. Eight seconds for the check and forty to build the 39 MB bundle, which is derived and committed nowhere.
Force generation #
Formations #
-
The requirements themselves do not cross the bridge: each is a Java lambda with a display label beside it, and only the label survives. So `Rule::parse` reads the label back into a predicate and MegaMek's own per-unit answers are the oracle - 42,453 checks over 477 Meks, and every requirement helm claims to read means what MegaMek means by it. A label it cannot read becomes `Rule::Unreadable`, a force carrying one gets `Verdict::Unknown`, and nothing guesses. Two labels do not describe their requirement and one blueprint cannot be answered at all; all three are in `crates/helm-force/UPSTREAM.md`.
Faction and era availability #
Beyond Meks #
Housekeeping #
-
A database built from one MegaMek and queried about another answers every question and answers them about a library that is not there: designs since added are missing and figures since corrected are the old ones. The version was already recorded in both places; the comparison just had nowhere to be made.