j134 fire fix: share ONE fire_tracker across tfil/ring/strafe/learned/surf (TR_FIRE_FIX, default on); env-gated TR_FIRE_DIAG alignment trace
This commit is contained in:
@@ -182,6 +182,7 @@ from std/strutils import parseFloat, parseInt, strip, toLowerAscii
|
||||
import std/strformat
|
||||
import gun_harness/gun_interface
|
||||
import movement_harness/movement_interface
|
||||
import movement_harness/fire_tracker
|
||||
import robocode_tankroyale_botapi/graphics
|
||||
import robocode_tankroyale_botapi/color
|
||||
# j105/j106 reuse: the exported time-indexed heat helpers + pillar globals.
|
||||
@@ -351,8 +352,15 @@ var
|
||||
## subtracts it.
|
||||
## MEASURED on 70 recorded battles (67065 true enemy fires): catches
|
||||
## 98.888% of enemy fires with the knob OFF and 100.000% with it ON.
|
||||
## Default ON; `TR_STRAFE_FIRE_FIX=0` restores the shipped detector exactly.
|
||||
## j134: the detector now lives ONCE in `movement_harness/fire_tracker.nim`
|
||||
## and every mover calls it. This flag is STRAFE's local gate: it is ON only
|
||||
## when BOTH `TR_STRAFE_FIRE_FIX` and the global `TR_FIRE_FIX` are on, so
|
||||
## either knob set to an off value restores the shipped detector exactly.
|
||||
StrafeFireFix*: bool = true
|
||||
## TEMPORARY j134 diagnostic (TASK B): when `TR_FIRE_DIAG` is set, print one
|
||||
## `[firediag] READ tick=…` line per enemy energy reading so the event/reading
|
||||
## tick alignment can be checked live. OFF by default; observability only.
|
||||
StrafeFireDiag*: bool = false
|
||||
## GUI: draw the full lava field (every non-zero tile, value-labelled) the
|
||||
## way TFIL does. Default ON; `TR_STRAFE_HEAT_GRID=0` hides the field so the
|
||||
## strafe overlays can be read on their own.
|
||||
@@ -386,7 +394,9 @@ proc loadStrafeEnv*() =
|
||||
StrafeWallBias = max(0.0, min(1.0, getEnvFloat("TR_STRAFE_WALL_BIAS", DefaultStrafeWallBias)))
|
||||
StrafeWallSafe = max(0.0, getEnvFloat("TR_STRAFE_WALL_SAFE", DefaultStrafeWallSafe))
|
||||
StrafeEscape = getEnvBool("TR_STRAFE_ESCAPE", true)
|
||||
StrafeFireFix = getEnvBool("TR_STRAFE_FIRE_FIX", true)
|
||||
StrafeFireFix = getEnvBool("TR_STRAFE_FIRE_FIX", true) and
|
||||
getEnvBool("TR_FIRE_FIX", true)
|
||||
StrafeFireDiag = existsEnv("TR_FIRE_DIAG")
|
||||
StrafeHeatGrid = getEnvBool("TR_STRAFE_HEAT_GRID", true)
|
||||
loadStrafeHeatEnv()
|
||||
|
||||
@@ -419,7 +429,6 @@ type
|
||||
arenaWidth*, arenaHeight*: float
|
||||
lava: seq[float]
|
||||
bullets: seq[TrackedBullet]
|
||||
prevEnergy: seq[tuple[id: int, energy: float]]
|
||||
# ── decision state ──
|
||||
targetX*, targetY*: float ## chosen tile centre (world coords)
|
||||
targetValid*: bool
|
||||
@@ -444,11 +453,8 @@ type
|
||||
wallEscapePicks*: int ## picks forced inward by the all-hot escape
|
||||
escapeModeTicks*: int ## ticks the escape bearing was in effect
|
||||
lastMode*: string ## "pick" | "fallback" | "escape" | "radial"
|
||||
# ── fire-detection fix (j133): event-fed energy-delta corrections ──
|
||||
hitBonusPending: float ## 3 * power of enemy bullets that hit us this tick
|
||||
dealtPending: float ## damage our bullets dealt to the enemy this tick
|
||||
fireFixSplitWaves*: int ## waves emitted by splitting a too-large drop
|
||||
fireFixCorrectedTicks*: int ## ticks whose drop was non-trivially corrected
|
||||
# ── fire detection (j134): the shared enemy-fire tracker ──
|
||||
fire: FireTracker ## ONE detector for every mover (movement_harness/fire_tracker)
|
||||
# ── diagnostics (gate B + GUI) ──
|
||||
callCount*: int
|
||||
picks*: int
|
||||
@@ -468,7 +474,7 @@ type
|
||||
lastTileCol, lastTileRow: int
|
||||
|
||||
proc initStrafe*(): StrafeModule =
|
||||
StrafeModule(debugGraphics: false)
|
||||
StrafeModule(debugGraphics: false, fire: initFireTracker(StrafeFireFix))
|
||||
|
||||
proc removeBulletNear*(m: var StrafeModule, x, y: float) =
|
||||
## Mark the tracked bullet closest to (x,y) within GridSize tolerance as dead.
|
||||
@@ -482,17 +488,12 @@ proc removeBulletNear*(m: var StrafeModule, x, y: float) =
|
||||
if bestIdx >= 0:
|
||||
m.bullets.del(bestIdx)
|
||||
|
||||
proc prevEnergyGet(m: StrafeModule, id: int): float =
|
||||
for e in m.prevEnergy:
|
||||
if e.id == id: return e.energy
|
||||
100.0
|
||||
proc prevEnergyGet(m: StrafeModule, id: int): float = m.fire.prevEnergyGet(id)
|
||||
|
||||
proc prevEnergySet(m: var StrafeModule, id: int, energy: float) =
|
||||
for i in 0..<m.prevEnergy.len:
|
||||
if m.prevEnergy[i].id == id:
|
||||
m.prevEnergy[i].energy = energy
|
||||
return
|
||||
m.prevEnergy.add((id: id, energy: energy))
|
||||
## Kept for the guard test / callers that seed an energy reading directly;
|
||||
## delegates to the shared tracker.
|
||||
m.fire.prevEnergySet(id, energy)
|
||||
|
||||
proc clearGraphics*(m: var StrafeModule) =
|
||||
## No-op: the SVG buffer is a module-level global cleared by the framework
|
||||
@@ -501,7 +502,7 @@ proc clearGraphics*(m: var StrafeModule) =
|
||||
|
||||
proc resetRound*(m: var StrafeModule) =
|
||||
m.bullets = @[]
|
||||
m.prevEnergy = @[]
|
||||
m.fire.reset()
|
||||
m.targetValid = false
|
||||
m.targetLava = 0.0
|
||||
m.targetPathHeat = 0.0
|
||||
@@ -521,10 +522,6 @@ proc resetRound*(m: var StrafeModule) =
|
||||
m.wallEscapePicks = 0
|
||||
m.escapeModeTicks = 0
|
||||
m.lastMode = ""
|
||||
m.hitBonusPending = 0.0
|
||||
m.dealtPending = 0.0
|
||||
m.fireFixSplitWaves = 0
|
||||
m.fireFixCorrectedTicks = 0
|
||||
m.callCount = 0
|
||||
m.picks = 0
|
||||
m.lastPickCall = 0
|
||||
@@ -594,47 +591,30 @@ proc noteEnemyBulletHit*(m: var StrafeModule, power: float) =
|
||||
## `power` the enemy spent firing in the same tick (net delta >= 0 reads as
|
||||
## "no fire"). ModularBot forwards `onHitByBullet`'s `e.bullet.power` here so
|
||||
## `detectFires` can add the bonus back before classifying the delta.
|
||||
## No-op when `TR_STRAFE_FIRE_FIX` is off (shipped detector preserved).
|
||||
if StrafeFireFix:
|
||||
m.hitBonusPending += 3.0 * power
|
||||
## No-op when the fix is off (shipped detector preserved).
|
||||
m.fire.noteEnemyBulletHit(power)
|
||||
|
||||
proc noteDamageDealt*(m: var StrafeModule, damage: float) =
|
||||
## The mirror contamination: OUR bullet damaging the enemy this tick adds
|
||||
## `damage` to the enemy's energy drop, which can push it above the 3.0 power
|
||||
## cap and get the enemy's OWN shot rejected by the shipped `<= 3.01` test.
|
||||
## ModularBot forwards `onBulletHit`'s `e.damage` here.
|
||||
## No-op when `TR_STRAFE_FIRE_FIX` is off.
|
||||
if StrafeFireFix:
|
||||
m.dealtPending += damage
|
||||
## No-op when the fix is off.
|
||||
m.fire.noteDamageDealt(damage)
|
||||
|
||||
proc detectFires(m: var StrafeModule, ws: WorldState) =
|
||||
## The shared tracker does the delta correction + split; STRAFE supplies its
|
||||
## shipped window (0.09 .. 3.01) and its own wave geometry.
|
||||
for ei in ws.enemies:
|
||||
let prev = m.prevEnergyGet(ei.id)
|
||||
let raw = prev - ei.energy
|
||||
var drop = raw
|
||||
if StrafeFireFix:
|
||||
# Undo the server's known energy contaminations. The bonus raises the
|
||||
# enemy's energy (shrinking/negating the drop); our damage lowers it
|
||||
# (inflating the drop). Both are known exactly from the events.
|
||||
drop += m.hitBonusPending - m.dealtPending
|
||||
if abs(drop - raw) > 1e-9: inc m.fireFixCorrectedTicks
|
||||
m.prevEnergySet(ei.id, ei.energy)
|
||||
if StrafeFireFix and drop > 3.01:
|
||||
# NEVER silently drop a drop. A delta above the power cap is either
|
||||
# several fires folded into one reading (unobserved radar latency) or
|
||||
# un-modelled contamination; either way SOME heat beats none. Split into
|
||||
# the fewest waves each <= 3.0, all from the same origin.
|
||||
let n = int(ceil(drop / 3.0))
|
||||
let p = drop / n.float
|
||||
for _ in 0..<n:
|
||||
m.spawnTrackedWave(ws, ei, p)
|
||||
inc m.fireFixSplitWaves
|
||||
elif drop >= 0.09 and drop <= 3.01:
|
||||
m.spawnTrackedWave(ws, ei, drop)
|
||||
if StrafeFireFix:
|
||||
# Consumed: each event-tick correction applies to exactly one reading.
|
||||
m.hitBonusPending = 0.0
|
||||
m.dealtPending = 0.0
|
||||
if StrafeFireDiag:
|
||||
let raw = m.fire.prevEnergyGet(ei.id) - ei.energy
|
||||
echo "[firediag] READ tick=", ws.tick, " id=", ei.id,
|
||||
" raw=", raw,
|
||||
" bonus=", m.fire.hitBonusPending,
|
||||
" dealt=", m.fire.dealtPending
|
||||
for p in m.fire.detect(ei.id, ei.energy, 0.09, 3.01):
|
||||
m.spawnTrackedWave(ws, ei, p)
|
||||
m.fire.endScan()
|
||||
|
||||
proc advanceBullets(m: var StrafeModule, selfX, selfY: float) =
|
||||
var i = 0
|
||||
@@ -1099,9 +1079,9 @@ proc computeMove*(m: var StrafeModule, ws: WorldState): MoveCommand =
|
||||
m.dwell = 0
|
||||
m.targetValid = false
|
||||
m.bullets = @[]
|
||||
m.prevEnergy = @[]
|
||||
m.fire.prevEnergy = @[]
|
||||
for ei in ws.enemies:
|
||||
m.prevEnergySet(ei.id, ei.energy)
|
||||
m.fire.prevEnergySet(ei.id, ei.energy)
|
||||
|
||||
m.advanceBullets(ws.selfX, ws.selfY)
|
||||
m.detectFires(ws)
|
||||
|
||||
Reference in New Issue
Block a user