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 <noreply@anthropic.com>
This commit is contained in:
@@ -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..<GFBins:
|
||||
result.bins[i] = GFPrior
|
||||
let d = abs(i - center)
|
||||
result.bins[i] = GFPrior + 0.5 / float(1 + d)
|
||||
|
||||
proc gfToIndex(gf: float): int {.inline.} =
|
||||
clamp(int(round((gf + 1.0) * 0.5 * float(GFBins - 1))), 0, GFBins - 1)
|
||||
@@ -60,15 +65,19 @@ proc predict*(g: var GFGun, state: WorldState, bulletSpeed: float): GunPredictio
|
||||
fireX: state.selfX,
|
||||
fireY: state.selfY,
|
||||
fireBearing: bearing,
|
||||
mea: mea,
|
||||
)
|
||||
g.cachedWaveStored = true
|
||||
|
||||
let gfAngle = bearing + indexToGF(g.peakBin()) * mea
|
||||
let peak = g.peakBin()
|
||||
let peakGF = indexToGF(peak)
|
||||
let gfAngle = bearing + peakGF * mea
|
||||
# Aim from self at gfAngle, at current dist (angular targeting)
|
||||
let px = state.selfX + cos(gfAngle) * dist
|
||||
let py = state.selfY + sin(gfAngle) * dist
|
||||
|
||||
when DebugGF:
|
||||
echo fmt"[gf-dbg] predict: peakGF={peakGF:.2f} peakBin={peak} mea={radToDeg(mea):.1f}° aimAngle={radToDeg(gfAngle):.1f}° waves={g.waves.len}"
|
||||
|
||||
GunPrediction(
|
||||
x: clamp(px, BotRadius, state.arenaWidth - BotRadius),
|
||||
y: clamp(py, BotRadius, state.arenaHeight - BotRadius),
|
||||
@@ -87,6 +96,10 @@ proc onResult*(g: var GFGun, e: FeedbackEvent) =
|
||||
let w = g.waves[0]
|
||||
g.waves.delete(0)
|
||||
|
||||
# Recompute mea from the actual bullet power (correct per-bin, not the cached first-bin mea)
|
||||
let speed = bulletSpeed(e.bulletPower)
|
||||
let mea = arcsin(clamp(8.0 / speed, -1.0, 1.0))
|
||||
|
||||
# Compute actual bearing from fire position to where the enemy actually was
|
||||
let actualDx = e.actualX - w.fireX
|
||||
let actualDy = e.actualY - w.fireY
|
||||
@@ -96,9 +109,12 @@ proc onResult*(g: var GFGun, e: FeedbackEvent) =
|
||||
while bearingDelta > 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..<GFBins:
|
||||
let dist = abs(i - centerIdx)
|
||||
|
||||
Reference in New Issue
Block a user