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:
2026-09-26 12:40:19 +02:00
parent bd57db6d63
commit 6ad5d99922
11 changed files with 435 additions and 214 deletions
+47 -36
View File
@@ -6,6 +6,7 @@ import std/os
import std/strutils except fromHex # `fromHex` would clash with color.fromHex
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
@@ -104,6 +105,9 @@ var
TfilCommitTicks*: int = DefaultTfilCommitTicks
TfilNoRev*: bool = false
TfilCommitLogPath*: string = ""
## j134: the shared fire-detection correction (`TR_FIRE_FIX`, default on).
## Off = the shipped `prev - energy` detector byte-for-byte.
TfilFireFix*: bool = true
proc getEnvInt(name: string, default: int): int =
let s = getEnv(name, "")
@@ -132,6 +136,7 @@ proc loadTfilCommitEnv*() =
TfilCommitTicks = max(1, getEnvInt("TR_TFIL_COMMIT_TICKS", DefaultTfilCommitTicks))
TfilNoRev = getEnvBool("TR_TFIL_NO_REV", false)
TfilCommitLogPath = getEnv("TR_TFIL_COMMIT_LOG", "")
TfilFireFix = getEnvBool("TR_FIRE_FIX", true)
loadTfilCommitEnv()
@@ -243,7 +248,7 @@ type
arenaWidth, arenaHeight: float
lava: seq[float] # flat row-major, index = row*cols + col
bullets: seq[TrackedBullet]
prevEnergy: seq[tuple[id: int, energy: float]] # enemy id -> last known energy
fire: FireTracker ## shared energy-drop detector (j134)
commitTarget: tuple[x, y: float] ## world coords of committed dodge point
commitTicks: int ## ticks remaining on commitment
commitLava: float ## lava at commit time (for spike detection)
@@ -258,7 +263,7 @@ type
lastPickCall: int ## callCount at the last pick (log only)
picks: int ## number of picks this round (log only)
proc initTFIL*(): TFILModule = TFILModule(debugGraphics: false)
proc initTFIL*(): TFILModule = TFILModule(debugGraphics: false, fire: initFireTracker(TfilFireFix))
proc removeBulletNear*(m: var TFILModule, x, y: float) =
## Mark the tracked bullet closest to (x,y) within GridSize tolerance as dead.
@@ -272,17 +277,10 @@ proc removeBulletNear*(m: var TFILModule, x, y: float) =
if bestIdx >= 0:
m.bullets.del(bestIdx)
proc prevEnergyGet(m: TFILModule, id: int): float =
for e in m.prevEnergy:
if e.id == id: return e.energy
100.0
proc prevEnergyGet(m: TFILModule, id: int): float = m.fire.prevEnergyGet(id)
proc prevEnergySet(m: var TFILModule, 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))
m.fire.prevEnergySet(id, energy)
proc clearGraphics*(m: var TFILModule) =
## No-op: the SVG buffer is a module-level global cleared by the framework
@@ -291,7 +289,7 @@ proc clearGraphics*(m: var TFILModule) =
proc resetRound*(m: var TFILModule) =
m.bullets = @[]
m.prevEnergy = @[]
m.fire.reset()
m.commitTicks = 0
m.cachedHull = @[]
m.cachedInsideTiles = @[]
@@ -349,30 +347,43 @@ proc initGrid(m: var TFILModule, arenaWidth, arenaHeight: float) =
m.arenaHeight = arenaHeight
m.lava = newSeq[float](m.cols * m.rows) # all 0.0
proc spawnTrackedWave(m: var TFILModule, ws: WorldState, ei: EnemyInfo,
power: float) =
## One tracked bullet/wave for a confirmed fire of `power`: linear prediction
## of our position at arrival becomes the aim heading. (Was inlined in
## `detectFires`; extracted so the shared split can spawn several.)
let speed = 20.0 - 3.0 * power
let dist = sqrt((ws.selfX - ei.x)^2 + (ws.selfY - ei.y)^2)
let travelTime = dist / speed
let predX = ws.selfX + ws.selfSpeed * cos(ws.selfHeading * PI / 180.0) * travelTime
let predY = ws.selfY + ws.selfSpeed * sin(ws.selfHeading * PI / 180.0) * travelTime
let heading = arctan2(predY - ei.y, predX - ei.x)
if m.bullets.len >= MaxTrackedBullets:
m.bullets.del(0) # ponytail: drop oldest; fine for 20-bullet cap
m.bullets.add TrackedBullet(
originX: ei.x, originY: ei.y,
x: ei.x, y: ei.y,
velX: speed * cos(heading),
velY: speed * sin(heading),
power: power,
alive: true,
age: 0)
proc noteEnemyBulletHit*(m: var TFILModule, power: float) =
## `onHitByBullet` -> the shooter's `3*power` bonus (no-op when off).
m.fire.noteEnemyBulletHit(power)
proc noteDamageDealt*(m: var TFILModule, damage: float) =
## `onBulletHit` -> our same-tick damage to the enemy (no-op when off).
m.fire.noteDamageDealt(damage)
proc detectFires(m: var TFILModule, ws: WorldState) =
## Check all enemies for energy drops; spawn a tracked bullet per confirmed fire.
## Check all enemies for energy drops; spawn a tracked bullet per confirmed
## fire. The shared tracker corrects the delta and splits over-cap drops.
for ei in ws.enemies:
let prev = m.prevEnergyGet(ei.id)
let drop = prev - ei.energy
m.prevEnergySet(ei.id, ei.energy)
if drop >= 0.09 and drop <= 3.01:
let speed = 20.0 - 3.0 * drop
# Linear prediction: aim at where we will be when the bullet arrives
let dist = sqrt((ws.selfX - ei.x)^2 + (ws.selfY - ei.y)^2)
let travelTime = dist / speed
let predX = ws.selfX + ws.selfSpeed * cos(ws.selfHeading * PI / 180.0) * travelTime
let predY = ws.selfY + ws.selfSpeed * sin(ws.selfHeading * PI / 180.0) * travelTime
let heading = arctan2(predY - ei.y, predX - ei.x)
if m.bullets.len >= MaxTrackedBullets:
m.bullets.del(0) # ponytail: drop oldest; fine for 20-bullet cap
m.bullets.add TrackedBullet(
originX: ei.x, originY: ei.y,
x: ei.x, y: ei.y,
velX: speed * cos(heading),
velY: speed * sin(heading),
power: drop,
alive: true,
age: 0)
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 TFILModule, selfX, selfY: float) =
## Advance positions and reap bullets that are: passed us, out of bounds, or too old.
@@ -519,9 +530,9 @@ proc computeMove*(m: var TFILModule, ws: WorldState): MoveCommand =
m.bullets = @[] # bullet positions are hopelessly stale
m.blockedTile = (col: 0, row: 0, active: false)
# Re-snapshot prevEnergy so energy changes during ramming aren't misread as fires
m.prevEnergy = @[]
m.fire.prevEnergy = @[]
for ei in ws.enemies:
m.prevEnergySet(ei.id, ei.energy)
m.fire.prevEnergySet(ei.id, ei.energy)
# Tile-change replan — see the knob rationale at the top of the file.
if (not jumped) and (m.callCount > 0) and (m.commitTicks > 0):