651ce80620
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>
86 lines
3.2 KiB
Nim
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,
|
|
)
|