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
+6 -2
View File
@@ -348,6 +348,7 @@ type
bulletSpeed*: float
travelDist*: float ## accumulated px so far
fireDist*: float ## distance to target at fire time
confidence*: float ## spawn-time GunPrediction.confidence (TMComposites gate)
active*: bool
pointScored*: bool ## the parallel point-model score for this flight has
## been recorded (tie-break mode only)
@@ -461,6 +462,7 @@ proc spawnBullets*(t: var VirtualTracker, gunId: GunId,
bulletSpeed: speed,
travelDist: 0.0,
fireDist: fireDist,
confidence: pred.confidence,
active: true,
pointScored: false,
hitSeen: false,
@@ -643,7 +645,7 @@ proc tickBullets*(t: var VirtualTracker, state: WorldState,
t.fitness[b.targetId][b.gunId].bins[b.powerBin].record(hit)
let fe = FeedbackEvent(
prediction: GunPrediction(x: b.aimX, y: b.aimY),
prediction: GunPrediction(x: b.aimX, y: b.aimY, confidence: b.confidence),
actualX: ex,
actualY: ey,
bulletPower: PowerBins[b.powerBin],
@@ -651,6 +653,7 @@ proc tickBullets*(t: var VirtualTracker, state: WorldState,
powerBin: b.powerBin,
missDistance: missDist,
hit: hit,
confidence: b.confidence,
)
onResolved(b.gunId, b.powerBin, fe)
b.active = false
@@ -720,7 +723,7 @@ proc tickBullets*(t: var VirtualTracker, state: WorldState,
if b.targetId in t.fitness:
t.fitness[b.targetId][b.gunId].bins[b.powerBin].record(b.hitSeen)
let fe = FeedbackEvent(
prediction: GunPrediction(x: b.aimX, y: b.aimY),
prediction: GunPrediction(x: b.aimX, y: b.aimY, confidence: b.confidence),
actualX: rx,
actualY: ry,
bulletPower: PowerBins[b.powerBin],
@@ -728,6 +731,7 @@ proc tickBullets*(t: var VirtualTracker, state: WorldState,
powerBin: b.powerBin,
missDistance: missDist,
hit: b.hitSeen,
confidence: b.confidence,
)
onResolved(b.gunId, b.powerBin, fe)
b.active = false