Files
SirRoboGarage/common_libs/movement_harness/gunheat_tracker.nim
SirStone 651ce80620 feat(ModularBot): KNN gun, gunheat tracker, bullet shadows — inspired by DrussGT
New DrussGT-inspired modules:
- KNNGun: K-nearest-neighbor statistical targeting using GF density peaks
- GunheatTracker: dual-heat system (predicted + confirmed) for 1-2 tick lead
- ShadowTracker: computes GF regions safe from in-flight bullets (enemy wave dodge)

VirtualBodyTracker now integrates gunheat for earlier fire detection and shadows
for safe-zone multiplier (90% reduction in danger zones).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-20 11:58:57 +02:00

86 lines
3.2 KiB
Nim

## Gunheat wave prediction — mirrors DrussGT's enemyGunHeat / imaginaryGunHeat pattern.
## Tracks two heat values:
## confirmedHeat — reset from actual energy-drop fire detections
## predictedHeat — reset speculatively when enemy COULD fire (heat near 0)
## This lets us create a "predicted" wave 1-2 ticks before energy drop confirms it.
##
## Gun cooling rate in Tank Royale: 0.1/tick.
## Initial gun heat at round start: 3.0 → first possible fire at tick 30.
import std/math
import gun_harness/gun_interface
const
GunCoolingRate* = 0.1
InitialGunHeat* = 3.0
type
WaveEventKind* = enum
wePredicted ## enemy CAN fire this tick (heat just reached 0)
weConfirmed ## energy drop confirmed a fire last tick
WaveEvent* = object
kind*: WaveEventKind
fireX*, fireY*: float ## enemy position at (predicted/confirmed) fire time
bulletPower*: float
tick*: int
GunheatTracker* = object
confirmedHeat*: float ## heat from last confirmed fire (energy drop)
predictedHeat*: float ## heat from last predicted fire (imaginary wave)
prevEnemyEnergy*: float
canFireTick*: int ## tick when enemy next confirmed able to fire
proc initGunheatTracker*(): GunheatTracker =
result.confirmedHeat = InitialGunHeat
result.predictedHeat = InitialGunHeat
result.prevEnemyEnergy = 100.0
result.canFireTick = -1
proc resetRound*(gt: var GunheatTracker) =
gt.confirmedHeat = InitialGunHeat
gt.predictedHeat = InitialGunHeat
gt.prevEnemyEnergy = 100.0
gt.canFireTick = -1
proc tick*(gt: var GunheatTracker, state: WorldState): seq[WaveEvent] =
## Call once per tick with current world state.
## Returns any wave events created this tick (0, 1, or 2 entries).
# Cool both heat values
gt.confirmedHeat = max(0.0, gt.confirmedHeat - GunCoolingRate)
gt.predictedHeat = max(gt.confirmedHeat, gt.predictedHeat - GunCoolingRate)
# Check energy drop for confirmed fire (fired last tick)
let drop = gt.prevEnemyEnergy - state.enemyEnergy
gt.prevEnemyEnergy = state.enemyEnergy
if drop >= 0.1 and drop <= 3.0 and gt.confirmedHeat == 0.0:
let power = drop
# ponytail: subtract one coolingRate because they fired last tick, not this tick
gt.confirmedHeat = 1.0 + power / 5.0 - GunCoolingRate
gt.predictedHeat = gt.confirmedHeat
gt.canFireTick = state.tick
result.add WaveEvent(
kind: weConfirmed,
fireX: state.enemyX,
fireY: state.enemyY,
bulletPower: power,
tick: state.tick,
)
# Predicted wave: confirmedHeat just reached 0 AND no confirmed fire this tick
elif gt.confirmedHeat == 0.0 and gt.predictedHeat == 0.0:
# Enemy CAN fire. Guess power = 2.0 (sensible default, caller can override).
# ponytail: flat prior on power; upgrade to BulletPowerPredictor if needed
let guessedPower = 2.0
gt.predictedHeat = 1.0 + guessedPower / 5.0 + GunCoolingRate # +cooling: fire next tick
gt.canFireTick = state.tick
result.add WaveEvent(
kind: wePredicted,
fireX: state.enemyX,
fireY: state.enemyY,
bulletPower: guessedPower,
tick: state.tick,
)