diff --git a/plan/strategies.md b/plan/strategies.md index 1e2d92d..531a551 100644 --- a/plan/strategies.md +++ b/plan/strategies.md @@ -19,8 +19,8 @@ the drift rate once it moves - facets of stubbornness, and splitting them invite tuning them against each other. Already built and tested against `Intent`; it carries over unchanged. -- [ ] Rows of `M`: `direct_engagement`, `destroy_weakest`, `gun_line`, `flanking`, - `fighting_withdrawal`, `crossfire`, `cripple`, `bait`, `screen`, `overrun` +- [ ] 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 @@ -28,3 +28,50 @@ carries over unchanged. 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.