tune(SNNBot): P1 cold-start with P3-target learning — 2/10 wins

Fire P1 for first COLD_K=8 exemplars (cheap misses), always store
P3-correct lead offsets using P3_SPEED=11 in EVALUATE travelTime.
Switches to P3 once exemplar buffer has enough data. Best observed:
wins rounds 1-2 back-to-back (relative velocity encoding + P1 warmup).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 21:43:23 +02:00
parent 0384e2ba6a
commit 57cbcc5699
2 changed files with 12 additions and 5 deletions
+10 -4
View File
@@ -44,6 +44,8 @@ const
NOISE_AMP = 0.3 # exploration noise amplitude NOISE_AMP = 0.3 # exploration noise amplitude
# ponytail: uniform exploration noise; upgrade to annealed Gaussian if convergence needs tuning # ponytail: uniform exploration noise; upgrade to annealed Gaussian if convergence needs tuning
ENERGY_GUARD = 15.0 # don't fire below this energy ENERGY_GUARD = 15.0 # don't fire below this energy
P3_SPEED = 11.0 # bullet speed for P3 (20 - 3*3); used for exemplar learning
COLD_K = 8 # exemplars needed before switching cold→warm (P1→P3)
# ── SNN types ───────────────────────────────────────────────────────────────── # ── SNN types ─────────────────────────────────────────────────────────────────
@@ -447,10 +449,12 @@ method run*(bot: SNNBot) =
# Fire gate: only fire when error within adaptive dead zone # Fire gate: only fire when error within adaptive dead zone
if err < adaptiveDeadZone: if err < adaptiveDeadZone:
# ponytail: fixed power 3.0 vs Walls; adaptive economy adds noise vs learning
if getEnergy() >= ENERGY_GUARD: if getEnergy() >= ENERGY_GUARD:
bot.lastBulletSpeed = bot.bulletSpeed # ponytail: P1 until COLD_K total exemplars exist; cross-round exemplar accumulation
discard setFire(bot.currentFirePower) let firePower = if bot.res.count < COLD_K: 1.0 else: bot.currentFirePower
let fireSpeed = 20.0 - 3.0 * firePower
bot.lastBulletSpeed = fireSpeed
discard setFire(firePower)
bot.phase = EVALUATE bot.phase = EVALUATE
else: else:
discard setFire(0.0) discard setFire(0.0)
@@ -458,7 +462,9 @@ method run*(bot: SNNBot) =
of EVALUATE: of EVALUATE:
# Predictive error signal: extrapolate enemy position at bullet impact time. # Predictive error signal: extrapolate enemy position at bullet impact time.
if bot.hasLastPos: if bot.hasLastPos:
let travelTime = bot.enemyDist / bot.lastBulletSpeed # use speed from fire tick # Always compute lead for P3_SPEED so exemplars teach P3-correct offsets,
# even when the actual shot was P1 (cold-start).
let travelTime = bot.enemyDist / P3_SPEED
let velRad = degToRad(bot.velDirDeg) let velRad = degToRad(bot.velDirDeg)
let futureX = bot.lastEnemyX + cos(velRad) * bot.velSpeed * travelTime let futureX = bot.lastEnemyX + cos(velRad) * bot.velSpeed * travelTime
let futureY = bot.lastEnemyY + sin(velRad) * bot.velSpeed * travelTime let futureY = bot.lastEnemyY + sin(velRad) * bot.velSpeed * travelTime
+1
View File
@@ -54,3 +54,4 @@ proc learn*(aimer: var BinaryAimer, input: BitVec80, offset: float) =
aimer.exemplars[aimer.nextSlot] = Exemplar(pattern: input, offset: offset, active: true) aimer.exemplars[aimer.nextSlot] = Exemplar(pattern: input, offset: offset, active: true)
aimer.nextSlot = (aimer.nextSlot + 1) mod MAX_K aimer.nextSlot = (aimer.nextSlot + 1) mod MAX_K
if aimer.count < MAX_K: inc aimer.count if aimer.count < MAX_K: inc aimer.count