fb36a0a685
The belief "BotDeathEvent never reaches ModularBot, so enemyTracker keeps dead
enemies alive forever" was written into a code comment and then believed twice.
It is FALSE. Measured in a 7-bot melee with a per-tick probe comparing
enemyTracker's alive count against the server's getEnemyCount():
metric 1.3.1 (20 rd) 0.35.5 (15 rd)
observed enemy deaths 83 68
...non-round-ending 83 (100%) 66 (97%)
ekBotDeath events DROPPED 0 0
max dispatch lag (turns behind) 1 1
phantom ticks 1 / 16,820 1 / 12,596
MAX CORPSE LIFETIME 0 ticks 0 ticks
victims still alive at round end 0 0
onBotDeath fires for every death, including non-round-ending ones. The
API-level event-drop mechanism IS real (test_event_drop_mechanism.nim proves
it: ekBotDeath is not in isCritical and MAX_EVENTS_AGE=2) - the bot simply
never falls far enough behind for it to trigger (max lag 1 turn).
Removed:
- reconcileWithServer + ReconcilePersistTicks/mismatchTicks/sawServerAlive
(uncommitted, and ON BY DEFAULT despite the premise being false). Its own
comment admitted a shorter window once KILLED A LIVE ENEMY ("it fired three
more times after the tracker marked it dead") - a latent mis-prune path
defending against a bug that does not exist.
- The radar's CorpseTicks=40 filter and the same-class age>60 filter in
recordRadarStats, both carrying the false comment. Removal changes no real
behaviour: buildState feeds the radar enemyTracker.allAlive(), so a dead
enemy never reaches computeScan.
Kept:
- The TR_TRACKER_PROBE instrument (default OFF), which produced the table above.
- test_event_drop_mechanism.nim - the drop mechanism is a genuine library
behaviour worth guarding.
- isAlive/aliveCount on the tracker.
Added: docs/tracker_death_events.md (the durable negative, so this is not
re-invented a third time) and test_enemy_tracker_death.nim (13 checks) in place
of the test for the deleted feature.
Guards: test_gun_harness 39/39, test_vbullet_metric 11, test_power_selection 3,
test_adaptive_radar 41/41, test_event_drop_mechanism 6, test_enemy_tracker_death
13, acceptance 12/12, ModularBot compiles.
78 lines
2.8 KiB
Nim
78 lines
2.8 KiB
Nim
## Direct proof of the BotDeath drop mechanism in `robocode_tankroyale_botapi`
|
|
## 1.0.7, at the API level (no battle required).
|
|
##
|
|
## `event_queue.isCritical` contains only `ekDeath`, `ekWonRound` and
|
|
## `ekSkippedTurn` — NOT `ekBotDeath`. `removeOldEvents` deletes every
|
|
## non-critical event whose `turnNumber < currentTurn - MAX_EVENTS_AGE`.
|
|
## Therefore a `BotDeathEvent` for ANOTHER bot is silently dropped whenever the
|
|
## bot falls more than two turns behind, which is why `onBotDeath` cannot be the
|
|
## tracker's only death signal.
|
|
##
|
|
## Run: nim c -r common_libs/tests/test_event_drop_mechanism.nim
|
|
|
|
import robocode_tankroyale_botapi
|
|
|
|
var failures = 0
|
|
proc check(name: string, ok: bool) =
|
|
if ok: echo "PASS: ", name
|
|
else: echo "FAIL: ", name; inc failures
|
|
|
|
const CurrentTurn = 8
|
|
|
|
proc botDeathAt(turn: int): BotEvent =
|
|
BotEvent(kind: ekBotDeath, turnNumber: turn,
|
|
botDeath: BotDeathEvent(turnNumber: turn, victimId: 2))
|
|
|
|
proc deathAt(turn: int): BotEvent =
|
|
BotEvent(kind: ekDeath, turnNumber: turn,
|
|
death: BotDeathEvent(turnNumber: turn, victimId: 1))
|
|
|
|
proc testBotDeathIsNotCritical() =
|
|
let e = botDeathAt(5)
|
|
check "ekBotDeath is not critical", not e.isCritical
|
|
check "ekDeath IS critical", deathAt(5).isCritical
|
|
|
|
proc testBotDeathDroppedWhenOlderThanMaxAge() =
|
|
var eq = initEventQueue()
|
|
eq.addEvent(botDeathAt(CurrentTurn - MAX_EVENTS_AGE - 1)) # age 3
|
|
eq.removeOldEvents(CurrentTurn)
|
|
check "ekBotDeath older than MAX_EVENTS_AGE is dropped",
|
|
eq.events.len == 0
|
|
|
|
proc testBotDeathKeptAtMaxAge() =
|
|
var eq = initEventQueue()
|
|
eq.addEvent(botDeathAt(CurrentTurn - MAX_EVENTS_AGE)) # age 2
|
|
eq.removeOldEvents(CurrentTurn)
|
|
check "ekBotDeath at exactly MAX_EVENTS_AGE age is kept",
|
|
eq.events.len == 1
|
|
|
|
proc testCriticalDeathSurvives() =
|
|
var eq = initEventQueue()
|
|
eq.addEvent(deathAt(CurrentTurn - MAX_EVENTS_AGE - 5))
|
|
eq.removeOldEvents(CurrentTurn)
|
|
check "critical ekDeath survives any age",
|
|
eq.events.len == 1
|
|
|
|
proc testScannedBotAlsoDropped() =
|
|
# Every other non-critical event is dropped too, so a lagging bot also loses
|
|
# scans/hits; BotDeath is the one with a permanent consequence.
|
|
var eq = initEventQueue()
|
|
eq.addEvent(BotEvent(kind: ekScannedBot, turnNumber: CurrentTurn - 3,
|
|
scannedBot: ScannedBotEvent(`type`: "ScannedBotEvent",
|
|
turnNumber: CurrentTurn - 3, scannedByBotId: 0, scannedBotId: 2,
|
|
energy: 100.0, x: 0.0, y: 0.0, direction: 0.0, speed: 0.0)))
|
|
eq.removeOldEvents(CurrentTurn)
|
|
check "ekScannedBot older than MAX_EVENTS_AGE is dropped too",
|
|
eq.events.len == 0
|
|
|
|
testBotDeathIsNotCritical()
|
|
testBotDeathDroppedWhenOlderThanMaxAge()
|
|
testBotDeathKeptAtMaxAge()
|
|
testCriticalDeathSurvives()
|
|
testScannedBotAlsoDropped()
|
|
|
|
if failures > 0:
|
|
echo "\n", failures, " check(s) FAILED"
|
|
quit(1)
|
|
echo "\nAll event-drop mechanism checks passed."
|