TMComposites gate: per-gun confidence faithful for 3 guns; no pair composes

Adds a per-sample intrinsic-confidence field (GunPrediction.confidence,
threaded through FeedbackEvent/VirtualBullet, populated by Pattern, DecayGF,
KNN, GuessFactor, Tsetlin, TMHorizon) and an offline recorder + analyzer that
reproduce the paper's Figure 2 per gun and its Eq-8 composite.

Measured on 3 held-out tr-bridge DrussGT battles (33k ticks, ~133k samples/gun):
- FAITHFUL: DecayGF (rho +0.133), KNN (+0.090), Pattern (+0.064, weak).
- GuessFactor is ANTI-faithful (rho -0.067); Tsetlin c_max is useless (0.001).
- No pair of guns specialises complementarily: the same gun dominates both
  high-confidence slices in every pair.
- Eq-8 alpha-normalised confidence-weighted composite: 18.41% vs Pattern
  20.45% (McNemar p=3.1e-126). Faithful-only variant 18.68%, still loses.
  Shuffle control passes weakly (composite > shuffle, p=4e-14) so ~0.7pp of
  competence is real but ~2pp short. Offline veto: design is dead.

See docs/tmcomposites_gate.md.
This commit is contained in:
2026-09-25 22:02:28 +02:00
parent d0750ab020
commit f58d65d2e8
12 changed files with 2666 additions and 11 deletions
@@ -35,6 +35,14 @@ type
GunPrediction* = object
## Absolute (x, y) where the gun predicts the enemy will be.
x*, y*: float
confidence*: float
## The gun's INTRINSIC, per-sample confidence in THIS prediction, on any
## positive scale the gun likes (the TMComposites gate normalises it by
## the sample range, Eq 7). 0.0 means "this gun has no confidence signal"
## (every deterministic geometric gun): such a gun casts no composite
## vote. Deliberately NOT a rolling hit-rate: that is the selector
## mechanism already measured negative (docs/gun_rack_analysis.md).
## See docs/tmcomposites_gate.md and common_libs/tests/measure_tmcomposites.nim.
FeedbackEvent* = object
## Outcome of a resolved virtual bullet.
@@ -45,6 +53,7 @@ type
powerBin*: int ## index into PowerBins the bullet belongs to
missDistance*: float ## px; < BotRadius = hit
hit*: bool
confidence*: float ## copied from the spawn-time GunPrediction (above)
proc bulletSpeed*(power: float): float {.inline.} =
20.0 - 3.0 * power