j131 learned movement: real bullet-endpoint resolution (TR_LEARNED_REAL_EVENTS, default off) + exact-geometry Gate A/B (inversion NOT fixed; state still the constraint)

This commit is contained in:
2026-09-26 11:54:11 +02:00
parent d5047598b5
commit cc332138b3
6 changed files with 569 additions and 14 deletions
+21
View File
@@ -620,6 +620,15 @@ method onBulletHitWall*(bot: ModularBot, e: BulletHitWallEvent) =
bot.strafeMover.removeBulletNear(e.bullet.x, e.bullet.y)
bot.surfMover.removeBulletNear(e.bullet.x, e.bullet.y)
bot.learnedMover.removeBulletNear(e.bullet.x, e.bullet.y)
# Real wall endpoint of an ENEMY bullet. On the running server (0.35.5) a
# BulletHitWallEvent is delivered only to the bullet's OWNER
# (`addPrivateBotEvent(bullet.botId, ...)`), so this branch is a no-op live;
# it is wired so a server that exposes enemy wall hit-points feeds the
# exact-geometry wave resolution instead of the arrival-deadline proxy.
if e.bullet.ownerId != getMyId():
discard bot.learnedMover.resolveEnemyBullet(
e.bullet.x, e.bullet.y, degToRad(e.bullet.direction),
e.bullet.ownerId, bot.tick, false)
method onBulletHitBullet*(bot: ModularBot, e: BulletHitBulletEvent) =
discard bot.resolveOwnBullet(e.bullet.bulletId) # bullet-vs-bullet: free the slot
@@ -630,6 +639,13 @@ method onBulletHitBullet*(bot: ModularBot, e: BulletHitBulletEvent) =
bot.strafeMover.removeBulletNear(e.bullet.x, e.bullet.y)
bot.surfMover.removeBulletNear(e.bullet.x, e.bullet.y)
bot.learnedMover.removeBulletNear(e.bullet.x, e.bullet.y)
# `e.bullet` is OUR bullet; `e.hitBullet` is the bullet it struck. When that
# is an ENEMY bullet, the event exposes its real endpoint + heading - a real
# resolution for a wave that would otherwise wait for the deadline.
if e.hitBullet.ownerId != getMyId() and e.hitBullet.bulletId != 0:
discard bot.learnedMover.resolveEnemyBullet(
e.hitBullet.x, e.hitBullet.y, degToRad(e.hitBullet.direction),
e.hitBullet.ownerId, bot.tick, false)
method onHitByBullet*(bot: ModularBot, e: HitByBulletEvent) =
# Feeds the ram bullet-rain abort window. Accumulate REAL ENERGY (the server's
@@ -637,6 +653,11 @@ method onHitByBullet*(bot: ModularBot, e: HitByBulletEvent) =
# energy/turn rate (see movements/ram_decision.bulletDamage).
bot.ramDmgAccum += bulletDamage(e.bullet.power)
bot.moveTracker.registerHit(e.bullet.power, e.bullet.direction, getX(), getY())
# A real ENEMY bullet hit us: its endpoint (our impact point) + heading give
# the wave's EXACT straight line, so resolve and drop that live wave now.
discard bot.learnedMover.resolveEnemyBullet(
e.bullet.x, e.bullet.y, degToRad(e.bullet.direction),
e.bullet.ownerId, bot.tick, true)
proc enemyEnergyForReport(bot: ModularBot): float =
## Last-scanned energy of the enemy the report is about (current target,
+1 -1
View File
@@ -511,7 +511,7 @@ proc knownEnvNames*(): seq[string] =
LearnedDecayEveryEnv, LearnedDecayShiftEnv, LearnedAlphaEnv,
LearnedTravelEnv, LearnedReversalEnv, LearnedPrefDistEnv,
LearnedDistBandEnv, LearnedRadialFracEnv, LearnedWallMarginEnv,
LearnedGlobalEnv, LearnedLabelEnv, LearnedLogEnv,
LearnedGlobalEnv, LearnedLabelEnv, LearnedRealEventsEnv, LearnedLogEnv,
# harness vars (read by the test framework, inherited by the bot, so they
# must NOT be reported as typos)
"TR_SERVER_JAR", "TR_BATTLE_RUNNER", "TR_BATTLE_RUNNER_DIR",
@@ -88,6 +88,66 @@ proc counters(m: LearnedSurferModule): int =
if c != 0'u8: inc n
n
proc testRealEvents() =
## `TR_LEARNED_REAL_EVENTS`: a wave resolves on the REAL bullet event (exact
## origin->endpoint line) instead of the arrival deadline, and is dropped the
## moment it resolves (ghost cleanup). Default-off parity is also pinned.
const Ex = 400.0
const Ey = 100.0
const Ux = 400.0
const Uy = 300.0
proc wsAt(tick: int; eEnergy: float): WorldState =
WorldState(
enemyX: Ex, enemyY: Ey, enemyEnergy: eEnergy,
selfX: Ux, selfY: Uy, selfSpeed: 8.0, selfHeading: 0.0,
arenaWidth: 800.0, arenaHeight: 600.0, tick: tick,
enemies: @[EnemyInfo(id: 1, x: Ex, y: Ey,
heading: 180.0, speed: 0.0, energy: eEnergy)])
# ── parity: with the knob off a real event changes nothing ──────────────
putEnv(LearnedLabelEnv, "histogram")
putEnv(LearnedRealEventsEnv, "")
loadLearnedEnv()
var mOff = initLearnedSurfer()
discard mOff.computeMove(wsAt(0, 100.0)) # tick 0: baseline energy sample
discard mOff.computeMove(wsAt(1, 99.0)) # tick 1: a 1.0 firepower drop
check "RE default-off: the fire is detected as a live wave",
mOff.liveWaves == 1
check "RE default-off: resolveEnemyBullet is a no-op",
(not mOff.resolveEnemyBullet(Ux, Uy, degToRad(90.0), 1, 13, true)) and
mOff.liveWaves == 1
# ── ON: the exact origin->endpoint line resolves the wave immediately ────
putEnv(LearnedRealEventsEnv, "1")
loadLearnedEnv()
var mOn = initLearnedSurfer()
discard mOn.computeMove(wsAt(0, 100.0))
discard mOn.computeMove(wsAt(1, 99.0))
check "RE on: the fire is detected as a live wave", mOn.liveWaves == 1
# endpoint = our position on the centre line (GF 0 -> bin 15) at ~nominal
# (200 px at speed 17 -> ~12 ticks after the fire at tick 1)
check "RE on: the real hit endpoint resolves the wave",
mOn.resolveEnemyBullet(Ux, Uy, degToRad(90.0), 1, 13, true)
check "RE on: the wave is dropped at once (ghost cleanup)", mOn.liveWaves == 0
check "RE on: a real resolution is counted", mOn.resolvedReal == 1
check "RE on: the exact straight line lands in the centre bin",
mOn.glob[15] == 1
check "RE on: the real flight time is recorded", mOn.lastFlightErr > -20.0
# ── ON: an unmatched wave still resolves (as a WALL MISS) at the deadline ─
var mMiss = initLearnedSurfer()
discard mMiss.computeMove(wsAt(0, 100.0))
discard mMiss.computeMove(wsAt(1, 99.0))
check "RE on: a wave is pending before any event", mMiss.liveWaves == 1
for t in 2..<40: discard mMiss.computeMove(wsAt(t, 99.0))
check "RE on: the unmatched wall wave eventually resolves",
mMiss.liveWaves == 0 and mMiss.resolvedDead >= 1
putEnv(LearnedRealEventsEnv, "")
putEnv(LearnedLabelEnv, "")
loadLearnedEnv()
proc main() =
# 1. concept
check "MovementModule concept", isMovementModule(LearnedSurferModule)
@@ -170,6 +230,8 @@ proc main() =
putEnv(LearnedDecayEveryEnv, "")
loadLearnedEnv()
echo ""
testRealEvents()
echo ""
echo "checks=", checks, " failures=", failures
if failures > 0: quit(1)