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
+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),