diff --git a/docs/HIERARCHY.md b/docs/HIERARCHY.md
index 0065380..269f2ef 100644
--- a/docs/HIERARCHY.md
+++ b/docs/HIERARCHY.md
@@ -1,31 +1,174 @@
# The hierarchy
-Princess decides one unit at a time. It picks a unit, scores every legal path
-for it, takes the best, and moves on to the next unit knowing nothing about what
-that one will do. Eight locally-best moves are not a plan, and the visible
-symptom is the conga line: a lance in single file, each machine having made a
-defensible choice.
+Princess decides one unit at a time. It picks a unit, scores every legal path for
+it, takes the best, and moves on to the next knowing nothing about what that one
+will do. Eight locally-best moves are not a plan, and the visible symptom is the
+conga line: a lance in single file, each machine having made a defensible choice.
+
+SDS splits the thinking across the force structure MegaMek already keeps. This
+page is the vocabulary — what the layers are, what changes at each, and which of
+them are decisions rather than labels.
+
+## The layers
+
+```mermaid
+graph TD
+ S[Scenario
the rules of winning] --> O[Objective
what we are trying to do now]
+ O --> C[Side
the coordinator]
+ C -->|Tactic| F1[Force]
+ C -->|Tactic| F2[Force]
+ C -->|Tactic| F3[Force]
+ F1 -->|TaskOverride, rare| U1[unit → Task]
+ F1 --> U2[unit → Task]
+ F2 --> U3[unit → Task]
+ F3 --> U4[unit → Task]
+```
+
+**Scenario** — the map, the forces, and the rule that decides a win. Fixed for
+the match.
-SDS splits the thinking across the force tree that MegaMek already keeps.
-
-## The cycle
-
-Once per round, at the first movement question:
-
-1. **Every unit thinks, at once.** Each is asked what it *could* do and what
- each option would be worth. It proposes; it does not decide.
-2. **Each force waits for all of its units.** This barrier is the whole point.
- A force that acted on the first three of four answers would be making
- Princess's mistake one unit later.
-3. **The force appraises, commits, and chooses.** It sums its units' readings of
- the situation, moves its stance if the case is strong enough, and picks each
- unit's proposal by what the stance it is *actually holding* is worth.
-4. **Orders are held and played** as each unit's turn arrives.
-
-Above a lance, a company does the same thing with its lances' readings, and its
-stance tilts theirs — it makes holding worth more to the lance on the threatened
-flank without telling that lance which hex to stand in. That is what keeps three
-levels from collapsing into one level with extra steps.
+**Objective** — what we are currently trying to do: win on battle value, or
+preserve what is left. It is the *utility function*, not a constraint list. Under
+one objective a withdrawal is terrible; under the other it is the plan. Changing
+it changes what "good" means everywhere below.
+
+**Force** — a group of units with a commander. One side holds a handful of them.
+
+**Tactic** — what one force is doing: `Engage`, `Flank`, `Entrench` and so on.
+One per force, chosen at the start of a turn, changed mid-turn only by a forcing
+event. A tactic biases movement, firing and physical scoring; it does not pick
+hexes.
+
+**Task** — what one unit actually does this turn: a hex, a facing, a volley.
+Every unit always has one. This is the enormous space, settled by ranking
+candidates on the feature basis.
+
+**TaskOverride** — a force constraining one of its units: *you are the spotter*,
+*you carry that squad*. Rare, and an exception by design. A constraint narrows
+the task set; it does not choose within it, so a unit told to be a taxi still has
+several hexes to pick from.
+
+## The structure is flat, deliberately
+
+There is no recursive force tree. A side holds forces, a force holds units, and
+that is the whole depth.
+
+Company-on-company is the practical ceiling for a played game — a command lance
+covering three others is sixteen units. Depth beyond that buys nothing and costs
+a great deal: a recursive design needs a general contract at every level, and a
+bug in it is a bug at all depths at once. Two fixed hops can be read as
+straight-line code.
+
+Flattening also makes cross-attachment ordinary rather than special. An infantry
+company that belongs to the command lance but fights alongside the artillery is
+normal task organisation, and a strict tree cannot express it without edges
+layered on top of containment. With one coordinator it is just a relation.
+
+Note that a **Command Lance is not a command layer.** It is a formation type with
+composition requirements that also fights. The commander is a *unit inside it*,
+not a level above it.
+
+## Timescales
+
+The layers are separated by how often they change, and that is a design
+constraint rather than an observation: **what a layer costs should be inversely
+proportional to how often it changes.**
+
+| layer | changes |
+|---|---|
+| Scenario | never, barring a GM |
+| Objective | perhaps once a match, and usually only when losing |
+| Tactic | every few rounds; every round at most |
+| Task | every round, for every unit |
+
+So Tasks get cheap scoring over a huge space, Tactics can afford a transition
+model with hysteresis over a small one, and the Objective can afford real
+deliberation because it is decided once. Anything expensive at Task frequency is
+misplaced, and so is anything trivial at Objective frequency.
+
+## Roles and Formations are labels, not decisions
+
+Both are classifications derived from what a force brought, and **neither should
+drive scoring.**
+
+A **unit Role** is MegaMek's own field — Brawler, Sniper, Missile Boat, Scout,
+Skirmisher, Striker, Juggernaut, Ambusher. The rules derive it from three
+questions: *how fast is the unit, how tough is it, and at what range does it
+prefer to fight?*
+
+A **Formation** is the same idea for a force: Battle Lance, Fire Lance, Recon
+Lance, and their variants, each defined by composition requirements over weight
+class, movement, armour, damage at a range, and the roles present.
+
+Both are lossy. A BGS-7S is labelled Missile Boat and carries MMLs that want a
+medium fight. A Dasher C is a Missile Boat whose LRMs have no minimum range and
+which wants to ferry armour and hunt backs. Formations misclassify the same way —
+a Hunter Lance qualifies at exactly 50% Ambushers while being mostly logistics.
+
+The rule that follows:
+
+> **Continuous capability drives decisions. Discrete labels are portrayal.**
+
+A unit's own range profile, speed and durability go into the feature basis. The
+word "Missile Boat" goes on the screen, so a person can read what the bot is
+doing. Formation likewise gives a force a name and a starting hint for its
+tactic, and stays out of the scorer — where a threshold at 50% would make a force
+at 49% behave discontinuously differently for no reason in the world.
+
+## Commanders
+
+Each force has one, and in a campaign it is a specific pilot in a specific
+machine — which can be killed. That makes a commander a mortal object rather than
+a configuration value, and it is where a force's personality attaches.
+
+Two consequences worth stating: killing the enemy commander becomes a tactic with
+a modelled payoff rather than flavour, and protecting ours becomes a reason to
+screen. MegaMek carries `commander` on the entity already; we do not yet read it.
+
+## The tactics
+
+Ten, each weighting a different family of features. Whether an eleventh is
+genuinely new is measured rather than argued: re-score a recorded corpus under
+each tactic and look for a pair that *never* diverges. Neighbouring tactics
+agreeing often is expected - some hexes are simply better - and only a pair that
+agrees everywhere is one tactic wearing two names.
+
+| tactic | target | what it optimises |
+|---|---|---|
+| `Engage` | — | the exchange ratio at our band |
+| `Advance` | — or enemy | accepts a worse exchange now to close |
+| `Harass` | — | freedom of action; the opposite of committing |
+| `Flank` | — or enemy | arc, not range |
+| `Screen` | own force | interposition between a friend and their enemies |
+| `Entrench` | — | positional durability: cover, elevation, seeing without being seen |
+| `Break` | — | losing line of sight — a tempo instrument, not a defensive one |
+| `Reposition` | location | an exogenous destination, overriding local scoring |
+| `Regroup` | — | cohesion |
+| `Retreat` | — | leaving the map |
+
+`Engage` is the default and should reproduce roughly what the bot does with no
+tactic layer at all, so everything else is measured against it.
+
+Some tactics imply a *distribution* of work inside a force rather than the same
+order to everybody — a lance cannot all flank; most of it holds attention while
+one unit takes the arc. A tactic that broadcasts one instruction to every unit
+cannot express a plan whose point is that units do different things.
+
+Not yet expressible, and waiting on the epics that unblock them: `Bombard` and
+`Spot` (artillery), `Taxi` and `Ride` (battle armor), `Seize` and `Deny` (map
+objectives and terrain effects), and closing to melee.
+
+## What is built, and what is not
+
+Built: two-level planning, the barrier where a force waits for all its units,
+menu reconciliation, and node transports so a level can run on another machine.
+
+Not built: the tactic layer described above. What exists today is a **stance** —
+a weight over seven hand-scored intents, chosen by summing per-unit appraisals
+whose arithmetic is hand-written rather than fitted. It is a parallel scoring
+system to the feature basis, it prices defence at roughly half of offence by
+hard-coded constant, and two of its seven intents are arithmetically unable to
+win. See `plan/scoring.md` and `plan/tactics.md`.
## Where a node runs is an address
@@ -37,101 +180,4 @@ line protocol on all three, so moving a thinker is a config change:
That indirection is not decoration. A planner backed by a language model wants a
key, a bigger machine and a much longer deadline than a unit thinker that runs a
thousand times a second next to the state — and both have to hang off the same
-tree or the tree is a fiction.
-
-## Strategy is a blend, not a choice
-
-A lance does not pick "flank" from a list. It holds a **stance**: a weight over
-intents that sums to one.
-
- close · hold · flank · break_los · focus · withdraw · screen
-
-So "mostly hold at range, a little flanking" is expressible, and is the normal
-case. Composition falls out for free — blending two stances is a weighted sum,
-where blending two enum values is a coin toss.
-
-## Commitment is one number
-
-`grit` is how hard a force is to talk out of what it is already doing. At 0 it
-re-decides every round and twitches; at 1 it dies executing its opening plan.
-
-It controls two things deliberately: the margin a rival stance must beat to take
-over, and how fast the stance drifts once it does. Both are facets of
-stubbornness, and splitting them into two knobs would invite tuning them against
-each other.
-
-The margin is scaled against what is at stake rather than absolute, so it means
-the same thing in a skirmish and a massacre.
-
-This is a fun mechanism before it is a strength mechanism. A bot that flip-flops
-reads as broken even when each individual choice is defensible; a bot that holds
-a plan reads as having one.
-
-## Roles are hints
-
-Every MegaMek design carries a role — sniper, brawler, scout. It says what the
-machine was built for, and it is the right place to start and the wrong place to
-finish.
-
-The case that forces this: **a sniper outranged by everything on the field.** Its
-role says stay back and shoot. From back there it will not land a shot all game.
-A bot that obeys the label loses while following its instructions correctly.
-
-So a role contributes a *preference* over intents, never a rule. Every role's
-preference is a blend across at least two intents, which is what makes it
-possible to outweigh — there is a test asserting exactly that, because a role
-that ever became a single intent would silently regain its veto.
-
-When a unit notices the conflict it says so, and the sentence travels up:
-
- sniper outranged: reach 12 against 21, nothing lands from here
-
-The lance sums that with everything else and decides. The sniper does not decide
-to close, and it does not suffer in silence either.
-
-## Everything rejected is kept
-
-A force's decision records every proposal it was offered, not just the ones it
-took. "The lance chose badly" and "no unit offered anything better" look
-identical in a record that keeps only the winner, and telling those two apart is
-most of what debugging a hierarchy is.
-
-It travels back to the host in the `rationale` field of the action, which the
-host ignores and writes to the match log.
-
-The decision also records what the menu was *priced against*. `threats` names
-each enemy, where it stood, and every hex it could have reached before we
-arrive - the set `damage_taken` is a collapse over - and every proposal carries
-`feared`, the one member of that set which contributed the most incoming damage
-to it. `feared` is an argmax over the arithmetic and not a prediction: nothing
-in this bot weights one reachable hex above another, so there is no such thing
-here as "where we think the enemy will go".
-
-## Scoring is being replaced
-
-A proposal is still scored by `Proposal::serves`: the unit says which intents
-its option serves and the force multiplies that by its stance. It is a
-self-report, so a unit that is wrong about itself is never contradicted.
-
-`sds_core::features` is the replacement. A feature is a named number computed
-from the observation, and it declares how it is normalised — `bounded`, `rank`
-or `local` — in the type system, so an unnormalised feature does not compile and
-a `local` one cannot be given a learned weight. Firing, target, latch,
-force-level and positional features exist and are tested. The positional group
-runs off `sds_core::los` through one survey per movement candidate, which is
-what lets a hex be scored on what could shoot it and not only on what it could
-shoot.
-
-## What is not done yet
-
-- Deployment is scored but not commanded. A deploy hex is priced on the best
- move it opens next turn — the same search, envelope, sweep and weights a move
- is priced with — but each machine chooses alone, so the lance still does not
- choose a shape and two units can want the same ground.
-- Movement *order* is the harness's, not the bot's. Which unit moves first is a
- real tactical decision.
-- Terrain is barely used. Proposals reason about range bands and ignore cover,
- which is most of what `break_los` would need to mean anything.
-- The damage model during movement is range bands, not to-hit numbers. Real
- numbers do not exist for a hex the unit is not standing in yet, but a better
- estimate than this one does.
+structure or the structure is a fiction.
diff --git a/plan/README.md b/plan/README.md
index 0a8c783..6fa9d57 100644
--- a/plan/README.md
+++ b/plan/README.md
@@ -71,7 +71,7 @@ one.
**We plan at the lance and fight for an objective.** Not eight machines each
solving its own problem. `plan/hierarchy.md` has the planning half - units
-propose, forces decide - and `plan/goals.md` has the objective half, where "get
+propose, forces decide - and `plan/objectives.md` has the objective half, where "get
four units off the east edge" makes a favourable trade bad. Both are open.
**We are reproducible against ourselves.** A scenario and a seed replay to the
@@ -124,7 +124,7 @@ collection of opinions, and until a control comes out near 50/50 with stalls nea
zero, no number in this repository means anything.
After that the spine is [protocol](protocol.md) -> [los](los.md) /
-[ev](ev.md) -> [features](features.md) -> [strategies](strategies.md), because
+[ev](ev.md) -> [features](features.md) -> [tactics](tactics.md), because
each one is what the next needs to be computable. [training](training.md) is last
for a reason: it fits `M`, and `M` does not exist until `features` and
`strategies` do.
@@ -141,11 +141,12 @@ for a reason: it fits `M`, and `M` does not exist until `features` and
| [ev](ev.md) | Damage as a distribution, not a mean | open | protocol |
| [los](los.md) | Line of sight computed here, checked against MegaMek | open | - |
| [features](features.md) | A normalised, named feature basis | open | protocol, los, ev |
-| [strategies](strategies.md) | Strategy as a blend of features, held with grit | blocked | features |
+| [scoring](scoring.md) | One basis, not two | open | features |
+| [tactics](tactics.md) | A force is doing one nameable thing, and can be told to do another | blocked | scoring, hierarchy |
| [beliefs](beliefs.md) | What the bot thinks is happening | blocked | features |
-| [goals](goals.md) | What a force is trying to achieve | blocked | strategies, protocol |
-| [doctrine](doctrine.md) | Composable lenses - culture, commander, personality, experience | blocked | strategies |
-| [capability](capability.md) | Forces and doctrines map to each other | blocked | strategies |
+| [objectives](objectives.md) | What winning means, and what to do when it stops being available | blocked | tactics |
+| [doctrine](doctrine.md) | Composable lenses - culture, commander, personality, experience | blocked | tactics |
+| [capability](capability.md) | Forces and doctrines map to each other | blocked | tactics |
| [difficulty](difficulty.md) | Fallible, never absurd | blocked | beliefs, doctrine |
| [training](training.md) | Weights fitted from recorded play | blocked | features, harness |
| [observability](observability.md) | Read back what the bot believed and chose | open | beliefs |
diff --git a/plan/candidates.md b/plan/candidates.md
index ac81500..c5d5397 100644
--- a/plan/candidates.md
+++ b/plan/candidates.md
@@ -49,7 +49,7 @@ wrong at both ends and has gone.
So the scoring work - line of sight, hit locations, damage distributions,
per-location value - is being used to rank five options out of scores. **No
weight vector can select a hex that was never proposed.** This epic is the
-ceiling on everything [features](features.md) and [strategies](strategies.md)
+ceiling on everything [features](features.md) and [tactics](tactics.md)
can achieve.
It is also the uncomfortable half of [PRINCESS.md](../docs/PRINCESS.md): we
@@ -85,7 +85,7 @@ aggregations are **not the same operator** - see below.
Over `M`, an enemy's possible positions: uncertainty. Collapse with the power
mean, whose exponent spans minimax (`p -> -inf`, they find our worst case)
through expectation (`p = 1`, they move without regard to us). Same operator
-[strategies](strategies.md) already names for composition, and it is
+[tactics](tactics.md) already names for composition, and it is
characterful rather than arbitrary - a cautious commander runs a lower `p`, so
it belongs to [doctrine](doctrine.md).
@@ -313,7 +313,7 @@ The frontier fixes it without an exchange rate. A stand dealing 25 while taking
survives only where it is genuinely the safest thing available, and a position
that fights is on the frontier whenever one exists. No objective term, no
distance-to-enemy term, no scalar: a rate between the two columns is doctrine's,
-it belongs to [strategies](strategies.md), and it is what `StandScore::margin`
+it belongs to [tactics](tactics.md), and it is what `StandScore::margin`
was deleted for.
**One damage column per enemy, not a maximum over enemies.** Dominance on "best
@@ -805,7 +805,7 @@ eight units and the enemy is credited with eight times its firepower. It is a
conditional, not a share: the enemy allocates fire, it does not multiply it, so
a force's real exposure is an allocation problem whose worst case is
concentration on one unit. Nothing here may add these numbers up, and the fix is
-force-level - see [hierarchy](hierarchy.md) and [strategies](strategies.md).
+force-level - see [hierarchy](hierarchy.md) and [tactics](tactics.md).
**The force now does it.** `features::incoming` shares each enemy's fire out
over our units by how much each would appeal as a target, and writes the result
@@ -933,7 +933,7 @@ There is no bridge from a `Stance` to a `Weights` anywhere in the tree.
Choosing along a frontier needs one, and `examples/stands.rs` carries a stand-in
so the demonstration can exist - in the example on purpose, because what a
stance emphasises is a tactical opinion nobody has argued over yet. Writing that
-bridge for real belongs to [strategies](strategies.md).
+bridge for real belongs to [tactics](tactics.md).
The firing group does not fully survive the aggregation either.
`expected_damage`, `p_kill`, `p_mission_kill` and `value_destroyed` come off the
diff --git a/plan/capability.md b/plan/capability.md
index 28e1252..5093507 100644
--- a/plan/capability.md
+++ b/plan/capability.md
@@ -2,7 +2,7 @@
id: capability
title: Forces and doctrines map to each other
status: blocked
-dependsOn: [strategies]
+dependsOn: [tactics]
exitCriterion: >
Selecting a lance for a doctrine and inferring a doctrine from that lance
round-trips.
diff --git a/plan/doctrine.md b/plan/doctrine.md
index 8e3404c..250bea8 100644
--- a/plan/doctrine.md
+++ b/plan/doctrine.md
@@ -2,7 +2,7 @@
id: doctrine
title: Composable lenses - culture, commander, personality, experience
status: blocked
-dependsOn: [strategies]
+dependsOn: [tactics]
exitCriterion: >
A Clan force plays zellbrigen, breaks it under pressure, and a human can author
a new opponent by writing rows and masks rather than training.
@@ -35,3 +35,30 @@ Verified on sarna: zellbrigen does not formally forbid melee, forbids
interfering in another's duel, and makes breaking LOS or withdrawing out of range
*dezgra*. So it forbids focus fire - the highest-value force behaviour - which is
what makes Clan forces a different opponent rather than a stronger one.
+
+## Personality attaches to a commander, not to a role
+
+Settled while designing the tactic layer, and it changes where the lenses hang.
+
+A **unit Role** is too lossy to group by: a BGS-7S is labelled Missile Boat and
+carries MMLs that want a medium fight, and a Dasher C is one whose LRMs have no
+minimum range and which wants to ferry armour and hunt backs. Grouping weights by
+the role *string* would fit the label's noise. Use the continuous capability the
+role is a lossy summary of.
+
+A **Formation** is the same problem one level up, and belongs to portrayal for
+the same reason - see [HIERARCHY.md](../docs/HIERARCHY.md).
+
+What is left is the **commander**, and every force has one. That gives the lens
+stack a home: a side's doctrine multiplies a force commander's personality
+multiplies the unit's capability-driven weights. Commutative, attributable, and a
+few parameters per level rather than a vector.
+
+- [ ] Decide **modifier or vector** by the sign-flip test: if a personality only
+ scales terms it is a multiplicative lens, and if it flips the sign of a
+ feature no lens can express it and the group needs its own weights. Testable
+ on the existing corpus with partial pooling, which reports how far a group
+ moves off the global vector - ridge at 1e-3 is already complete pooling, so
+ this is one penalty term rather than a new solver
+- [ ] Personality is embodied, so it can be killed. Succession needs an answer;
+ the rules designate a key lieutenant per additional Command Lance
diff --git a/plan/goals.md b/plan/goals.md
deleted file mode 100644
index 1725793..0000000
--- a/plan/goals.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-id: goals
-title: What a force is trying to achieve
-status: blocked
-dependsOn: [strategies, protocol]
-exitCriterion: >
- A scenario with an extraction objective is played toward that objective rather
- than toward destroying the enemy.
----
-
-# goals
-
-A goal changes the objective, not the policy, which is why it cannot be another
-strategy. "Get four units off the east edge" makes trading BV bad even when the
-trade is favourable, and the map edge is not in any combat feature.
-
-Goals decompose by **assignment** (1st Lance screens while 2nd exits); strategies
-decompose by **blending**. Different operations.
-
-MegaMek already carries this: MMS V2 has per-player `victory:` and composable
-triggers - `UnitPositionTrigger` is literally an extraction goal.
-
-- [ ] Read goals from the scenario rather than inventing a vocabulary
-- [ ] Goal-conditioned features (`distance_to_exit`) in one union basis with
- zeros, so `M` stays rectangular
-- [ ] Per-unit goals: this one is the VIP, these are the screen
-- [ ] Default goal: defeat the enemy as a cooperating team
diff --git a/plan/hierarchy.md b/plan/hierarchy.md
index 5101b41..99d2ef9 100644
--- a/plan/hierarchy.md
+++ b/plan/hierarchy.md
@@ -4,8 +4,9 @@ title: Units propose, forces decide
status: open
dependsOn: []
exitCriterion: >
- A company of two lances plans in one cycle per round, every force waits on all
- its children, and the rejected proposals are in the record.
+ A side of several forces plans in one cycle per round, every force waits on all
+ its units, the rejected proposals are in the record, and a scenario that
+ declares forces produces a `force_path` the bot actually reads.
---
# hierarchy
@@ -240,3 +241,34 @@ range, terrain, arc, and the movement modifier the stand's own gait buys.
Costs 2.2 ms for 8 units x 200 stands x 8 enemies in release, measured on a
loaded machine, against the 96.8% of decision time movement already spends.
+
+## The structure is flat, and that is a decision
+
+There is no recursive force tree: a side holds forces, a force holds units, and
+that is the depth. Company-on-company is the practical ceiling for a played game
+- sixteen units - and depth beyond it buys nothing while costing a general
+contract at every level. Two fixed hops read as straight-line code.
+
+It also makes cross-attachment ordinary. An infantry company that belongs to the
+command lance but fights alongside the artillery is normal task organisation, and
+a strict tree cannot express it without edges layered over containment. One
+coordinator makes it a relation.
+
+A **Command Lance is not a command layer** - it is a formation type with
+composition requirements that also fights, and the commander is a unit inside it.
+
+See [HIERARCHY.md](../docs/HIERARCHY.md) for the full vocabulary.
+
+- [ ] **`force_path` is never populated.** The wire has carried it since the
+ protocol was written - "outermost first: company, then lance" - and it is
+ empty in every scenario we generate, so the hierarchy has exactly one level
+ in use. Everything about coordination is untestable until a scenario
+ declares forces and MegaMek reports them
+- [ ] `from_above` exists on `ForceThinker::command` and carries nothing, for the
+ same reason
+- [ ] The coordinator assigns **relations** between forces - who screens whom,
+ who spots for whom - and each force chooses its own tactic within them.
+ Relational assignment is the only part that must be joint
+- [ ] `commander` is on MegaMek's entity and not on our wire. It is where a
+ force's personality attaches, and in a campaign it is a mortal pilot rather
+ than a configuration value
diff --git a/plan/objectives.md b/plan/objectives.md
new file mode 100644
index 0000000..1ebcd40
--- /dev/null
+++ b/plan/objectives.md
@@ -0,0 +1,56 @@
+---
+id: objectives
+title: What winning means, and what to do when it stops being available
+status: blocked
+dependsOn: [tactics]
+exitCriterion: >
+ A losing force switches to preserving what it has, does so on a stated
+ threshold rather than a guess, and does not oscillate across that threshold.
+---
+
+# objectives
+
+An objective is what the side is currently trying to do. It is the **utility
+function**, not a constraint list: under `WinOnBattleValue` a withdrawal is
+terrible, and under `PreserveByRouting` an advance is. The same tactics are legal
+either way; what changes is what they are worth.
+
+That distinction is load-bearing. A `TaskOverride` removes options from a set
+after they are scored. An objective changes the scoring. Collapsing the two
+loses the difference between "forbidden" and "bad".
+
+Absent objectives, `Engage` is the whole game and half the tactic vocabulary is
+unreachable - which is the state today.
+
+## Two to start
+
+- `WinOnBattleValue` - the current victory rule. Makes `Retreat` unreachable,
+ `Reposition` nearly so, and `Screen` unavailable to a side with only one force.
+- `PreserveByRouting` - reached when the match is lost. Admits `Retreat`,
+ `Reposition`, `Screen` above one force, and `Break`, which is how a
+ disengagement survives: losing line of sight is what buys the turn in which
+ withdrawing is not fatal.
+
+`Break` sits in both sets, which is right - breaking contact serves winning and
+losing equally.
+
+- [ ] The transition is a **threshold**, not a judgement: remaining battle value,
+ or a count of crippled units. A threshold can be stated, tested and shown
+ to a player; a learned judgement cannot yet
+- [ ] Verify it does not oscillate. Flip at 30% and check the behaviour at 31%
+ is stable. This is the cheapest available test of the hysteresis in
+ `plan/tactics.md`, and losing is easy to produce on demand
+- [ ] An objective may also **bias** rather than only constrain, including by
+ which side of the scenario we are on - an attacker and a defender have
+ different utility, not different legal moves
+
+## Scenario-derived objectives, later
+
+MegaMek already carries this. MMS V2 has per-player `victory:` conditions and
+composable triggers, and `UnitPositionTrigger` is literally an extraction
+objective. None of our maps declare one yet, so the objective layer cannot be
+exercised beyond the two above.
+
+- [ ] Read objectives from the scenario rather than inventing a vocabulary
+- [ ] Objectives decompose by **assignment** - first lance screens while second
+ exits - which is the coordinator's relational assignment, not a blend
diff --git a/plan/order.txt b/plan/order.txt
index 2998c47..0875271 100644
--- a/plan/order.txt
+++ b/plan/order.txt
@@ -12,9 +12,10 @@ hierarchy
ev
los
features
-strategies
+scoring
+tactics
beliefs
-goals
+objectives
doctrine
capability
difficulty
diff --git a/plan/protocol.md b/plan/protocol.md
index 1c45aeb..59689a1 100644
--- a/plan/protocol.md
+++ b/plan/protocol.md
@@ -14,12 +14,12 @@ The observation is the interface. What is not in it does not exist for any bot.
Adding a field is deliberate, with a reason in the commit message.
- [x] **Per-location armour and structure** - blocks breach state, so it blocks
- punch-versus-sandblast allocation and [cripple](strategies.md). On the
+ punch-versus-sandblast allocation and [cripple](tactics.md). On the
wire as `locations`, with rear armour on the three locations that have it
- [x] **Per-location contents** - armour says how hard a location is to break,
not what breaking it costs the target. A weapon count and their damage,
live ammunition and whether CASE contains it, engine, gyro and cockpit
- slots, and an actuator count. One shared addition: [cripple](strategies.md)
+ slots, and an actuator count. One shared addition: [cripple](tactics.md)
needs it for arc-aligned targeting, and a mission kill needs it before
"all its guns are gone" can mean anything. Deliberately a summary and not
a critical slot listing
@@ -94,7 +94,7 @@ Adding a field is deliberate, with a reason in the commit message.
change LOS. The board is sent once and never updated, so [los](los.md)
would drift correctly-implemented and wrong
- [ ] **Scenario objectives** - MMS V2 carries per-player `victory:` conditions
- and composable triggers. Blocks [goals](goals.md)
+ and composable triggers. Blocks [objectives](objectives.md)
- [ ] **Minefields** - the bot is blind to them
- [ ] **Buildings as objects** - a building spans hexes and has an identity, and
the board message sends only per-hex terrain. Every terrain attribute
diff --git a/plan/scenarios.md b/plan/scenarios.md
index 843fa63..d891b67 100644
--- a/plan/scenarios.md
+++ b/plan/scenarios.md
@@ -34,7 +34,7 @@ Forces come from helm, which already indexes year, BV, role and tonnage.
the check has nothing left to reject. Epoch 3
- [x] Asymmetric scenarios: `sds scenarios --asymmetric` draws the two sides
independently. See below
-- [ ] Objective scenarios, which [goals](goals.md) needs
+- [ ] Objective scenarios, which [objectives](objectives.md) needs
City maps are excluded: the bot has no notion of buildings, so a city fight
measures its blindness.
diff --git a/plan/scoring.md b/plan/scoring.md
new file mode 100644
index 0000000..e9ae99c
--- /dev/null
+++ b/plan/scoring.md
@@ -0,0 +1,84 @@
+---
+id: scoring
+title: One basis, not two
+status: open
+dependsOn: [features]
+exitCriterion: >
+ Every score the bot computes comes from the named feature basis. `Intent`,
+ `Stance`, `Appraisal` and the six-axis `asks` table are deleted, and no weight
+ the bot uses was written by hand.
+---
+
+# scoring
+
+The bot has **two scoring systems** and only one of them is trained.
+
+The first is the 51-feature basis: named, normalised, documented in
+[FEATURES.md](../docs/FEATURES.md), fitted from recorded play. It scores
+candidate stands and volleys.
+
+The second is a parallel arithmetic nobody fitted. `unit.rs` scores seven intents
+from hand-written expressions over `projected_damage`; `surface.rs` maps each
+intent to a damage weight and six bespoke axes - `take, flank, blind, range,
+band, heat` - and *those* rank the candidates. The 51 features are not consulted.
+
+This epic deletes the second one.
+
+## What the hand-written arithmetic gets wrong
+
+Each of these is measured, and each was found from a different direction:
+
+- **Defence is discounted by a hard-coded half.** `Intent::Hold` scores
+ `dealt_now - projected_damage(enemy) * 0.5`, and `asks(Close)` prices the
+ incoming-fire axis at `-0.10` with the comment "the incoming barely priced".
+ The fitted weights independently land at 1.69:1 favouring closing, and the bot
+ takes a quarter of its shots at one hex.
+- **Intents are denominated inconsistently.** Five are in damage points and read
+ in the tens; `Screen` is `+/-1` and `Withdraw` is `+/-2` to `+/-4`. Each wins
+ 0.2% of decisions - not rarely chosen, arithmetically unable to win.
+- **Units vote in proportion to their firepower.** `combine` sums raw appraisals,
+ so a unit projecting 40 points outvotes one projecting 1 by forty to one.
+- **Two quantities answer one question.** `projected_damage` and
+ `expected_damage` both mean "what we would land", computed differently, used by
+ the two systems respectively.
+
+## The work
+
+- [ ] Express a tactic as a weighting over the **named basis**, not over six
+ bespoke axes. That makes tactics fittable rather than authored, and it is
+ what lets a personality be a weight vector rather than a table somebody
+ typed
+- [ ] Delete `Intent`, `Stance`, `Appraisal`, `Commitment` and `asks`. The
+ transition model in `plan/tactics.md` replaces what `Commitment` did
+- [ ] Retire `projected_damage` in favour of `expected_damage`, or say why two
+ are needed
+- [ ] **`Screen` has no feature to score itself by.** Nothing in the basis
+ measures standing between two things; `cover_distance` and `friend_support`
+ are the nearest and neither is it. This is why the old `Intent::Screen`
+ decayed into a health check
+- [ ] Commensurable denominators for the two halves of an exchange.
+ `expected_damage` is a share of our maximum output and `incoming_damage` a
+ share of our remaining health, so their difference is not an exchange ratio
+ - see `plan/matchup.md`
+
+## Orphaned feature clusters
+
+Clustering all 51 against the ten tactics leaves four families that no tactic is
+about, and they are all **resource and method** rather than geometry:
+
+| cluster | features | note |
+|---|---|---|
+| heat | 5 | a *set point implied by* a tactic, not a tactic. `Break` cools, `Entrench` tolerates |
+| ammunition | 1 | same shape as heat |
+| damage kind | `p_kill`, `p_mission_kill`, `p_psr_threshold`, `p_breach` | legging a fast unit and killing a slow one are the same points to opposite purpose |
+| target choice | the `target_*` family | finish the wounded, kill the dangerous, kill the cheap |
+
+The covered clusters are all geometry - range, arc, cover, visibility, cohesion,
+position. That the bias runs this way probably reflects how the basis grew rather
+than what matters, and it is the same finding as "offence is priced exactly and
+defence is not priced at all" seen from the feature side.
+
+- [ ] Decide whether damage-kind and target-choice want a **firing solution
+ selector** at the force - one joint choice given final movements - rather
+ than a pre-declared tactic. Fire allocation has complete information after
+ movement, so declaring it in advance can only lose
diff --git a/plan/strategies.md b/plan/strategies.md
deleted file mode 100644
index 531a551..0000000
--- a/plan/strategies.md
+++ /dev/null
@@ -1,77 +0,0 @@
----
-id: strategies
-title: Strategy as a blend of features, held with grit
-status: blocked
-dependsOn: [features]
-exitCriterion: >
- A force's stance is a simplex over named strategies, blending composes, and
- an SVD of M shows the vocabulary is not redundant.
----
-
-# strategies
-
-`w = S^T M`. `S` is what the force is doing, `M` maps strategies to feature
-weights, and scoring is `Phi w`. Blending two strategies gives a weighting that
-is genuinely half of each, which an enum cannot do.
-
-Commitment is one parameter. `grit` sets both the margin a rival must beat and
-the drift rate once it moves - facets of stubbornness, and splitting them invites
-tuning them against each other. Already built and tested against `Intent`; it
-carries over unchanged.
-
-- [ ] Rows of `M`, **three to start**: `direct_engagement`, `cripple`,
- `fighting_withdrawal`. One per objective we can actually fit
-- [ ] Port `Commitment`/`Grit` from the intent simplex to the strategy simplex
-- [ ] Force-level assignment: greedy per-unit argmax, then swaps that improve the
- force-level features. Crossfire is not expressible per unit
-- [ ] **SVD check on `M`.** If effective rank collapses, the named strategies are
- theatre - that is Princess's disease measured rather than read
-- [ ] Composition operator as a hyperparameter: the power mean spans min,
- geometric and arithmetic in one `p`
-
-## As many strategies as there are objectives, and no more
-
-A row of `M` is a weight vector. A weight vector comes from a fit. A fit needs a
-label. So if ten rows are fitted from two labels, `M` has effective rank two and
-eight of the names are decoration - blending `flanking` with `gun_line` would
-produce something that is not between them, because they are not independent
-directions.
-
-This epic already carries the test that catches it: "**SVD check on `M`.** If
-effective rank collapses, the named strategies are theatre." Declaring ten rows
-today would fail that check, and would fail it because of the label count rather
-than anything about the strategies.
-
-So the rule: **a strategy is named when it has an objective to be fitted
-against.** Three to start, matching the labels being built:
-
-| strategy | objective | what it should look like |
-|---|---|---|
-| `direct_engagement` | damage inflicted | efficient bracketing - get to the range band where our weapons work and trade at the best rate |
-| `cripple` | BV loss inflicted | take what makes them dangerous: legs, guns, the locations already open |
-| `fighting_withdrawal` | damage taken, negated | keep what is left and make them pay for the ground |
-
-These three are behaviourally distinct rather than three names for aggression,
-which is the point. `direct_engagement` and `cripple` diverge exactly where
-damage and battle value diverge: chipping armour is damage and barely moves BV,
-while a destroyed gun is little damage and a large BV drop.
-
-`cripple` is the one that needs features we do not have. Hit location is rolled,
-not chosen, so "go for the legs" is expressed through kicks, through preferring
-targets whose locations are already breached, and through called shots where
-the rules allow them. That makes `cripple` wait on the melee features, and on
-`target_breach` being crossed with location rather than taken whole.
-
-### Waiting on a label
-
-`destroy_weakest` (kills secured), `gun_line`, `flanking`, `crossfire`,
-`screen`, `overrun`. Each is a real thing a force does; none has a number to be
-fitted against yet. `crossfire` additionally cannot be expressed per unit, so it
-needs a force-level label.
-
-### Waiting on more than a label
-
-`bait` is not blocked on an objective, it is blocked on [beliefs](beliefs.md).
-Baiting means acting on what the opponent will do in response, and nothing in
-the bot models an opponent's future choice. Naming it before that exists would
-be naming a behaviour we cannot compute.
diff --git a/plan/tactics.md b/plan/tactics.md
new file mode 100644
index 0000000..d915250
--- /dev/null
+++ b/plan/tactics.md
@@ -0,0 +1,127 @@
+---
+id: tactics
+title: A force is doing one nameable thing, and can be told to do another
+status: blocked
+dependsOn: [scoring, hierarchy]
+exitCriterion: >
+ Every force carries one of the named tactics each round, a human can read why
+ it changed, and a run where the tactic layer is disabled loses to one where it
+ is enabled by an interval that excludes zero.
+---
+
+# tactics
+
+A tactic is what one force is doing: `Engage`, `Flank`, `Entrench`. One per
+force, chosen at the start of a turn, changed mid-turn only by a forcing event.
+It biases movement, firing and physical scoring; it never picks a hex.
+
+See [HIERARCHY.md](../docs/HIERARCHY.md) for the layer it sits in and the list.
+
+**What it replaces.** Today a force holds a *stance* - a weight over seven
+intents scored by hand-written arithmetic on `projected_damage`, summed across
+units without normalisation. Three defects follow and all three are measured:
+`Intent::Hold` discounts incoming damage by a hard-coded 0.5; `Screen` and
+`Withdraw` are denominated in units two orders of magnitude smaller than the
+intents they compete with, so they win 0.2% of the time each and are effectively
+dead; and one stance broadcasts one instruction to every unit, which cannot
+express a plan whose point is that units do different things.
+
+## The vocabulary
+
+Ten tactics, listed in [HIERARCHY.md](../docs/HIERARCHY.md).
+
+**How to tell whether an eleventh is a new direction or an old one renamed.**
+Not "they picked the same hex" - two tactics coinciding on one round means
+nothing, because some hexes are simply better and similar tactics *should* agree
+often. The question is whether they **always** agree.
+
+That is measurable offline and needs no new matches. A decision log carries every
+candidate with its `phi`, so a tactic expressed as a weighting over the basis can
+be re-scored against a recorded corpus: for each decision, what would this tactic
+have chosen? Then correlate the choices pairwise across tactics.
+
+Same shape as `sds labels`, and it should carry the same discipline: report max
+agreement against every other tactic, band it - under about 0.4 is a new
+direction, over about 0.8 is mostly a rename - and *run it against two corpora
+before retiring anything*, because an agreement measured on one corpus is a
+property of that corpus until a second one confirms it.
+
+Expect real correlation between neighbours. `Advance` and `Engage` share most of
+their situations; `Break` and `Retreat` both move away. The signal is a pair that
+never diverges anywhere.
+
+- [ ] `sds tactics `: re-score every logged decision under each tactic
+ and report the pairwise agreement matrix
+- [ ] Candidates already cut on reasoning rather than measurement, to re-test
+ once the tool exists: `Delay` needs a time value we do not model,
+ `Standoff` looked like `Engage` when we outrange and `Retreat` when we do
+ not, and `Destroy` looked like `Advance` at an enemy until an objective
+ makes a kill worth more than its battle value
+
+- [ ] The `Tactic` trait, and an enum wrapper so a force can hold a
+ heterogeneous list and compare it uniformly
+- [ ] Target polymorphism at the type level: `Screen` cannot be constructed
+ against an enemy, `Advance` cannot be constructed against a friend. Separate
+ traits per target arity behind one interface, so it is a compile error
+ rather than a runtime check
+- [ ] Names may not collide with a unit Role or a Formation. `Skirmish` is out -
+ `Skirmisher` is a Role. So are `Probe`, `Sweep`, `Recon` and `Support`
+- [ ] The blocked tactics present as commented-out variants, so the shape is
+ visible before the epics that unblock them land
+
+## Choosing one
+
+Targeted tactics choose their target **with** the tactic and are ranked with it -
+four things worth screening are four candidates, not one tactic and a later
+lookup. Untargeted ones may resolve a target later: `Bombard` hits whatever is
+spotted.
+
+That keeps the search small. A force facing eight enemies has on the order of a
+hundred options, which is an order of magnitude *less* than one unit's firing
+decision - measured over the corpus at a median of 47 candidates, p90 263, max
+2052. Tactic selection is not where the time goes.
+
+What must not happen is enumerating every force's tactic jointly. The coordinator
+assigns **relations** - who screens whom, who fixes while another flanks - and
+each force then chooses its own tactic within those. Four forces give six pairs;
+enumerating four independent choices of a hundred gives 10^8.
+
+- [ ] Per-force choice within a coordinator-assigned constraint
+- [ ] Relational assignment at the coordinator, for the pairs only
+- [ ] Some tactics imply a distribution of work *inside* a force rather than one
+ order to everybody. A lance cannot all flank: most of it holds attention
+ while one unit takes the arc. Decide whether that is a pattern of
+ `TaskOverride`s or something the tactic expresses directly - this is open
+
+## Changing one
+
+A small state space with a cost to moving, which is what a stance's `grit` was
+reaching for and what a transition model does properly.
+
+- [ ] Legal and illegal transitions. `Retreat -> Advance` without a forcing event
+ is the flap that must be expensive: a force that routs, recovers nerve and
+ charges back into the guns it fled
+- [ ] **Elective** transitions pay the switching cost. **Forced** ones do not, and
+ have three callers: a target that no longer exists, a scenario condition,
+ and a GM. Hysteresis that applied to a forced transition would strand a
+ force whose target just died
+- [ ] Transition propensities are a small fittable table - a few features per
+ transition rather than a weight vector per tactic - which is learnable from
+ hundreds of matches rather than tens of thousands
+- [ ] A tactic can be **self-committing**: a force that manoeuvred into a
+ hull-down indirect-fire position and lost its spotter is worse off than the
+ switching cost alone says, because the position it chose has no fallback.
+ Price that separately from the flip cost
+
+## Preconditions
+
+Some tactics do nothing unless something else is arranged. `Bombard` without a
+spotter is the clear case - and the fallback to direct fire is often unavailable
+*because* the tactic put the force somewhere unseen.
+
+- [ ] Preconditions in the trait, checked at assignment and monitored during
+ execution. Failure is a forced transition, which shares a path with target
+ loss
+- [ ] Distinguish **hard** preconditions, which must be guaranteed, from **soft**
+ ones, which are only value and belong in scoring. `Flank` without something
+ fixing them still works, just worse
diff --git a/plan/training.md b/plan/training.md
index da1f34d..64204a0 100644
--- a/plan/training.md
+++ b/plan/training.md
@@ -197,7 +197,7 @@ neither side traded scores zero in every one of them.
### Why this many
-Not a fishing expedition. `plan/strategies.md` names a strategy only when it has
+Not a fishing expedition. `plan/tactics.md` names a strategy only when it has
an objective to fit it against, and `M`'s rank is bounded by the number of
independent labels - so the label list is the ceiling on how many distinct
behaviours this bot can ever have. Each pair below is a distinction a single
diff --git a/weights/fitted-r11.json b/weights/fitted-r11.json
new file mode 100644
index 0000000..9cf005e
--- /dev/null
+++ b/weights/fitted-r11.json
@@ -0,0 +1,79 @@
+{
+ "basis": "4a963bc90ee9",
+ "commit": "1d6bb72-dirty",
+ "epoch": 4,
+ "fit": {
+ "agreement": 0.505795574288725,
+ "credit": "ratio",
+ "decisions": 18980,
+ "excluded_dead": [],
+ "excluded_local": [
+ "damage_lead"
+ ],
+ "excluded_thin": {
+ "defense_6plus": 0.0029152468049227706
+ },
+ "framing": "difference",
+ "gamma": 0.93,
+ "imitation_rows": 0,
+ "label": "inflicted_bv_per_round",
+ "matches": 300,
+ "r_squared": 0.25177197083940683,
+ "ridge": 0.001,
+ "rows": 4989629,
+ "tier": "team"
+ },
+ "recorded": "2026-08-26T12:19:23Z",
+ "version": 1,
+ "weights": {
+ "ammo_spent": -0.005583938916848326,
+ "arc_spread": -0.041490291613012126,
+ "cohesion": -0.009868670870972785,
+ "concentration": 0.02022127772291372,
+ "cover_distance": 0.0017774731024245956,
+ "cover_quality": 0.003373572880928168,
+ "defense_1plus": 0.0050835906547254785,
+ "defense_2plus": 0.008607256110706996,
+ "defense_3plus": 0.011688985864039785,
+ "defense_4plus": 0.009114284761196571,
+ "defense_5plus": 0.002916497094067679,
+ "deploy_ground": 0.19515634709145324,
+ "deploy_outlook": 0.042561089331107645,
+ "edge_distance": 0.006407268220688474,
+ "elevation_gain": 0.009478807745659878,
+ "enemy_centroid": -0.03757791173545799,
+ "enemy_nearest": 0.008169262324644886,
+ "expected_damage": 0.0868457633280001,
+ "friend_support": -0.0017598950514153594,
+ "heat_ammo_explosion_risk": 0.009666918204454963,
+ "heat_incurred": -0.0010975732895659937,
+ "heat_mp_penalty": 0.014695829065605773,
+ "heat_shutdown_risk": -0.002301747354170842,
+ "heat_to_hit_penalty": -0.04414237859180607,
+ "incoming_damage": -0.033359258713535324,
+ "level_terrain": 0.0020237858343682587,
+ "level_tmm": -0.02868158004198141,
+ "los_in": -0.03664959542283217,
+ "los_out": 0.03762021772135282,
+ "overkill": -0.05117732934229372,
+ "overlook": 0.0049983993437264845,
+ "p_breach": -0.0028073621032410757,
+ "p_kill": 0.051868493725396765,
+ "p_mission_kill": 0.01375460875467852,
+ "p_psr_threshold": 0.026822935429581307,
+ "percent_options": 0.0024515726845411186,
+ "range_band_fit": 0.009862662171576714,
+ "range_spread": 0.023525725766304923,
+ "rear_arc_exposure": -0.003428660264005039,
+ "rear_arc_gain": -0.0008191071591288034,
+ "submerged": -0.00865594771519558,
+ "target_breach": 0.00834439224425374,
+ "target_current_bv": 0.042813401891670544,
+ "target_gunnery": 0.019485511402631727,
+ "target_health": -0.03830512354548032,
+ "target_original_bv": 0.003992044711032523,
+ "target_piloting": -0.0016147482852539314,
+ "target_tonnage": -0.013093796687275725,
+ "value_destroyed": 0.01627238956736042
+ }
+}
diff --git a/weights/fitted-r12-nodisp.json b/weights/fitted-r12-nodisp.json
new file mode 100644
index 0000000..7b18d02
--- /dev/null
+++ b/weights/fitted-r12-nodisp.json
@@ -0,0 +1,79 @@
+{
+ "basis": "4a963bc90ee9",
+ "commit": "44a2fb1-dirty",
+ "epoch": 4,
+ "fit": {
+ "agreement": 0.44325605900948367,
+ "credit": "ratio",
+ "decisions": 15259,
+ "excluded_dead": [],
+ "excluded_local": [
+ "damage_lead"
+ ],
+ "excluded_thin": {
+ "defense_6plus": 0.001895964796321773
+ },
+ "framing": "difference",
+ "gamma": 0.93,
+ "imitation_rows": 0,
+ "label": "inflicted_bv_per_round",
+ "matches": 300,
+ "r_squared": 0.23093241010648735,
+ "ridge": 0.001,
+ "rows": 3623485,
+ "tier": "team"
+ },
+ "recorded": "2026-08-27T16:15:21Z",
+ "version": 1,
+ "weights": {
+ "ammo_spent": -0.0040440916108910865,
+ "arc_spread": -0.042187416472592006,
+ "cohesion": -0.01650755161506821,
+ "concentration": 0.019974712453658432,
+ "cover_distance": 0.0011736029696675463,
+ "cover_quality": 0.009434758161851636,
+ "defense_1plus": 0.023363419384688573,
+ "defense_2plus": 0.023414112091617752,
+ "defense_3plus": 0.0235496591587149,
+ "defense_4plus": 0.020789287808407175,
+ "defense_5plus": 0.017656144070919522,
+ "deploy_ground": 0.1929808397950484,
+ "deploy_outlook": 0.04200026896495738,
+ "edge_distance": 0.004811751704553361,
+ "elevation_gain": 0.0017929428505074332,
+ "enemy_centroid": -0.008058343095501592,
+ "enemy_nearest": -0.007966114971400996,
+ "expected_damage": 0.06362481673739745,
+ "friend_support": -0.0021540862220886005,
+ "heat_ammo_explosion_risk": 0.015941394457049785,
+ "heat_incurred": 0.010725158379188792,
+ "heat_mp_penalty": 0.019405980157209044,
+ "heat_shutdown_risk": 0.004994582290725599,
+ "heat_to_hit_penalty": -0.0656866691643483,
+ "incoming_damage": -0.07199198543012904,
+ "level_terrain": -0.03953732240882364,
+ "level_tmm": -0.08972856058036631,
+ "los_in": -0.03649871483661407,
+ "los_out": 0.03168369651119106,
+ "overkill": -0.04439591088755461,
+ "overlook": 0.004533115858180404,
+ "p_breach": -0.0006556012245436022,
+ "p_kill": 0.04663591851545215,
+ "p_mission_kill": 0.00472071565708572,
+ "p_psr_threshold": 0.03294790256926541,
+ "percent_options": 0.002678445975289285,
+ "range_band_fit": 0.015110783087060538,
+ "range_spread": 0.01827705972896492,
+ "rear_arc_exposure": 0.006089577978090319,
+ "rear_arc_gain": -0.0015940991529187177,
+ "submerged": -0.007257306204500381,
+ "target_breach": 0.010599414295630352,
+ "target_current_bv": 0.03934925565011759,
+ "target_gunnery": 0.018812938995083548,
+ "target_health": -0.03611410119183127,
+ "target_original_bv": 0.004277682136240265,
+ "target_piloting": -0.0020798525730568013,
+ "target_tonnage": -0.010642799643238048,
+ "value_destroyed": 0.0027998064280556787
+ }
+}