Commit Graph

12 Commits

Author SHA1 Message Date
SirStone d3b3c28cdf fix(adversaries): migrate to bot-api 1.0.7 - kills an intermittent crash that corrupted measurements
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.
2026-09-21 09:14:47 +02:00
SirStone 1e8f0d342a fix(adversaries): the launchers ran STALE binaries - this is why the earlier fix never took effect
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.
2026-09-21 08:22:04 +02:00
SirStone c214abcfa8 fix(adversaries): repair four of the five sparring bots
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.
2026-09-21 08:21:47 +02:00
SirStone c034eb9d25 feat(testing): gun rack gauntlet + analysis reports
- fix(ModularBot): onBulletHitBot → onBulletHit (real hits were never tracked)
- feat(ModularBot): per-round gun stats dump to /tmp/gun_stats.jsonl
- feat(ModularBot): gun selection counter per round
- fix(tests): adversary paths _garage suffix removed from 7 test files
- feat(tests): test_gauntlet_5bots.nim — 10-round gauntlet vs all 5 adversaries
- feat(tests): analyze_gun_stats.nim — JSONL parser for gun performance tables
- docs: gun_rack_analysis.md — full per-gun performance report
- docs: gun_rack_summary.md — TL;DR verdict table (keep/drop/tune)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-20 21:14:12 +02:00
SirStone 2cc2a3bd87 fix(ModularBot): ram loop prevention, dead-target guards, cleaner logging
- 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
2026-09-20 20:44:45 +02:00
SirStone ab473c6b68 feat(ModularBot): melee targeting — multi-enemy tracker, per-enemy gun fitness, radar auto-switch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-20 12:25:10 +02:00
SirStone 63db0a4439 refactor: strip _garage suffix from adversarial test bots
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>
2026-09-20 10:50:32 +02:00
SirStone 1eaaadf627 refactor: move adversarial test bots to common_libs/test_framework/adversaries/ 2026-09-20 10:49:31 +02:00
SirStone be4e9549ef fix(test_framework): pass --tps -1 to server for uncapped headless speed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-16 22:57:28 +02:00
SirStone 67bba96da1 remove(test_framework): delete custom WallsBot, will use original bots 2026-09-16 08:43:16 +02:00
SirStone 94eb805d04 feat(test_framework): add WallsBot adversary for lead prediction testing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-16 08:40:26 +02:00
SirStone 7e1f1483a4 docs(test_framework): add comprehensive integration test guide and fix runner blocking issue
- 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>
2026-09-13 10:54:56 +02:00