From 8684815566549fd69fc79b1dfeba2bdf2a5eaa98 Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Fri, 4 Sep 2026 09:19:44 -0400 Subject: [PATCH] docs(mounting): jmm's drop-off algorithm, and which field says who decided The back-projection recorded verbatim before it is interpreted, with the clamp's trap named: a relative normalisation makes the least-bad drop score 1. Also `actor` is the host's default, not the deciding unit. Co-Authored-By: Claude Opus 5 (1M context) Change-Id: I9be25ec562310fd3c374bf0eb05ba2019c3daca8 --- crates/sds-core/src/wire.rs | 9 ++++++ plan/mounting.md | 48 +++++++++++++++++++++++++++++++ scenarios/tactics/mount-close.mms | 42 +++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 scenarios/tactics/mount-close.mms diff --git a/crates/sds-core/src/wire.rs b/crates/sds-core/src/wire.rs index 759f48e..1d7d8e9 100644 --- a/crates/sds-core/src/wire.rs +++ b/crates/sds-core/src/wire.rs @@ -269,6 +269,15 @@ pub struct Observation { /// A default, not a fact. It used to be "the unit being asked about" - the /// host broke on the first eligible unit and the bot was handed the result. /// A reply naming a unit from [`Self::eligible`] overrides it. + /// + /// **Reading a decision log to ask "which unit decided this" must use the + /// reply's own `unit`, not this.** They differ whenever the bot chose a unit + /// other than the default, which is most of the time - and a sweep keyed on + /// `actor` reads as a coherent answer about the wrong subject. It nearly + /// answered "does a dropped unit shoot that round" wrongly: keyed on + /// `actor`, one unit appeared to be skipped in the drop round; keyed on the + /// reply's `unit`, it had never fired in any round of the match and the + /// absence had nothing to do with the drop. #[serde(default)] pub actor: Option, /// Every unit of ours that may take this turn, ids only. Empty in a phase diff --git a/plan/mounting.md b/plan/mounting.md index 29b933a..8cce5c5 100644 --- a/plan/mounting.md +++ b/plan/mounting.md @@ -127,6 +127,54 @@ that lands within the passenger's own reach of something. **This is what MegaMek does. Whether it matches the rules is not established here.** +### Four duties and two profiles, one mechanism + +`TaskOverride` and `TacticOverride` now carry `Spot`, `Board`, `Carry`, +`Deliver`, and the `Embark` and `SafeDropoff` profiles. **None of them needed +new machinery.** `Spot` was expressible in existing columns, `Embark` found the +basis gap and recorded it, and the drop-off column is the one addition jmm has +sanctioned. Four consumers of one design is a better answer to "can tactics be +built from what exists" than any single tactic working. + +### The algorithm, as jmm gave it + +- Calculate eligible moves for the carrier with normal feature scoring +- From that set, calculate all eligible drop-off points for the passenger + (typically a subset of moved locations + possibly some adjacency superset + depending on type of dismount) +- For the set of all possible drop-off points, score the passenger's evaluation + of where it would LIKE to go +- For all scored drop-off points, they give their score to the hex the carrier + would have to walk to, in order to use that drop-off point +- This is now a per-hex feature for the carrier, which is 0 in hexes that aren't + drop-off eligible, and some normalized-to-1 as "best dropoff I could ask for" +- The carrier has a TacticsOverride to have it even consider that feature which + would normally be weighted 0 + +**It is a back-projection.** The passenger's valuation of a drop hex flows +*backwards* through the reachability relation onto the carrier's move hex that +would enable it. The carrier is not evaluating the passenger; the passenger's +own scoring is being projected. + +**And it is a new column**, which is worth stating rather than slipping past: +`Spot` needed none, `Embark` found a gap and recorded it, and this one jmm has +explicitly sanctioned. + +### The clamp is the hard part + +> The biggest issue with this is that we have to ensure the feature stays 0 if +> none of the drop-off points are any good for the carried unit at this time. +> That's how you don't have 3-range infantry mount, and then immediately +> dismount next turn not being any closer to the enemy. If none of their hexes +> would lead to good shots they should score the hex 0 as a clamp. + +**A relative normalisation defeats this outright.** "Normalised to 1 as the best +drop-off I could ask for", read as *best within this set*, makes the least-bad +drop of a uniformly bad set score 1 - and the platoon dismounts pointlessly, +which is the anti-pattern the clamp exists to stop. The reference has to be +absolute: what a good position is *for that unit*, not the best of what is on +offer. + - [ ] **Where to drop, scored as the passenger.** `Posture` already carries the candidate hex beside the unit and every positional feature is written against that pair, so scoring a unit at a hex it does not occupy is what diff --git a/scenarios/tactics/mount-close.mms b/scenarios/tactics/mount-close.mms new file mode 100644 index 0000000..77603ac --- /dev/null +++ b/scenarios/tactics/mount-close.mms @@ -0,0 +1,42 @@ +# The same pair, deployed on top of the enemy. +# +# `mount-probe.mms` cannot answer whether a dropped unit shoots in the round it +# is put down: its passenger never had a shot in any round, aboard or not, so +# the absence of a firing turn says nothing about the drop. +# +# Two changes make it answerable. Both sides deploy on the north edge, so the +# platoon's own short range reaches something. And the enemy is a gun +# emplacement, which cannot move away: a target that manoeuvres out of reach +# every round returns the same undetermined answer with more steps. +# +# The acceptance test for mounting, and deliberately the smallest board state +# that can produce one: a Badger tracked transport with an infantry compartment, +# a foot platoon beside it, and a single distant enemy so the match has two +# sides and a reason to end. If the platoon does not end a turn aboard, the +# failure is in one of three places rather than confounded across a lance. +# +# The Badger is the carrier because it is one: an Atlas and a Manticore both +# have `MekArms`, `ClampMountMek`, `RoofRack` and `TankTrailerHitch` and carry +# no infantry at all. 286 designs have an `InfantryCompartment`; most do not. +# +# The passenger is motorised rather than on foot because a foot platoon runs 1: +# deployment puts the pair several hexes apart and a unit closing one hex a turn +# cannot reach a carrier before the match is decided. This one runs 3 and the +# ride is still worth three times its own pace, so the test is about mounting +# rather than about walking. A battle armour squad was tried first and its own +# candidate generation is too slow to finish a match here. +# +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# https://creativecommons.org/licenses/by-nc-sa/4.0/ +MMSVersion=1 +Name=mount-close +Description=A transport, a platoon and an enemy all on one edge: does a dropped unit shoot that round. +BoardWidth=1 +BoardHeight=1 +Maps=GrassLands/16x17 Grasslands Open Terrain 2 +Factions=North,South +Location_North=N +Location_South=N +Unit_North_1=Badger (C) Tracked Transport A,North 1,4,5 +Unit_North_2=Motorized Infantry Dark Caste Bandits,North 2,4,5 +Unit_South_1=Calliope Turret (2800),South 1,4,5 -- 2.51.2