Which save moments can EvoBot actually act on? #90

Closed
opened 2026-08-26 06:55:34 +02:00 by SirStone · 2 comments
Owner

part-of: #88

Question

From Robocode Tank Royale official docs/sources: which events reach the bot and allow acting — self BotDeathEvent, RoundEndedEvent, GameEndedEvent — and what (if anything) is deliverable on Ctrl-C / server drop / connection loss. Include delivery timing relative to round boundaries. Resolution output: table of trigger → fires on clean end? → fires on abort/kill? → usable as save moment y/n.

part-of: #88 ## Question From Robocode Tank Royale official docs/sources: which events reach the bot and allow acting — self BotDeathEvent, RoundEndedEvent, GameEndedEvent — and what (if anything) is deliverable on Ctrl-C / server drop / connection loss. Include delivery timing relative to round boundaries. Resolution output: table of trigger → fires on clean end? → fires on abort/kill? → usable as save moment y/n.
SirStone added the wayfinder:research label 2026-08-26 06:55:34 +02:00
Author
Owner

claimed by wayfinder-research-agent, 2026-08-26

claimed by wayfinder-research-agent, 2026-08-26
Author
Owner

Resolution — researched against official Tank Royale protocol schemas, server source (Kotlin), Java Bot API reference, and official flow docs. Events are platform-defined; findings hold for the .Nim binding insofar as it follows the protocol.

Trigger table

Trigger Fires on clean end? Fires on abort/kill? Usable as save moment?
onDeath / own BotDeathEvent ✅ Yes — public event sent to all bots incl. the victim, inside the tick of the turn the bot died (server: addPublicBotEvent, Java API) ❌ No — death only happens from game logic; process kill/server drop bypasses it entirely ⚠️ Partial — only if THIS bot dies mid-match; a surviving EvoBot never gets it
onRoundEnded (RoundEndedEventForBot) ✅ Yes — sent individually to every connected participant each round, dead or alive (GameServer.broadcastRoundEndedToParticipants) ❌ No — abort skips round-end broadcast ✅ Yes — best per-round save moment (fires every round regardless of survival; carries accumulated results)
onWonRound (WonRoundEvent) ✅ Yes — private event to round winner(s) in final tick (ModelUpdater.nextRound) ❌ No ⚠️ Only when this bot wins the round
onGameEnded (GameEndedEventForBot) ✅ Yes — victory (≤1 alive bot/team on last round), max-turns, or all-disabled draw (GameServer.onGameEnded) ❌ No — abort path (stop-game) sends NO game-ended to anyone ✅ Yes but insufficient alone — only fires at battle end
Game aborted via controller/server UI (game-aborted-event) n/a ⚠️ Event exists but server broadcasts it only to observers + controllers, NOT to bots (broadcastGameAborted() → broadcastToObserverAndControllers, GameServer.kt L785); no bot API exposes a handler ❌ No
User Ctrl-C / SIGKILL of bot process n/a ❌ Nothing protocol-defined reaches a killed process. Only a self-installed OS signal handler (outside Tank Royale) could write to disk first ❌ Not via platform; own signal handler = only option
Server shutdown / crash n/a ❌ No event; bots just stop receiving ticks and time out (battle-lifecycle.md error handling) ❌ As event. See client-side callbacks below
Network drop n/a ❌ No server event; server marks bot gone and continues ⚠️ Best-effort: client-side onDisconnected / onConnectionError fire in the still-alive bot process when the socket breaks → last-resort save hook (IBaseBot.onDisconnected)

Key answers

  1. Own death event: Yes — BotDeathEvent is a public bot event; the victim receives its own death (API splits it into onDeath [self] vs onBotDeath [any bot]). Delivered in the dying turn's tick, i.e., before that round's RoundEndedEvent.
  2. RoundEndedEvent: Sent by the server individually to all participants after every round's final tick — including dead bots. Delivery assumes a live WebSocket; it is not retransmitted.
  3. GameEndedEvent: Clean completion only (victory/max-turns/draw). The abort path produces nothing for bots.
  4. Abort/kill paths: No abort signal ever reaches a bot. The ONLY hooks where a dying bot can still write to disk: (a) any normal event handler running before death (handlers execute synchronously in the bot process — direct disk writes work); (b) client-side onDisconnected/onConnectionError when socket drops while process lives; (c) self-installed SIGINT/SIGTERM handlers. SIGKILL/power-loss = unrecoverable by design → argues for frequent periodic saves, not end-of-battle saves.
  5. Timing/ordering between rounds: Per round end the sequence is strictly: final tick-event-for-bot (carrying WonRound/BotDeath events) → round-ended-event-for-bot → next round starts with round-started-event → first tick. Client APIs dispatch all queued events during round-end handling before the next round begins (event-handling.md). Dead bots receive no further ticks except their death tick (server skips them unless they have pending events), but DO keep receiving RoundEnded/GameEnded.

Implication for EvoBot checkpointing: the only trigger that fires reliably every round regardless of survival or how the match later ends is onRoundEnded. onGameEnded is a bonus final flush, never the safety net. Abort/Ctrl-C/server-death give at most onDisconnected (best-effort).

Resolution — researched against official Tank Royale protocol schemas, server source (Kotlin), Java Bot API reference, and official flow docs. Events are platform-defined; findings hold for the .Nim binding insofar as it follows the protocol. ## Trigger table | Trigger | Fires on clean end? | Fires on abort/kill? | Usable as save moment? | |---|---|---|---| | `onDeath` / own `BotDeathEvent` | ✅ Yes — public event sent to **all bots incl. the victim**, inside the tick of the turn the bot died ([server: `addPublicBotEvent`](https://github.com/robocode-dev/tank-royale/blob/main/server/src/main/kotlin/dev/robocode/tankroyale/server/core/TurnProcessor.kt), [Java API](https://github.com/robocode-dev/tank-royale/blob/main/bot-api/java/src/main/java/dev/robocode/tankroyale/botapi/IBaseBot.java)) | ❌ No — death only happens from game logic; process kill/server drop bypasses it entirely | ⚠️ Partial — only if THIS bot dies mid-match; a surviving EvoBot never gets it | | `onRoundEnded` (`RoundEndedEventForBot`) | ✅ Yes — sent individually to **every connected participant each round**, dead or alive ([GameServer.broadcastRoundEndedToParticipants](https://github.com/robocode-dev/tank-royale/blob/main/server/src/main/kotlin/dev/robocode/tankroyale/server/core/GameServer.kt)) | ❌ No — abort skips round-end broadcast | ✅ **Yes — best per-round save moment** (fires every round regardless of survival; carries accumulated results) | | `onWonRound` (`WonRoundEvent`) | ✅ Yes — private event to round winner(s) in final tick ([ModelUpdater.nextRound](https://github.com/robocode-dev/tank-royale/blob/main/server/src/main/kotlin/dev/robocode/tankroyale/server/core/ModelUpdater.kt)) | ❌ No | ⚠️ Only when this bot wins the round | | `onGameEnded` (`GameEndedEventForBot`) | ✅ Yes — victory (≤1 alive bot/team on last round), max-turns, or all-disabled draw ([GameServer.onGameEnded](https://github.com/robocode-dev/tank-royale/blob/main/server/src/main/kotlin/dev/robocode/tankroyale/server/core/GameServer.kt)) | ❌ **No** — abort path (`stop-game`) sends NO game-ended to anyone | ✅ Yes but insufficient alone — only fires at battle end | | Game aborted via controller/server UI (`game-aborted-event`) | n/a | ⚠️ Event exists but server broadcasts it **only to observers + controllers, NOT to bots** (`broadcastGameAborted()` → `broadcastToObserverAndControllers`, [GameServer.kt L785](https://github.com/robocode-dev/tank-royale/blob/main/server/src/main/kotlin/dev/robocode/tankroyale/server/core/GameServer.kt)); no bot API exposes a handler | ❌ No | | User Ctrl-C / SIGKILL of bot process | n/a | ❌ Nothing protocol-defined reaches a killed process. Only a self-installed OS signal handler (outside Tank Royale) could write to disk first | ❌ Not via platform; own signal handler = only option | | Server shutdown / crash | n/a | ❌ No event; bots just stop receiving ticks and time out ([battle-lifecycle.md error handling](https://robocode-dev.github.io/tank-royale/articles/tank-royale.html)) | ❌ As event. See client-side callbacks below | | Network drop | n/a | ❌ No server event; server marks bot gone and continues | ⚠️ Best-effort: client-side `onDisconnected` / `onConnectionError` fire **in the still-alive bot process** when the socket breaks → last-resort save hook ([IBaseBot.onDisconnected](https://github.com/robocode-dev/tank-royale/blob/main/bot-api/java/src/main/java/dev/robocode/tankroyale/botapi/IBaseBot.java)) | ## Key answers 1. **Own death event:** Yes — `BotDeathEvent` is a *public* bot event; the victim receives its own death (API splits it into `onDeath` [self] vs `onBotDeath` [any bot]). Delivered in the dying turn's tick, i.e., *before* that round's `RoundEndedEvent`. 2. **RoundEndedEvent:** Sent by the server individually to all participants after every round's final tick — including dead bots. Delivery assumes a live WebSocket; it is not retransmitted. 3. **GameEndedEvent:** Clean completion only (victory/max-turns/draw). The abort path produces nothing for bots. 4. **Abort/kill paths:** No abort signal ever reaches a bot. The ONLY hooks where a dying bot can still write to disk: (a) any normal event handler running before death (handlers execute synchronously in the bot process — direct disk writes work); (b) client-side `onDisconnected`/`onConnectionError` when socket drops while process lives; (c) self-installed SIGINT/SIGTERM handlers. SIGKILL/power-loss = unrecoverable by design → argues for frequent periodic saves, not end-of-battle saves. 5. **Timing/ordering between rounds:** Per round end the sequence is strictly: final `tick-event-for-bot` (carrying WonRound/BotDeath events) → `round-ended-event-for-bot` → next round starts with `round-started-event` → first tick. Client APIs dispatch all queued events during round-end handling before the next round begins ([event-handling.md](https://robocode-dev.github.io/tank-royale/articles/tank-royale.html)). Dead bots receive no further ticks except their death tick (server skips them unless they have pending events), but DO keep receiving RoundEnded/GameEnded. **Implication for EvoBot checkpointing:** the only trigger that fires reliably every round regardless of survival or how the match later ends is `onRoundEnded`. `onGameEnded` is a bonus final flush, never the safety net. Abort/Ctrl-C/server-death give at most `onDisconnected` (best-effort).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: SirStone/SirRoboGarage#90