j160 (default-off): the energy-reserve FIRING FLOOR + ENEMY-EXHAUSTION ram trigger

TR_RAM_FLOOR_ENERGY (0.0 = off): at/below this self energy we start no NEW
shot, holding back the reserve for a final ram exchange. Justified by the only
energy gain in the game being +3*power per bullet hit LANDED, so not firing
denies the enemy its only refill. Blocks only NEW shots (gunHeat already gates
committed ones) and is bypassed while ramming.

TR_RAM_ENEMY_ENERGY (0.0 = off): last-scanned enemy energy <= this -> ram
mode. Enemy energy IS observable (ScannedBotEvent.energy, schemas.nim:306),
1-8 ticks stale. This is the shipped finisher with its energy tolerance
promoted to a knob, keeping the self>enemy surplus guard because RAM_DAMAGE
0.6 applies to BOTH bots on every contact tick.

Open-loop measurement (measure_ramfloor_energy, 8149 recordings / 29871
rounds / 33.8M ticks): 'both low' is COMMON (10.4% of ticks below 20, 15.1%
below 25) but neither side goes low first (enemy 52.7% / us 47.3%), and the
owner's literal trigger - enemy so low it cannot fire (energy <= 1.95) - is
only 2.5% of ticks, 1.1% while we are healthy.

Guards 136 -> 147 in test_tfil_commit_env.nim, all green. A/B PRE-REGISTERED
in docs/ram_floor_exhaustion_ab.md and NOT RUN.
This commit is contained in:
2026-09-27 12:46:46 +02:00
parent 7c5bc9ccbd
commit 23bce2dad5
8 changed files with 455 additions and 3 deletions
+2
View File
@@ -98,6 +98,8 @@ TR_RAM_PLAN=off # the change-of-plan trigger (enemy outguns us while
TR_RAM_PLAN_DIST=250.0 # px; max range at which the plan trigger may fire
TR_RAM_PLAN_MARGIN=20.0 # energy advantage the plan trigger needs
TR_RAM_PLAN_HITRATE=0.05 # pooled virtual hit rate below which the gun duel counts as failing
TR_RAM_FLOOR_ENERGY=0.0 # j160 firing floor: at/below this self energy stop firing (0 = off)
TR_RAM_ENEMY_ENERGY=0.0 # j160 exhaustion: last-scanned enemy energy <= this -> ram (0 = off)
# ── movement internals: tfil (the floor-is-lava field) ──────────────────────
TR_TFIL_RANGE_LO=100.0 # px; lower edge of the range band the ring mover prefers
+6 -1
View File
@@ -1459,9 +1459,14 @@ method run*(bot: ModularBot) =
let distPx = hypot(pred.x - getX(), pred.y - getY())
if shouldFire(gunDir, aimTarget, gunHeat, distPx):
# j160 FIRING FLOOR: at/below TR_RAM_FLOOR_ENERGY self energy we hold
# the reserve for the ram instead of spending it on a shot. Off by
# default (`RamFloorEnergy = 0.0`), and `ramming` (the exhaustion
# trigger) wins the conflict, so an engaged ram never starves itself.
let floorBlocks = fireFloorBlocks(RamFloorEnergy, getEnergy(), shouldRam)
# Enqueue the selected gun so onBulletFired can stamp the server's bulletId.
# getEnergy() > power mirrors the server's "bot.energy <= firepower" reject.
if setFire(power) and getEnergy() > power:
if not floorBlocks and setFire(power) and getEnergy() > power:
bot.pendingFires.add(PendingShot(
gunId: selectedGun,
angleErr: abs(normDelta),
+3
View File
@@ -410,6 +410,8 @@ proc printEffectiveValues(ctx: EnvReportContext) =
emit("TR_RAM_PLAN_MARGIN", $RamPlanMargin, sourceOf("TR_RAM_PLAN_MARGIN"))
emit("TR_RAM_PLAN_HITRATE", $RamPlanHitRate, sourceOf("TR_RAM_PLAN_HITRATE"))
emit("TR_RAM_LOG", onOff(RamLog), sourceOfPresence("TR_RAM_LOG"))
emit("TR_RAM_FLOOR_ENERGY", $RamFloorEnergy, sourceOf("TR_RAM_FLOOR_ENERGY"))
emit("TR_RAM_ENEMY_ENERGY", $RamEnemyEnergy, sourceOf("TR_RAM_ENEMY_ENERGY"))
# ── the horizon TM gun ────────────────────────────────────────────────────
# `resetLearning`/`targetChanged` resolve the lazily-read fields at round
@@ -661,6 +663,7 @@ proc knownEnvNames*(): seq[string] =
"TR_RAM_OPPORTUNITY", "TR_RAM_OPP_DIST", "TR_RAM_OPP_MARGIN",
"TR_RAM_ABORT_DMG", "TR_RAM_PLAN", "TR_RAM_PLAN_DIST",
"TR_RAM_PLAN_MARGIN", "TR_RAM_PLAN_HITRATE", "TR_RAM_LOG",
"TR_RAM_FLOOR_ENERGY", "TR_RAM_ENEMY_ENERGY",
"TR_TFIL_RANGE_LO", "TR_TFIL_RANGE_HI", "TR_TFIL_RANGE_TEMP",
"TR_TFIL_RANGE_K", "TR_TFIL_CORRIDOR_HEAT", "TR_TFIL_WALL_HOTNESS",
"TR_TFIL_CORRIDOR_TICKS", "TR_TFIL_ARRIVE_TICKS",