fix(guns): per-bin wave queues unbreak GF/DecayGF/KNN learning; fix vbullet drops
Wave queues (guess_factor, decay_gf, knn_gun): predict() stored ONE wave per tick while onResult() popped one per resolved bullet (~4/tick), so the queue drained to empty within a few dozen ticks, ~3 of every 4 resolutions returned without learning, and the survivor paired with a same-tick wave (bearingDelta ~= 0) pinning the histogram at centre. PROOF: GF.vHits == HeadOn.vHits and DecayGF.vHits == HeadOn.vHits byte-for-byte in every one of 50 rounds — the guns had degenerated to HeadOn. Now each gun keeps a per-bin FIFO with an O(1) head cursor. At most one push per (tick, bin) so the fire site's 5th predict() call is a no-op, and onResult pops the oldest wave of its OWN bin via e.bulletPower. Aiming math untouched (it was already correct: 0 deg = East, CCW+). maxBullets 2048 -> 8192: the rack spawns 52 bullets/tick so the ring wrapped every ~39 ticks while a long power-3 shot needs ~90, silently discarding unresolved bullets and biasing every measured hit rate by range. Added a droppedBullets counter so a future overflow is measurable, and wavePushes/ waveStarved counters on the three guns. After the fix: vDropped = 0 and vStarved = 0 across all 48 recorded rounds. fitnessFor is now exported, deterministic (enemies iterated in ascending id order) and shared by the selector and the stats dump, replacing a hand-rolled merge in ModularBot that never advanced its window head. Round lines gain additive keys: vDropped, vStarved.
This commit is contained in:
@@ -199,24 +199,10 @@ method onRoundEnded*(bot: ModularBot, e: RoundEndedEventForBot) =
|
||||
## Dump per-gun virtual bullet stats to /tmp/gun_stats.jsonl (one line per round).
|
||||
# Use lastKnownTargetId: currentTargetId is -1 if enemy died before round end
|
||||
let targetId = if bot.currentTargetId >= 0: bot.currentTargetId else: bot.lastKnownTargetId
|
||||
# Aggregate fitness across all tracked enemies (or just the primary target if known).
|
||||
# Use aggregate (targetId=-1) so we always have data even if target switched.
|
||||
let fit = block:
|
||||
var f: seq[vb.GunFitness]
|
||||
if targetId >= 0 and targetId in bot.tracker.fitness:
|
||||
f = bot.tracker.fitness[targetId]
|
||||
else:
|
||||
# aggregate across all enemies
|
||||
f = newSeq[vb.GunFitness](bot.tracker.numGuns)
|
||||
for perEnemy in bot.tracker.fitness.values:
|
||||
for gid in 0..<bot.tracker.numGuns:
|
||||
for binIdx in 0..<len(vb.PowerBins):
|
||||
let src = perEnemy[gid].bins[binIdx]
|
||||
let n = min(src.count, vb.WindowSize)
|
||||
for k in 0..<n:
|
||||
f[gid].bins[binIdx].hits[(f[gid].bins[binIdx].head + f[gid].bins[binIdx].count) mod vb.WindowSize] = src.hits[k]
|
||||
inc f[gid].bins[binIdx].count
|
||||
f
|
||||
# Per-target fitness when known, else a deterministic recency-weighted aggregate
|
||||
# over all enemies (see VirtualTracker.fitnessFor). Using the shared proc keeps
|
||||
# this stats dump identical to what the gun selector sees.
|
||||
let fit = bot.tracker.fitnessFor(targetId)
|
||||
|
||||
var gunsArr = newJArray()
|
||||
for gid in 0..<13:
|
||||
@@ -251,7 +237,9 @@ method onRoundEnded*(bot: ModularBot, e: RoundEndedEventForBot) =
|
||||
"realShots": bot.realShotsFired,
|
||||
"realHits": bot.realHits,
|
||||
"score": e.results.totalScore,
|
||||
"bulletDmg": e.results.bulletDamage
|
||||
"bulletDmg": e.results.bulletDamage,
|
||||
"vDropped": bot.tracker.droppedBullets,
|
||||
"vStarved": bot.guessFactor.waveStarved + bot.decayGF.waveStarved + bot.knnGun.waveStarved
|
||||
}
|
||||
let f = open("/tmp/gun_stats.jsonl", fmAppend)
|
||||
f.writeLine($row)
|
||||
@@ -269,6 +257,15 @@ method onRoundStarted*(bot: ModularBot, e: RoundStartedEvent) =
|
||||
bot.gunSelectionCount[i] = 0
|
||||
bot.gunRealShots[i] = 0
|
||||
bot.gunRealHits[i] = 0
|
||||
# Reset per-round integrity counters so each /tmp/gun_stats.jsonl line reports
|
||||
# that round's numbers (summing the lines gives the session total).
|
||||
bot.tracker.droppedBullets = 0
|
||||
bot.guessFactor.wavePushes = 0
|
||||
bot.guessFactor.waveStarved = 0
|
||||
bot.decayGF.wavePushes = 0
|
||||
bot.decayGF.waveStarved = 0
|
||||
bot.knnGun.wavePushes = 0
|
||||
bot.knnGun.waveStarved = 0
|
||||
setAdjustGunForBodyTurn(true)
|
||||
setAdjustRadarForBodyTurn(true)
|
||||
setAdjustRadarForGunTurn(true)
|
||||
|
||||
Reference in New Issue
Block a user