id: shape title: A feature's weight is a curve, not a number status: blocked dependsOn: [training, features] exitCriterion: > Every feature contributes f(x) from a stored curve, monotone wherever the semantics demand it, and a decision log still prints named contributions that sum to the score. #
shape #
Weights::score is total += weight * reading.value. One number per column
says more is proportionally better, forever, and nothing in BattleTech is
shaped like that: damage saturates past what a location can absorb, heat has a
knee, a piloting roll has a cliff at twenty, distance to an objective is
plausibly U-shaped.
overkill - "the chance this volley puts more damage into some location than
that location can absorb" - is a hand-built column for a shape a weight could
not express. It is the evidence that some of the 54 columns exist to fake
curves.
The change #
weight * value becomes f(value): a piecewise-linear curve with a handful of
knots, stored in the weights file as a short knot table. Still a dot product
over an expanded basis, so fitting stays convex and Weights::score barely
moves. A knot table is more legible than a scalar, not less, and the vignette
becomes a plot of the curve rather than a number beside a board.
Monotonicity is the part worth insisting on #
Constrain the curve's direction wherever the semantics demand one: more
expected_damage is never worse, more attacker_self_damage is never better.
That makes the model structurally unable to learn something a player would
call absurd, and because it is a regulariser it costs less data rather than
more - which matters when a config-identical control arm has moved a headline
metric by 23 points.
Interactions, named only #
A GAM is additive and cannot form a product. That is not theoretical:
value_destroyed and target_current_bv are both fractions, and absolute
value removed is their product, which no sum of the two can reach. Add a
pairwise term only where the mechanism fits in one sentence, each with its own
vignette. Never a search over pairs. overkill is the precedent.
Fix the fingerprint first #
basis_fingerprint hashes each feature's name, normalisation and description,
so it is already blind to a changed computation. Curves make that worse: two
fits with identical column names and different knots would be
indistinguishable. This has to be extended before the first curve lands.
Out of scope #
MLP, embeddings, transformer. Post-hoc explanation is not interpretability - a SHAP value says what an opaque model did on one input; it does not let a player read the reasoning and say it is wrong about BattleTech. The additive decomposition is the product, not the accuracy.