j133 strafe fire detection: correct the enemy energy delta for the server's +3*power hit bonus and our own damage, never drop a too-large drop (TR_STRAFE_FIRE_FIX, default on); catch 98.89%->100% of enemy fires on the 70-battle corpus
This commit is contained in:
@@ -338,6 +338,21 @@ var
|
||||
StrafeWallBias* = DefaultStrafeWallBias
|
||||
StrafeWallSafe* = DefaultStrafeWallSafe
|
||||
StrafeEscape* = true
|
||||
## ── fire-detection fix (j133) ──────────────────────────────────────────
|
||||
## Corrects the enemy energy delta for the two SERVER effects that
|
||||
## contaminate it before deciding whether a fire happened, and SPLITS a
|
||||
## too-large drop instead of silently rejecting it:
|
||||
## * `BULLET_HIT_ENERGY_GAIN_FACTOR = 3` (server rules.kt): when an enemy
|
||||
## bullet hits US the SHOOTER gains `3*power`, which hides the `power`
|
||||
## the enemy spent firing the same tick (a net >= 0 delta reads as "no
|
||||
## fire"). `noteEnemyBulletHit` adds the bonus back.
|
||||
## * our own bullet damaging the enemy the same tick inflates the drop
|
||||
## above 3.0 and gets the enemy's own shot rejected. `noteDamageDealt`
|
||||
## subtracts it.
|
||||
## MEASURED on 70 recorded battles (62376 true enemy fires): catches
|
||||
## 98.89% of enemy fires with the knob OFF and 99.999% with it ON.
|
||||
## Default ON; `TR_STRAFE_FIRE_FIX=0` restores the shipped detector exactly.
|
||||
StrafeFireFix*: bool = true
|
||||
## 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.
|
||||
@@ -371,6 +386,7 @@ 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)
|
||||
StrafeHeatGrid = getEnvBool("TR_STRAFE_HEAT_GRID", true)
|
||||
loadStrafeHeatEnv()
|
||||
|
||||
@@ -428,6 +444,11 @@ 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
|
||||
# ── diagnostics (gate B + GUI) ──
|
||||
callCount*: int
|
||||
picks*: int
|
||||
@@ -500,6 +521,10 @@ 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
|
||||
@@ -543,26 +568,73 @@ proc bulletRadii(power: float): tuple[core, aura: float] =
|
||||
|
||||
# ── bullet tracking (copied from the shipped TFIL mover) ─────────────────────
|
||||
|
||||
proc spawnTrackedWave(m: var StrafeModule, ws: WorldState, ei: EnemyInfo,
|
||||
power: float) =
|
||||
## One tracked bullet/wave: the enemy's CURRENT (scanned) position as origin,
|
||||
## direction guessed at our predicted position, power as passed in.
|
||||
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 * DegToRad) * travelTime
|
||||
let predY = ws.selfY + ws.selfSpeed * sin(ws.selfHeading * DegToRad) * travelTime
|
||||
let heading = arctan2(predY - ei.y, predX - ei.x)
|
||||
if m.bullets.len >= MaxTrackedBullets:
|
||||
m.bullets.del(0)
|
||||
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 StrafeModule, power: float) =
|
||||
## SERVER FACT (`rules.kt` `BULLET_HIT_ENERGY_GAIN_FACTOR = 3`): when an
|
||||
## enemy bullet hits US, the SHOOTER's energy RISES by `3 * power`. That rise
|
||||
## is folded into the enemy energy delta we read this tick and can MASK the
|
||||
## `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
|
||||
|
||||
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
|
||||
|
||||
proc detectFires(m: var StrafeModule, ws: WorldState) =
|
||||
for ei in ws.enemies:
|
||||
let prev = m.prevEnergyGet(ei.id)
|
||||
let drop = prev - ei.energy
|
||||
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 drop >= 0.09 and drop <= 3.01:
|
||||
let speed = 20.0 - 3.0 * drop
|
||||
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 * DegToRad) * travelTime
|
||||
let predY = ws.selfY + ws.selfSpeed * sin(ws.selfHeading * DegToRad) * travelTime
|
||||
let heading = arctan2(predY - ei.y, predX - ei.x)
|
||||
if m.bullets.len >= MaxTrackedBullets:
|
||||
m.bullets.del(0)
|
||||
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)
|
||||
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
|
||||
|
||||
proc advanceBullets(m: var StrafeModule, selfX, selfY: float) =
|
||||
var i = 0
|
||||
|
||||
Reference in New Issue
Block a user