89370008da36a1fcbaa3f3a5035c890d6b2a5031
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
89370008da |
fix(tsetlin): make the TM actually learn - saturation 714 -> 13.8 literals/clause
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. |
||
|
|
974528d5cf |
feat(gun_harness): offline gun range, proven equivalent to live play
Gun evaluation previously required a full end-to-end battle (Java server + battle runner + websocket IPC to 2 bot processes, 50 rounds, ~3.4 min) and yielded only ~300-900 REAL shots across 13 guns -- far too few to rank guns, which is why tuning needed many repetitions. VirtualTracker is already a pure function of (WorldState stream, gun list); the only reason it needed Java was where WorldState came from. So the range replays a seq[WorldState] through the SAME tracker: offline and online scores are the same metric by construction, not an approximation. ACCEPTANCE TEST (the point of the whole thing): record one live round, replay it offline, compare per-gun virtual hit rates. 12/12 deterministic guns match EXACTLY, reproduced twice. Tsetlin is compared separately because tmLearnOne calls rand(). Getting to 12/12 exposed two real ordering quirks in the live loop: run() calls go() before the aim/fire block, so tickBullets resolves against the NEXT tick's scan while the prediction used the previous one; and if the target dies during that go() the final tick's spawn+resolution is skipped entirely. The recorder emits an end marker for the second case. The 5th (selected-gun) predict call was verified to be a no-op. Measured cost: 8 fixtures (1770 ticks, ~92k virtual bullets, 13 guns) replay in 2.9 s, ~32k virtual bullets/s -- roughly 70x faster and 100x more samples than a live gauntlet. Also adds a per-tick WorldState recorder behind const RecordWorldState (default off, mirrors the ShotLog idiom) which records the state the bot ACTUALLY builds, staleness included, rather than true positions -- recording the latter would hand the guns perfect information and produce flattering scores. 9 new guard checks (33 total, all passing), including fixture round-trip, replay determinism, stationary->HeadOn 100%, constant-velocity->Linear>HeadOn, and the energy-threshold turner crossing at t=41. |
||
|
|
3c90a5941d |
feat(selector): range-aware firing gate fitted to 2611 measured shots
Measured, not assumed. With the gate temporarily opened to 20 deg, every real shot was logged (tick, angle error at fire time, distance, power, hit) across 3 gauntlets: 2611 shots, 57.3% aggregate. Findings: - The geometric cone atan(BotRadius/d) is directionally confirmed but a WEAK lever: even at 0.0-0.1 deg error the hit rate at 400-600px is only ~53-57%, because PREDICTION error dominates alignment error. - Real effect of tightening the gate: 57.9% -> 68.0% aggregate hit rate (fixed 0.1 deg), not the 76.9% previously reported -- that was a high-variance draw (per-rep 62.8/66.4/77.2%). - The shipped range-aware gate (SafetyFactor 0.6) does NOT beat the fixed 2.0 deg gate on hit rate (55.8% vs 57.9%, ~1.5 sigma, inside noise). It fires 22-28% more shots and therefore lands more total hits (~509 vs ~434 per rep). No per-adversary score delta exceeded the 300-point run-to-run noise band, so no config is demonstrably better on score. Shipped anyway because it is strictly more expressive (a fixed threshold is the special case), tunable from one const, and physically motivated, but the honest verdict is recorded in-code: the gate is not the bottleneck. AimThresholdDeg is removed; shouldFire now takes distPx. Degenerate or NaN distance falls back to the ceiling rather than dividing by zero. Also adds a per-shot logger to ModularBot behind 'const ShotLog' so the measurement above is reproducible, and 10 new guard checks (24 total, all passing) covering monotonicity, clamping, formula, perfect alignment, gross misalignment and degenerate distance. Cross-checked against the server source: the gun fires BEFORE the turn is applied, so the logged angle error is the true departure error, and fireAssist auto-aim is off (unset by the Nim API and forced false by setAdjustRadarForGunTurn). |
||
|
|
e53690036b |
fix(guns): speed-sensitive caches, dead stop-shot branch, exact TM trace pairing
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. |