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.
The user's goal was "a TM gun that can learn fast and generalize better".
Swept offline over the real DrussGT fixtures (no live battles) by coordinate
descent, one lever at a time, with a SHUFFLED-FEEDBACK CONTROL - a TM trained
on randomised targets. That control is what settles the question.
Final confirmation, 4 seeds each (~74,600 first-100-tick bullets per config):
config EARLY(first 100) OVERALL
Shuf_w3 (RANDOM feedback) 23.9% 20.0%
win3_s1.1 (best real TM found) 23.7% 20.2%
Shuf_w10 (RANDOM feedback) 23.1% 20.0%
win3_st100 (prior job's edit) 23.0% 20.1%
win3_off (TM correction ~= 0) 22.7% 20.2%
def_w10 (shipped default) 22.1% 20.3%
Linear (deterministic reference) 34.0% 24.3%
The best real config beats the default early (23.7% vs 22.1%, non-overlapping
per-seed ranges, z=+7.34, p=2e-13) - but its own SHUFFLED control scores 23.9%,
i.e. HIGHER, z=-0.91, p=0.37. Random targets do at least as well. So the early
gain is not learning.
Per-lever screens were flat: TM_N_CLAUSES 25/50/100/200 all 23.0% early,
completely flat; TM_N_STATES 4/32/100 all ~22-23% (unstable across seeds);
TM_S mildly monotonic (lower better early); TM_T flat; TM_WINDOW_SIZE 2/3/10
all within noise of each other and of the shuffled control.
Two further findings:
- The TM-off ablation (correction ~= 0) scores 22.7%/20.2%, essentially the
same as TM-on. The TM's correction is near-zero-mean noise; the gun's
one-shot internal linear baseline accounts for its accuracy.
- The TM gun is 10.3 pp behind Linear early and 4.1 pp behind overall. That
deficit is in the BASELINE MODEL (LinearGun iterates flight time; this gun
does not), not in the TM hyper-parameters. Tuning knobs cannot close it.
Conclusion: do not tune TM hyper-parameters further. Either the input
representation or the prediction target is what needs to change - the shuffled
control shows the TM is not extracting target information beyond its baseline.
Defaults left UNCHANGED (window=10/states=32/S=1.5/T=25/clauses=50); an
uncommitted prior edit (window=3/states=100) was reverted as unsupported.
Hyper-parameters are now compile-time overridable (-d:TM_WINDOW_SIZE=3 etc.)
so future sweeps need no gun edit.
NOT MEASURED: real hit rate vs DrussGT (offline only by design). The repo's own
docs/gun_rack_analysis.md 2 reports the virtual metric is a sign-unstable ranker
of real hit rate, so the comparison against "Linear 10.7% real" is not direct -
whether the TM is competitive live is INFERRED-unknown, not measured.
Guards: test_gun_harness 39/39, test_vbullet_metric, test_power_selection,
test_tsetlin_gun, test_tm_pattern_learning all green.
TASK 2 - power selection, a clear win. bestPower used an ABSOLUTE
MinHitRate = 0.40 bar. Measured per-bin virtual rates (rolling-100 fraction)
show no bin ever clears 40%, so 11 of 14 guns were stuck at bin 0 (power 1.0)
even where higher bins were comparable:
Linear p1.0 44% p1.5 39% p2.0 30% p3.0 29% old bin 0 -> new bin 3
Accel p1.0 44% p1.5 40% p2.0 26% p3.0 29% old bin 1 -> new bin 3
Pattern p1.0 50% p1.5 40% p2.0 27% p3.0 12% old bin 1 -> new bin 2
Replaced with a scale-aware PowerBarFrac = 0.50 (a dimensionless FRACTION of
the gun's own best bin rate). 13 of 14 selections now pick heavier bullets.
Real effect vs DrussGT (8 rounds x 3 runs): hit rate unchanged (7.56% ->
7.47%) but damage dealt +52% (157 -> 239 per run) and rounds end faster.
Same accuracy, half the shots, half again more damage.
TASK 1 - the TM pattern-classifier gun does NOT earn its slot. It was built as
a mixture of experts with a corrected-Granmo TM as a multi-class gate over
HeadOn/Linear/Circular/WallBounce/Accel, labelled by which expert's prediction
was closest to the actual enemy position (an exact, supervised, per-shot
label - no delayed credit). Offline it loses to the best of its OWN experts on
essentially every fixture, and against DrussGT it cost real performance:
baseline (path+relative) 7.56% real hit rate, damage 157
+ power fix 7.47%, damage 239
+ power fix + TM gun 5.59%, damage 133
The gun was selected on 806 ticks and fired 24 real shots at 4.2%.
So the tree ships with EnableTmSelector = false: code and wiring kept intact
for re-enabling, but it is not in the active rack.
Worth recording from the clause dump: the gate DOES latch onto meaningful
structure. On energy-threshold-turner, HeadOn's clauses key on the energy bits
(the rule's own driving variable) while Circular keys on distance/velocity. So
the TM is learning something real and interpretable - it simply cannot beat
'always pick the best expert'. Root cause (INFERRED): the closest-expert label
is noisy because several experts are near-tied, and under the path metric the
winner varies by power bin while the gate sees one shared per-tick input, so a
one-vs-rest gate over a saturated 870-bit clause space has no margin to exploit.
(Zero-padding the 2-frame window was tried first and saturated every clause at
256-755 included literals; alternating the two real frames fixed that.)
Also factors the corrected feedback into an exported tmLearnDir and exports the
encoding/TM primitives; the Tsetlin tests still reproduce the documented
mean=13.8 included literals, so the refactor is behaviour-preserving.
Verified: 33/33 guard checks, tsetlin tests green, metric checks green, new
power-selection guard green (13/14 selections change; relative bar still picks
bin 1 and not bin 3 for a [30,25,12,5]% profile), 12/12 offline==online
acceptance under the shipped default.
The gun has never contributed anything: Tsetlin.vHits was byte-for-byte
equal to Linear.vHits in every measured round of every run, because its
learned correction was always exactly 0.
Six diagnosed defects fixed, plus one that was required to make the first
one work:
1. Type I now conditions on the clause output. It previously rewarded
included true literals unconditionally, omitting Granmo's (c=0, lk=1)
-> toward Exclude counter-force, so true literals ratcheted toward
Include forever. This was the root cause of the saturation.
2. Type II was unreachable dead code: its guard required cOut==1 AND
lits[lit]==0 AND st>0 (included), but cOut==1 guarantees every included
literal is 1. Its direction was wrong too - it should increment EXCLUDED
false literals when the clause fires.
3. Resource allocation restored: Granmo's (T - clip(v,-T,T))/(2T) target
replaces |error|/(2*RESID_MAX); TM_T was only an output normaliser.
4. Label baseline fixed - the factor-2 shrink. predX = linearX + cx, so the
label was delta - cx while the learner's output IS cx, giving
error = delta - 2cx and a fixed point of cx = delta/2: HALF the needed
correction even with perfect feedback. TmTrace now stores linearX/linearY
and training uses delta.
5. Hits no longer zero their label (a hit means |miss| < 18px, not 0).
6. The enemy-energy feature was duplicated - tmEncodeFrame passed
state.selfEnergy with a stale comment claiming enemyEnergy was absent,
while WorldState.enemyEnergy exists. Enemy-energy rules were literally
unrepresentable.
7. REQUIRED EXTRA: tmEvalClause now implements Granmo Eq. 6 - an all-Exclude
clause outputs 1 during learning and 0 during classification. Without it,
fix#1 deadlocks every clause at empty.
MEASURED EFFECT (energy-threshold-turner fixture, seed 1):
mean included literals per active clause 714.0 -> 13.8
active clauses 100/100 -> 53/100
nonzero corrections 8/764 -> 708/764
Tsetlin virtual hits (Linear = 27/400) 27/400 -> 69/400
Divergence achieved: offline on 7/8 fixtures, and in a live gauntlet
(RandomMover: Tsetlin 199/1200 vs Linear 288/1200, vDropped=vStarved=0).
Tsetlin now LEARNS but is not yet competitive with Linear - the regression
head is untuned, flagged as follow-up rather than claimed as a win.
Also ignores compiled test harnesses that have no file extension, which the
existing '**/tests/test_*' rule misses.
Four guns cached a whole prediction per tick while predict() is called once
per power bin, so every bin after the first (and the real fired shot, which
shares lastState) reused the power-1.0 lead. Fixed by caching only the
speed-INDEPENDENT derived state and recomputing the lead per requested speed:
- stop_shot: also fixes prevSpeed being written before it was read, which
made abs(speed) < abs(prev) permanently false and the entire
stop-prediction branch unreachable (it was just Linear).
- displacement: the cache key included bulletSpeed, so the guard missed on
all four bins and the 15-tick window advanced ~4x/tick, making the
inferred velocity ~4x too small.
- averaged_lead: tick cache removed outright. pattern_matcher: split into
speed-independent match+path and per-call lead.
FeedbackEvent gains fireTick/powerBin (additive; only virtual_bullets
constructs one) so guns can pair feedback to the exact shot instead of
guessing by coordinates. tsetlin uses it: traces are now keyed exactly by
(fireTick, powerBin) with a 1024-slot ring, and the 10-frame window shifts
at most once per tick (it was shifting ~4-5x/tick, so isWarmedUp tripped
after ~2 ticks).
KNOWN INCOMPLETE: tsetlin still does not diverge from Linear in battle. The
two named bugs are fixed (a 600-tick sim shows trainedShots=2141,
traceMisses=0, and a fixed-input probe converges to a 9.6px correction), but
the TM's clause feedback itself is broken: ~131 of 1740 literals end up
included per clause, so its conjunction never fires. Sweeping TM_S,
TM_N_CLAUSES and a two-branch Type-I update did not change the correction
from 0. Needs a real TM fix or removal, not another bug fix.
First-ever guard tests for the gun selector: common_libs/tests/
test_gun_harness.nim (14 checks, headless, no Java). There were none before,
which is how six broken guns survived a full analysis cycle. Against the
previous HEAD, 5 of these checks FAIL - that is the regression guard.
- 30-tick cooldown after ghost-stuck/timeout ram exit prevents re-entry loop
- enemy_tracker.update() skips dead bots to prevent same-tick scan resurrection
- TFIL graphics cleared when ramming is active movement
- [config] logs: white base with green-highlighted changes only
- [ram:enter] logs trigger reason and key values on false→true transition
- [death] and [target-invalid] logs retained for diagnostics