From 024ae44e45b866ced95a296740aa6ef0191dc74b Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Sat, 29 Aug 2026 02:05:14 -0400 Subject: [PATCH] docs(exotic-weapons): what a clean-looking merge with battle-armor costs Both branches move `Excluded::for_weapon` off name matching from opposite sides. This one asks the round first; `battle-armor` asks `anti_mek` before the names, because "Swarm Mek" and "Leg Attack" match none of the strings and were summed into volleys as guns that do nothing. The merged order is round, then the flag, then the names. Taking this branch's version wholesale compiles, passes every test in both, and silently restores the anti-Mek bug - nothing here mentions the flag and no test here has a swarm attack in it. The same shape sits in `averageDamage`, where their swarm substitution and this branch's rewritten tail are both needed. Noted at both sites and in the epic; the resolution is theirs on rebase. Also: the leg attack's critical modifier is not a constant and must never be written here as one - `hasMyomerBooster()` makes it run 4 to 16 per squad. --- bridge/sds/Observation.java | 6 +++++ crates/sds-core/src/brackets.rs | 16 +++++++++++ crates/sds-core/src/features/firing.rs | 10 ++++++- plan/exotic-weapons.md | 37 ++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/bridge/sds/Observation.java b/bridge/sds/Observation.java index e38f7bf..0e14b2d 100644 --- a/bridge/sds/Observation.java +++ b/bridge/sds/Observation.java @@ -581,6 +581,12 @@ public final class Observation { * launcher, which is an MML or an ATM, is the damage computed from the * round instead. See {@link #redefinesRanges}. */ + // **Merging with `claude/battle-armor`:** that branch substitutes a swarm + // attack's damage inside this method, and this branch rewrote its tail - + // the two single-packet fallbacks and the nearest-bracket fill below. The + // substitution and the fallbacks are independent and both are needed. A + // resolution that keeps only this version compiles and runs, and drops the + // swarm figure with nothing failing. static double averageDamage(WeaponType wt, AmmoType at, int range, int longRange) { if (range > longRange) { return 0.0; diff --git a/crates/sds-core/src/brackets.rs b/crates/sds-core/src/brackets.rs index 0d3d959..d8026ad 100644 --- a/crates/sds-core/src/brackets.rs +++ b/crates/sds-core/src/brackets.rs @@ -341,6 +341,22 @@ impl Excluded { /// still be holding something this bot cannot price, and that is a fact /// about the bin rather than about the gun - so it is read off the wire, /// where the bridge put it, instead of being guessed at from a name. + /// # Merging this with `claude/battle-armor`: the order is load-bearing + /// + /// That branch moves the same decision off names from the other side. It + /// tests `weapon.anti_mek`, MegaMek's own flag, **before** the name list, + /// because "Swarm Mek" and "Leg Attack" contain none of the strings the + /// list matches and were being summed into volleys as guns that do nothing. + /// This branch adds the round test in front. The merged order is + /// **round, then `anti_mek`, then the name list**, and none of the three is + /// redundant: the round is per-bin, the flag is a property MegaMek states, + /// and the names are the handful of genuine special cases that no property + /// carries - see `docs/PROTOCOL.md` on the AMS. + /// + /// **Taking this function wholesale compiles, passes every test in both + /// branches, and silently restores the anti-Mek bug**, because nothing here + /// mentions `anti_mek` and no test in this branch has a swarm attack in it. + /// A green build is not evidence the other branch's work survived. pub fn for_weapon(weapon: &crate::wire::Weapon) -> Option { if weapon.unmodelled_round.is_some() { return Some(Excluded::UnmodelledRound); diff --git a/crates/sds-core/src/features/firing.rs b/crates/sds-core/src/features/firing.rs index be8c5ab..feba688 100644 --- a/crates/sds-core/src/features/firing.rs +++ b/crates/sds-core/src/features/firing.rs @@ -758,7 +758,15 @@ impl Feature for ValueDestroyed { /// Three things in the plan want it and none can be answered without it. A /// battle armour leg attack does four points, which is nearly noise, and is /// held at no value at all rather than given a plausible constant - its payoff -/// is a through-armour critical. Matches end on criticals rather than on damage +/// is a through-armour critical. +/// +/// **Four is the leg attack's damage, and nothing here is its critical +/// modifier.** That modifier is not a constant and must not be written as one: +/// `hasMyomerBooster()` contributes `squadSize * 2`, which is twelve on a +/// six-suit point against two vibro claws' two, so it runs from 4 to 16 - a +/// factor of four across a real roster. It belongs to the battle armour work, +/// which computes it per squad and spends this column as the currency. Nothing +/// in this file should ever acquire a number for it. Matches end on criticals rather than on damage /// totals, and [`PKill`] is damage-based, so the ammunition explosion that ends /// a match in round three is not in it. And a kick or a punch into an open /// location is the second half of that quotation exactly. diff --git a/plan/exotic-weapons.md b/plan/exotic-weapons.md index 22d3937..f2be372 100644 --- a/plan/exotic-weapons.md +++ b/plan/exotic-weapons.md @@ -88,3 +88,40 @@ bot as "does nothing". The resolver is asked before nothing is reported. exclusion is real (they fire in a phase this bot does not model) and there is no property on the wire that carries it. A `weaponFamily` field, if one is ever added, is where this belongs + +## Merging this branch with `claude/battle-armor` + +Both branches move the same decision off name matching, from opposite sides, and +**neither is right alone**. The merge order has `expected-crits` landing first, +so the resolution falls to `battle-armor` on rebase. What it costs to get wrong +is written here because a wrong resolution does not fail. + +`brackets::Excluded::for_weapon` is the site. + +- **This branch** asks the *round* first: a Narc launcher is a beacon until + somebody loads explosive pods, and `unmodelled_round` drops a gun holding + ammunition the model cannot price. Then it falls through to the name list. +- **`battle-armor`** asks `weapon.anti_mek` - MegaMek's own flag - before the + name list, because "Swarm Mek" and "Leg Attack" contain none of the strings + the list matches and were being summed into volleys as guns that do nothing. + +**The merged order is round, then `anti_mek`, then the name list.** None of the +three is redundant: the round is a fact about the bin, the flag is a property +MegaMek states about the weapon, and the names are the handful of genuine +special cases no property carries - `docs/PROTOCOL.md` records why the AMS is +one of them. + +**The hazard is that taking this branch's `for_weapon` wholesale compiles, +passes every test in both branches, and silently restores the anti-Mek bug.** +Nothing in this branch mentions `anti_mek`, and no test here has a swarm attack +in it, so there is nothing to go red. A green build is not evidence that the +other branch's work survived the merge. + +The same shape sits in `bridge/sds/Observation.java`: `battle-armor` substitutes +a swarm attack's damage inside `averageDamage`, and this branch rewrote that +method's tail - the two single-packet fallbacks and the nearest-bracket fill. +They are independent and both are needed; a resolution that keeps only this +branch's version drops the swarm figure with nothing failing. + +This is the same failure the rest of tonight was about, wearing a merge's +clothes: a signal that reads clean standing in for a question nobody asked. -- 2.51.2