diff --git a/plan/features.md b/plan/features.md index 1a3a07c..58730fd 100644 --- a/plan/features.md +++ b/plan/features.md @@ -2150,3 +2150,70 @@ no damage on a target. - [x] Give `InfantryWeaponHandler` a declared exclusion off the handler rather than off the name. 648 pairs, one wire field, and the silent zeros fell from 402 to 44 + +## On value-weighting `expected_criticals`, and the +0.77 collinearity + +The ablation on the 45-match corpus put `expected_criticals` at **+0.77 +collinear with `value_destroyed`**, r² +0.0042 (an upper bound, since least +squares cannot do worse with an extra column) and agreement **−0.0044**, which +is the informative half and went the wrong way. The suggested remedy is to +weight criticals by `Location::value()` so the column becomes independent rather +than collinear. + +**I think value-weighting would make the collinearity worse, not better**, and +the reason is what `value_destroyed` already is. + +`value_destroyed` is `sum over locations of P(destroyed) x value(location)`. It +is *already* the value-weighted column. Weighting criticals by the same function +would give the two columns a shared weighting basis on top of a shared driver - +both would be "damage getting into locations, priced by `Location::value()`" and +differ only in whether the location dies. That aligns them; it does not separate +them. + +**What is actually orthogonal is the through-armour term**, and it is orthogonal +by construction rather than by choice. Four LB-X firing cluster, measured: + +| target | `expected_criticals` | of which TAC | `value_destroyed` | +|---|---|---|---| +| whole armour | 0.393 | **100%** | **0.00000** | +| half armour | 0.494 | 79.5% | **0.00000** | +| stripped | 10.160 | 3.9% | 0.00573 | + +Where this column is entirely the natural 2, the other column is exactly zero - +a critical through intact armour destroys nothing, so `value_destroyed` cannot +see it at any weighting. The 0.77 has to be coming from the stripped regime, +where the internal term dominates and the two are measuring the same event from +two sides. That part is *meant* to correlate: the volley that destroys a +location is the volley that got inside it. + +**Two things worth checking before anyone fits to that number.** + +The ablation may have measured a column without its orthogonal half. The +through-armour term landed late in this branch; a corpus recorded before it +carries the internal term alone, which is precisely the half collinear with +`value_destroyed`. Whoever repeats this at ~190 matches should check which +version the corpus holds - the answer is in whether a volley against intact +armour scores anything at all. + +And the corpus was recorded by a bot whose weights did not price the column, so +it contains no decision that was *taken* to fish for criticals. A feature that +exists to enable a behaviour the recording bot never exhibited will look +uninformative on that corpus close to by construction. That is not an argument +that the column is good; it is an argument that this corpus cannot say. + +**If decorrelation is wanted, split the column rather than weighting it.** The +two terms are already separate in the arithmetic, they have different drivers - +one scales with internal damage, the other with the *count* of hit-location +rolls - and they mean different things a player would name differently: +exploiting a hole, and fishing for one. Splitting costs nothing but a column and +would let the fit price them apart. Value-weighting costs the thing the feature +was built to be. + +- [ ] jmm's original objection to value-weighting stands and is about + calibration rather than collinearity: `Location::value()`'s own + documentation says its figures are round numbers chosen to rank locations + and that nothing downstream should read them as calibrated. Importing them + into a column whose purpose *is* calibration is the trade to argue about, + and it has not changed +- [ ] Re-run the ablation at ~190 matches against a corpus that carries the + through-armour term, and split the column before weighting it