2cc2a3bd87
- 30-tick cooldown after ghost-stuck/timeout ram exit prevents re-entry loop - enemy_tracker.update() skips dead bots to prevent same-tick scan resurrection - TFIL graphics cleared when ramming is active movement - [config] logs: white base with green-highlighted changes only - [ram:enter] logs trigger reason and key values on false→true transition - [death] and [target-invalid] logs retained for diagnostics
58 lines
2.3 KiB
Nim
58 lines
2.3 KiB
Nim
## random_oscillator.nim — perpendicular strafing with randomized reversal timing.
|
|
## Harder to predict than fixed-period oscillation; same wall-lockout guard as oscillator.nim.
|
|
|
|
import std/math
|
|
import std/random
|
|
import std/times
|
|
import gun_harness/gun_interface
|
|
import movement_harness/movement_interface
|
|
|
|
const
|
|
MaxSpeed* = 8.0 ## Tank Royale max speed
|
|
MinPeriod* = 15 ## shortest reversal interval (ticks)
|
|
MaxPeriod* = 45 ## longest reversal interval (ticks)
|
|
WallMargin* = 80.0 ## px from wall to trigger forced reversal
|
|
WallLockout* = 20 ## ticks to suppress further wall-reversals after one fires
|
|
|
|
type RandomOscillatorModule* = object
|
|
sign: float ## +1 or -1
|
|
elapsed: int ## ticks since last reversal
|
|
nextPeriod: int ## random period chosen at last reversal
|
|
wallLockout: int ## remaining suppression ticks
|
|
rng: Rand
|
|
debugGraphics*: bool
|
|
|
|
proc initRandomOscillator*(): RandomOscillatorModule =
|
|
var rng = initRand(getTime().toUnix())
|
|
let period = rng.rand(MinPeriod..MaxPeriod)
|
|
RandomOscillatorModule(sign: 1.0, elapsed: 0, nextPeriod: period,
|
|
wallLockout: 0, rng: rng, debugGraphics: false)
|
|
|
|
proc computeMove*(m: var RandomOscillatorModule, ws: WorldState): MoveCommand =
|
|
inc m.elapsed
|
|
if m.wallLockout > 0: dec m.wallLockout
|
|
|
|
let nearWall = ws.selfX < WallMargin or ws.selfX > ws.arenaWidth - WallMargin or
|
|
ws.selfY < WallMargin or ws.selfY > ws.arenaHeight - WallMargin
|
|
|
|
if m.elapsed >= m.nextPeriod or (nearWall and m.wallLockout == 0):
|
|
m.sign *= -1.0
|
|
m.elapsed = 0
|
|
m.nextPeriod = m.rng.rand(MinPeriod..MaxPeriod)
|
|
if nearWall: m.wallLockout = WallLockout
|
|
|
|
# Perpendicular heading to enemy bearing (same dot-product trick as phantom_meteor)
|
|
let enemyBearingRad = arctan2(ws.enemyY - ws.selfY, ws.enemyX - ws.selfX)
|
|
let perpHeadingDeg = radToDeg(enemyBearingRad) + 90.0
|
|
|
|
var delta = perpHeadingDeg - ws.selfHeading
|
|
while delta > 180.0: delta -= 360.0
|
|
while delta < -180.0: delta += 360.0
|
|
|
|
# Dot-product trick: if going backward, flip turn to stay perpendicular
|
|
var normDelta = if m.sign < 0: delta - 180.0 else: delta
|
|
while normDelta > 180.0: normDelta -= 360.0
|
|
while normDelta < -180.0: normDelta += 360.0
|
|
|
|
(speed: m.sign * MaxSpeed, turnRate: normDelta.clamp(-10.0, 10.0))
|