68e0375be2
Replaces melee_scan in the rack. melee_scan spun the radar at the 45 deg/tick cap unconditionally, so a full 360 deg revolution took 8 ticks and every enemy was scanned roughly every 8 ticks. The new module starts with the same full spin, and once it is SURE it has covered every enemy it sweeps back and forth over only the minimal covering arc of all enemy bearings. MEASURED, real melees via the bridge, per-enemy onScannedBot counts: 3-bot melee (2 enemies): 29.6 -> 61.1 scans/100 melee-ticks (2.06x) 4-bot melee (3 enemies): 36.0 -> 75.7 scans/100 melee-ticks (2.10x) Covering-arc widths observed: mostly <90 deg in the 2-enemy case, up to 240 deg in the 3-enemy case, so the gain shrinks as the arc widens - and at the ExitTrackWidthDeg=300 fallback it degenerates to exactly the old full spin, so there is no loss when narrowing would not help. TRADEOFF, recorded rather than hidden: a wider arc legitimately takes longer to traverse, so the freshness window costs 5-7 points (fresh<=16: 93-95% vs 98-100%) and more at fresh<=8 (75-76% vs 97-100%). More scans per enemy, at slightly staler individual fixes. DESIGN: acquisition spins 360 until every live known enemy was seen within FreshnessTicks=16 (two revolutions of slack), no new id appeared, and the live count matches getEnemyCount(); that must hold FreshStreakTicks=3 consecutive ticks. Tracking then bang-bang sweeps the wraparound-aware covering arc (350+10 -> 20 through 0) widened by MarginDeg=20 each end, at up to 45 deg/tick. Fallbacks return to acquisition: any stale enemy, any new id, or an arc >= 300 deg. Enter 270 / Exit 300 gives 30 deg of hysteresis so it cannot flap. Adds EnemyInfo.lastSeenTick (additive) so coverage is judged on staleness, not mere knowledge - without it an enemy that slipped behind the sweep would keep contributing its own stale bearing, which is self-confirming. The offline range now round-trips that field from the fixture 'lst'. COMPANION FIX, and it matters: the radar-mode switch used the TRACKER's known enemy count, so in melee the bot saw one enemy before scanning the second, locked to 1v1, and the melee radar never ran at all. Now uses getEnemyCount() (server truth), so melee mode persists until one enemy is genuinely left. 41 new unit checks (wraparound arcs, straddle at 0/360, single/empty enemies, the 45 deg/tick cap, every phase transition and fallback). melee_scan is kept but marked DEPRECATED; nothing in the rack imports it. Non-regression: 33 gun-harness checks, vbullet metric, power selection, and 12/12 offline==online acceptance all pass.
62 lines
1.9 KiB
Nim
62 lines
1.9 KiB
Nim
## Gun harness — shared types and Gun concept.
|
|
## Coordinate system: 0° = East, CCW positive (Tank Royale standard).
|
|
## Bullet speed formula: 20 - 3 * power.
|
|
|
|
import std/math
|
|
|
|
const BotRadius* = 18.0 ## hit detection radius in px
|
|
|
|
type
|
|
EnemyInfo* = object
|
|
id*: int
|
|
x*, y*: float
|
|
heading*, speed*, energy*: float
|
|
lastSeenTick*: int ## tracker tick this enemy was last scanned
|
|
## (the radar freshness signal; 0 if unknown)
|
|
|
|
WorldState* = object
|
|
## All raw data given to every gun every tick.
|
|
# Enemy (current TARGET)
|
|
enemyX*, enemyY*: float
|
|
enemySpeed*, enemyHeading*: float
|
|
# Self
|
|
selfX*, selfY*: float
|
|
selfSpeed*, selfHeading*: float
|
|
selfRadarHeading*: float
|
|
selfEnergy*: float
|
|
enemyEnergy*: float
|
|
# Arena
|
|
arenaWidth*, arenaHeight*: float
|
|
# Meta
|
|
tick*: int
|
|
# All alive enemies (for movement modules)
|
|
enemies*: seq[EnemyInfo]
|
|
|
|
GunPrediction* = object
|
|
## Absolute (x, y) where the gun predicts the enemy will be.
|
|
x*, y*: float
|
|
|
|
FeedbackEvent* = object
|
|
## Outcome of a resolved virtual bullet.
|
|
prediction*: GunPrediction
|
|
actualX*, actualY*: float ## actual enemy position at resolution time
|
|
bulletPower*: float
|
|
fireTick*: int ## tick the virtual bullet was spawned (predict time)
|
|
powerBin*: int ## index into PowerBins the bullet belongs to
|
|
missDistance*: float ## px; < BotRadius = hit
|
|
hit*: bool
|
|
|
|
proc bulletSpeed*(power: float): float {.inline.} =
|
|
20.0 - 3.0 * power
|
|
|
|
## Gun concept — any type T implementing these two procs is a valid gun.
|
|
template isGun*(T: typedesc): bool =
|
|
compiles(
|
|
block:
|
|
var g: T
|
|
let ws = WorldState()
|
|
let p: GunPrediction = g.predict(ws, 0.0)
|
|
let fe = FeedbackEvent()
|
|
g.onResult(fe)
|
|
)
|