From 78723fce1380ce8a92d6a835e0d27c9494647e7c Mon Sep 17 00:00:00 2001 From: Davide Cappellini Date: Sun, 20 Sep 2026 01:05:22 +0200 Subject: [PATCH] fix(GFGun): head-on prior + per-bin mea in onResult - Cold-start bug: uniform bins[0..30]=0.1 made peakBin() always return 0 (first-wins tie), giving GF=-1 (max CW escape) before any learning. Fixed with a triangular head-on bump at bin 15 (GF=0) as the prior. - onResult now recomputes mea from FeedbackEvent.bulletPower instead of the stale first-bin mea cached by predict; correct per-power-bin GF. - Add DebugGF const (default false) with [gf-dbg] echoes in predict/onResult. Co-Authored-By: Claude Sonnet 4.6 --- common_libs/guns/guess_factor.nim | 32 +++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/common_libs/guns/guess_factor.nim b/common_libs/guns/guess_factor.nim index 43fac7d..93dee1d 100644 --- a/common_libs/guns/guess_factor.nim +++ b/common_libs/guns/guess_factor.nim @@ -2,18 +2,19 @@ ## Bins: 31, ranging GF -1 (max CW escape) to +1 (max CCW escape). ## Learns from virtual bullet outcomes; caches wave state per-tick. -import std/math +import std/[math, strformat] import gun_harness/gun_interface const - GFBins = 31 - GFPrior = 0.1 + GFBins = 31 + GFPrior = 0.1 + DebugGF* = false type Wave = object fireX, fireY: float fireBearing: float # atan2(enemyY-selfY, enemyX-selfX) at fire tick (rad) - mea: float # max escape angle (rad) + # mea not stored — recomputed from FeedbackEvent.bulletPower at resolution time GFGun* = object bins: array[GFBins, float] @@ -24,8 +25,12 @@ type proc initGFGun*(): GFGun = result.cachedTick = -1 + # Seed with a head-on prior: triangular bump at bin 15 (GF=0). + # Prevents the cold-start tie-break to GF=-1 (bin 0) that poisons early fitness. + let center = (GFBins - 1) div 2 # = 15 for i in 0.. PI: bearingDelta -= 2.0*PI while bearingDelta < -PI: bearingDelta += 2.0*PI - let gf = if w.mea > 1e-10: clamp(bearingDelta / w.mea, -1.0, 1.0) else: 0.0 + let gf = if mea > 1e-10: clamp(bearingDelta / mea, -1.0, 1.0) else: 0.0 let centerIdx = gfToIndex(gf) + when DebugGF: + echo fmt"[gf-dbg] onResult: fireBearing={radToDeg(w.fireBearing):.1f}° actualBearing={radToDeg(actualBearing):.1f}° delta={radToDeg(bearingDelta):.1f}° MEA={radToDeg(mea):.1f}° GF={gf:.2f} peakBin={centerIdx}" + # Triangular smoothing kernel over adjacent bins for i in 0..