All prior measurements ran on server 0.35.5. The default is now the current
1.3.1 jar, with the legacy jar kept and switchable via TR_SERVER_JAR (no code
edit). test_gauntlet_5bots.nim no longer clobbers a caller's TR_SERVER_JAR -
it used to putEnv() unconditionally, so an override was silently ignored.
RE-BASELINE (controlled RulesProbe battle, stationary bot, powers 0.1/0.5/1/2/3):
dimension 1.3.1 0.35.5 verdict
bullet damage per hit 0.4/2/4/10/16 identical SAME
bullet speed (20-3p) within noise within noise SAME
post-fire gun heat (1+p/5) identical identical SAME
cooling 0.1/tick 0.1/tick SAME
bulletDamage SCORE exactly 100/round 104..113/round DIFFERENT
bulletKillBonus (20%) 20/round 20..23/round DIFFERENT
LOUD FINDING - a SCORING rule changed, physics did not: 0.35.5 credits
OVERKILL to bulletDamage (the killing bullet's full damage even past 0 energy);
1.3.1 caps it at the energy actually removed. Every 0.35.5 score is therefore
inflated ~5-6%, and bulletKillBonus inherits the inflation. Gauntlet totals
shift accordingly (SittingDuck 1936 -> 1800, WaveSurfer 1886 -> 1669).
Consequence: score-based numbers recorded on 0.35.5 are NOT comparable to 1.3.1.
Our gun A/Bs used real HIT RATE, not score, so those conclusions stand.
Runner 1.0.2 (unchanged, no newer one on the box) is measured compatible with
the 1.3.1 server. Note TrBattleCapture uses the runner's EMBEDDED server, which
is 1.0.2 - so the capture path still runs an older engine than the gauntlet.
Also re-ran acceptance_offline_vs_online on the new default: 12/12.
Four of the five adversaries imported the OLD package (tankroyale_botapi
1.0.1); only SittingDuck used robocode_tankroyale_botapi 1.0.7, which is what
the rest of the repo requires. A previous report claimed OscillatorBot was
already on 1.0.7 - that was WRONG, and OscillatorBot turned out to crash the
MOST (8 SIGSEGVs in the first reproduction, 15 in its historical /tmp logs).
THE CRASH, reproduced with an identical stack in every case:
botThreadEntry -> run -> adversary run -> go -> dispatchPendingEvents ->
tankroyale_botapi-1.0.1/event_queue.nim(89) addEvent -> realloc/rawDealloc ->
SIGSEGV
Counts, old API: 60 melee battles x 8 rounds gave RandomMover 1, PatternMover 3,
WaveSurfer 0, OscillatorBot 8; 6 battles x 6 rounds vs SittingDuck gave 4/2/0/3.
ROOT CAUSE: the main->bot event hand-off. 1.0.1 passes a lock-protected
seq[BotEvent] (signalTick writes gPendingEvents, dispatchPendingEvents copies it
under lock). 1.0.7 uses a Channel[seq[BotEvent]] (send(move(pending)) /
tryRecv). The old path copied string-bearing BotEvent payloads across threads
every tick, churning ORC refcounts on the shared heap until the freelist was
corrupted. 1.0.7's own source documents this as the gdb-confirmed fix.
WHY IT MATTERED MORE THAN IT LOOKED: the crash silently corrupted measurements.
Against a stationary duck, crash contamination inflated WaveSurfer's rest
fraction from 12.4% (clean) to 20.7%; in a focused run the server logged
'Bot left: OscillatorBot' while the game continued and its score stopped
growing. So every gauntlet run tonight was fighting adversaries that were
partially dead - which is a second, independent reason the user's instinct that
these bots were bugged was correct, and why they should not be used as a
measurement baseline. (The per-gun REAL hit rates are unaffected: those came
from DrussGT battles.)
FIX: all four migrated to robocode_tankroyale_botapi 1.0.7. NO API adaptations
were needed beyond the module rename - every symbol these bots use is identical
in 1.0.7, verified by diffing the two packages (constants/utils/json_parse/
schemas semantically identical; the movement and intent procs in bot.nim are
byte-identical). The .nimble files now require robocode_tankroyale_botapi.
VERIFIED: 120 melee battles x 8 rounds plus 6x6 vs SittingDuck -> 0 SIGSEGV in
all four stderr logs (0 bytes). Behaviour unchanged: sub-1% absolute drift in
mean speed, rest fraction, reversal rate, mean range and perpendicular fraction,
all within run-to-run spread; the one >=3-sigma flag (WaveSurfer perpendicular
relative to DrussGT) was isolated against a stationary opponent and shown to be
the chaotic closed loop, not the migration. test_wavesurfer_velocity passes 7/7.
NOT migrated, reported only: GotoTest_garage, OscillatorBot_garage (archived
copy), PPO_Bot_garage, QBot_garage, SAC_LSTM_Bot_garage - older experiment
garages, left alone deliberately.
P0. Three of the five launchers ran ./<Bot> (a tracked binary at the bot root)
while config.nims sets outdir=out and both the test framework's compileBots and
a manual 'nim c src/<Bot>.nim' write to out/. SittingDuck and OscillatorBot
correctly ran ./out/<Bot>; RandomMover, PatternMover and WaveSurfer did not.
cmp confirms the root and out binaries differed for all three.
Consequence: the previous session's adversary fixes were compiled into out/ and
never executed. Every gauntlet and every capture ran the OLD code. This is
almost certainly why the user's instinct that these bots were still bugged was
correct while the code claimed otherwise.
Fixed by pointing all five launchers at ./out/<Bot>, and by deleting the three
stale root binaries so the trap cannot recur. Verified end to end through the
booter: WaveSurfer went from standing still 96.2% of ticks with a 1398-tick
longest standstill, to rest 12.3% / mean speed 6.69 / longest zero run 18 /
perpendicular 0.845.
Also honours GUN_STATS_PATH in test_gauntlet_5bots.nim (same knob ModularBot
reads) so pooled gauntlet runs append to one file instead of clobbering the
default.
NOTE for a follow-up: the out/ binaries are still TRACKED build artifacts, which
is the same class of hazard that caused this. Untracking them (as was done for
ModularBot_garage/ModularBot) would remove the failure mode entirely.
The user suspected these were bugged. They were, and the verdicts are not
uniform - three genuinely broken, one merely sloppy, one fine:
- WaveSurfer: GENUINELY BUGGED, worst of the five. (a) The enemy velocity
decomposition was sin/cos SWAPPED - enemyVx used sin and enemyVy used cos,
while Tank Royale is 0 deg = East, CCW+, so it must be cos for X and sin for
Y. Its linear-prediction gun was aiming at a reflected position. (b) The wall
escape flipped strafeDir on EVERY tick the bot was inside the wall margin,
so instead of turning away it flip-flopped in place: measured standing still
(speed < 0.5) for 96.2% of ticks with a longest continuous standstill of 1398
ticks. Fixed with a hysteretic wall-escape selection plus a corner escape,
dead enemyLastDir removed, and per-round state reset.
AFTER, measured through the booter: rest 12.3%, mean speed 6.69, full speed
79.7%, longest zero run 18, perpendicular 0.845 / radial 0.012 - it now
actually strafes. Gun sanity: lead error 1.0 px vs 106 px for head-on on a
constant-velocity target; lead gun 45.8% hits vs 29.3% for head-on.
- PatternMover: GENUINELY BUGGED. Real deadlock - it decremented its step
counter by the REQUESTED amount while issuing setTargetSpeed(8), so against a
wall the counter never reached 0, advanceStep never ran and it was stuck
forever (309-tick standstill). Now counts down by ACTUAL distance/turn with a
STALL_LIMIT watchdog and steers toward the arena centre. Standstill 309 -> 19
ticks; full-speed ticks 10.0% -> 28.4%.
- OscillatorBot: GENUINELY BUGGED, milder. No wall handling at all, so it
ground along walls 53.4% of ticks and could pin in a corner. Added wall
steering that preserves the fixed 25-tick reversal cadence. Wall-band 53.4%
-> 18.6%, mean wall distance 72 -> 119.
- RandomMover: merely sloppy, not broken. Its turn intent saturated against the
speed-dependent limit (18.4% of moving ticks clamped) and the fire gate was a
very loose 10 deg. Now clamps to calcMaxTurnRate and fires within 3 deg.
Saturation 18.4% -> 3.9%.
- SittingDuck: FINE. Speed 0 for 100% of ticks, zero shots. Left untouched -
it is a duck by design.
Adds test_wavesurfer_velocity.nim, a direct assertion that the decomposition is
cos/sin and explicitly NOT the swapped form (7 cases).
KNOWN ISSUE, not fixed: RandomMover/PatternMover/WaveSurfer import
tankroyale_botapi 1.0.1 and intermittently SIGSEGV in
tankroyale_botapi/event_queue.nim:89 addEvent, freezing the bot for the rest of
the battle. It reproduces on old and new code and never occurs for SittingDuck/
OscillatorBot, which import robocode_tankroyale_botapi 1.0.7. Migrating the
three to 1.0.7 would likely fix it and is worth doing - it is a real
reliability risk for these as sparring partners.
- 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
Renames PatternMover_garage → PatternMover, RandomMover_garage → RandomMover,
WaveSurfer_garage → WaveSurfer. Updates all .json, .sh, .nimble, and config.nims
files to match TR Booter naming convention (directory name = bot name).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add maxSpeed param (default true) to runBattleRunner/runBattle
- Drain stdout in poll loop — Java blocked on full pipe buffer causing timeout
- TestBattleRunner.java already had --max-speed; .class was stale and needed recompile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds TR_SAMPLE_BOTS env var support to point to external sample bots (Walls, Fire, SpinBot, etc). Updates test_bullet_economy.nim to use Walls from the sample-bots directory instead of custom WallsBot, removing the hardcoded path dependency.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Document exception handling, zero-value BotResult trap, shared adversary bots
- Add offline parsing example using parseServerOutput
- Skip tests gracefully when JARs missing (guard before suite blocks)
- Fix blocking readLine in runner_process.nim: poll with 50ms sleep + atEnd check
(was preventing timeout enforcement, now blocks correctly during battle)
- Add test task to QBot.nimble and config.nims setup docs to AGENTS.md
- Add debug logging to TestBattleRunner for bot identity tracking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements:
- BattleResult type and JSON-lines parser (#127)
- TR server lifecycle manager (#128)
- Bot compiler using nim c (#129)
- runBattle() orchestrator (#130)
- Example test in OscillatorBot_garage (#131)
- Framework usage guide (#132)
- TestBattleRunner.java for external server (#133)
- BattleRunner process lifecycle (#134)
Fix: runner_process.nim was redefining TimeoutError locally; now
uses std/net.TimeoutError consistently with server_manager.nim.