robocode_shim: generalize bridge to any legacy bot; validate 28 champions as sparring partners

Generalize the classic-Robocode shim so the hosted bot's main class, jar, extra
classpath and data directory are configurable (SHIM_BOT_CLASS / SHIM_BOT_JAR /
SHIM_EXTRA_CP / SHIM_DATA), with DrussGT kept as the default so every existing
script, fixture and generated bot dir behaves identically.

- LegacyBotBridge: generalized bridge (DrussGTBridge kept as an alias).
- BotHost: (mainClass, jar, extraJars, dataDir); disableShield -> generic
  disableStaticBoolean.
- ClassicPeer: implement ITeamRobotPeer; synthesize StatusEvent each turn;
  make move/turnBody/turnGun/turnRadar the immediate (turn-ending) variants;
  guard re-entrant execute() from event handlers; record delivered events.
- make_botdir.sh / run_bridge_battle.sh / run_smoke.sh generalized, legacy
  invocations unchanged; generated launcher uses a per-process mktemp data dir
  so concurrent bots/battles cannot clobber one classic data directory.

Validated 35 legacy bots against a real Tank Royale battle (2 rounds vs sample
SpinBot): 28 usable (27 effective + DrussGT), 5 weak-but-playing, 2 failing.
Adds robots.json (manifest) and LEGACY_BOTS.md (how-to, status, missing-API
costs). No third-party jar is committed.
This commit is contained in:
2026-09-26 00:25:33 +02:00
parent 0f5cfe37b2
commit ef16dd982d
11 changed files with 1553 additions and 532 deletions
+196
View File
@@ -0,0 +1,196 @@
# Legacy champion bots as Tank Royale sparring partners
The classic-Robocode bridge in `tools/robocode_shim/` is now **generalized**: it can
host any unmodified classic robot jar, not just DrussGT. This document is the
short version of how to use it and what was validated. The machine-readable
manifest is [`robots.json`](robots.json).
**No third-party jar is committed.** Every jar lives in `/tmp` and is referenced
by absolute path; the repo only contains the shim, the manifest and this doc.
## Direct answer
* **28 legacy champions are usable right now** (27 validated as effective +
DrussGT itself). A further **5 play but are weak/degraded** (they fire and
scan but landed 0 hits against the sample SpinBot). **2 fail** (Nene,
Seraphim — see below).
* Cost to add the top remaining failures: **~2–4 h** to add a second driver path
for bots that implement `IBasicRobot` directly and return `null` from
`getRobotRunnable()` (this is the only hard blocker left; it unlocks Nene and
Seraphim, the two Azuma-framework melee bots), plus ~0.5–1 h to diagnose the
divide-by-zero those bots hit during construction.
* The weak-but-working bots need **~2–6 h of physics-divergence investigation**
(they are not blocked on a missing API — see “Degraded bots” below).
## Add a bot in 3 commands
```bash
# 1. download the jar (the RoboRumble archive is browsable)
curl -Lo /tmp/bots/voidious.Diamond_1.8.28.jar \
http://robocode-archive.strangeautomata.com/robots/voidious.Diamond_1.8.28.jar
# 2. generate the Tank Royale bot directory
tools/robocode_shim/make_botdir.sh Diamond \
--class voidious.Diamond --jar /tmp/bots/voidious.Diamond_1.8.28.jar
# 3. prove it plays a real Tank Royale battle (2 rounds vs the sample SpinBot)
SHIM_BOTDIR=/tmp/tr_bots/Diamond tools/robocode_shim/run_bridge_battle.sh \
/home/davide/Projects/tank-royale/sample-bots/java/build/archive/SpinBot 2
```
`make_botdir.sh` accepts `--class`, `--jar`, `--name`, `--version`, `--authors`,
`--desc`, `--country`, `--gametypes`. The legacy forms still work unchanged:
```bash
tools/robocode_shim/make_botdir.sh # -> /tmp/tr_bots/DrussGT (DrussGT defaults)
tools/robocode_shim/make_botdir.sh /tmp/tr_bots/DrussGT # same, explicit path
```
### GUI steps
1. Launch the Tank Royale GUI.
2. **Bots → Add bot**, and select the generated bot directory `/tmp/tr_bots/<Name>`
(or its `<Name>.json`). The GUI reads the `.json` identity file.
3. Repeat for each `/tmp/tr_bots/<Name>` directory you want in the roster.
4. Start a classic 1v1 (or melee) battle and add the bots as participants.
The generated `<Name>.sh` launcher selects the hosted classic robot via
`SHIM_BOT_CLASS` / `SHIM_BOT_JAR` and creates its own data directory, so the GUI
does not need any extra environment variables.
### One-line smoke battle for any generated bot
```bash
SHIM_BOTDIR=/tmp/tr_bots/<Name> tools/robocode_shim/run_bridge_battle.sh \
/home/davide/Projects/tank-royale/sample-bots/java/build/archive/SpinBot 2
```
### Offline triage (no server, ~2 s per bot)
```bash
SHIM_BOT_JAR=/tmp/bots/<jar> SHIM_BOT_CLASS=<fqcn> tools/robocode_shim/run_smoke.sh
```
> Caution: this synthetic harness has **false negatives** — Diamond, Dookious and
> Lukious NPE there but play fine in a real battle. Only a real battle is
> authoritative.
## Status table (MEASURED: 2-round battles vs sample SpinBot, embedded server)
| # | Name | Style (inferred) | Status | Evidence (fired / hits / taken, 2 rounds) |
|---|---|---|---|---|
| — | DrussGT | DC gun + wave-surfing movement | **REFERENCE** | 141 / 17 / 0 — won 2/2 |
| 1 | Diamond | pattern-matching gun + surf | **WORKS** | 50 / 18 / 1 — won 2/2 |
| 2 | Dookious | surf movement + pattern/GF gun | **WORKS** | 86 / 16 / 1 — won 2/2 |
| 3 | Lukious | wave-surfing movement | **WORKS** | 70 / 15 / 3 — won 2/2 |
| 4 | Shadow | pattern matching (team-derived) | **WORKS** | 25 / 19 / 0 — won 2/2 |
| 5 | Phoenix | pattern matching (team-derived) | **WORKS** | 50 / 17 / 0 — won 2/2 |
| 6 | RougeDC | dynamic-clustering gun + surf | **WORKS** | 67 / 14 / 2 — won 2/2 |
| 7 | DiamondStealer | rammer / brawler | **WORKS** | 18 / 12 / 1 — won 2/2 |
| 8 | GresSuffurd | surf gun + movement | **WORKS** | 72 / 15 / 0 — won 2/2 |
| 9 | KurtWaveSurfer | wave surfer | **WORKS** | 84 / 13 / 1 — won 2/2 |
| 10 | CassiusClay | pattern-matching gun + surf | **WORKS** | 55 / 11 / 0 — won 2/2 |
| 11 | Coriantumr | mini pattern matcher | **WORKS** | 41 / 14 / 1 — won 2/2 |
| 12 | BrokenSword | mini pattern matcher | **WORKS** | 38 / 15 / 3 — won 2/2 |
| 13 | WaveSurferGF | wave surfer (GF gun) | **WORKS** | 49 / 13 / 1 — won 2/2 |
| 14 | WaveSurferPG | wave surfer (pattern gun) | **WORKS** | 57 / 16 / 3 — won 2/2 |
| 15 | TripHammer | pattern matcher | **WORKS** | 52 / 19 / 1 — won 2/2 |
| 16 | RetroGirl | perceptual pattern matcher | **WORKS** | 106 / 26 / 5 — won 2/2 |
| 17 | Jen | micro wave surfer | **WORKS** | 71 / 13 / 2 — won 2/2 |
| 18 | BlitzBat | micro brawler | **WORKS** | 34 / 12 / 1 — won 2/2 |
| 19 | Ascendant | aggressive 1v1 mega | **WORKS** | 74 / 15 / 0 — won 2/2 |
| 20 | DiamondHawk | pattern matcher | **WORKS** | 45 / 18 / 0 — won 2/2 |
| 21 | YersiniaPestis | strong aggressive mega | **WORKS** | 52 / 12 / 0 — won 2/2 |
| 22 | HawkOnFire | corner camper / low bot | **WORKS** | 74 / 12 / 0 — won 2/2 |
| 23 | WallAvoider | micro wall-avoider (blocking style) | **WORKS** | 47 / 10 / 1 — won 2/2 |
| 24 | Cigaret | mini surfer | **WORKS** | 46 / 5 / 5 — split 1-1 |
| 25 | CigaretBH | mini bullet-hiding surfer | **WORKS** | 67 / 11 / 3 — split 1-1 |
| 26 | Komarious | mini pattern matcher / surfer | **WORKS** | 62 / 20 / 10 — split 1-1 |
| 27 | LionWWSVMvoid | wave-surfing SVM gun | **WORKS** | 62 / 12 / 12 — lost 0-2 |
| 28 | FloodMini | mini flood-fill movement | WORKS_WEAK | 25 / 0 / 10 — lost 0-2 |
| 29 | Aristocles | micro surfer | WORKS_WEAK | 39 / 0 / 10 — lost 0-2 |
| 30 | PatternRobot | simple pattern robot | WORKS_WEAK | 8 / 0 / 2 — lost 0-2 |
| 31 | LightningBug | pattern matcher | WORKS_WEAK | 36 / 0 / 10 — lost 0-2 |
| 32 | Aurora | micro (team-derived) | WORKS_WEAK | 6 / 0 / 13 — lost 0-2 |
| 33 | Nene | melee (Azuma framework) | **FAILS** | 0 / 0 / 14 — inert |
| 34 | Seraphim | melee (Azuma framework) | **FAILS** | 0 / 0 / 14 — inert |
`WORKS` = the battle completed, the bot scanned, moved and fired, and landed hits.
`WORKS_WEAK` = the battle completed and the bot scanned/fired, but landed **0**
hits against SpinBot; usable as a target/movement sparring partner, not as a
gun-quality reference. Losing to SpinBot is not itself a failure (LionWWSVMvoid
lost but is classified WORKS because it landed 12 hits and clearly fights).
## Missing-API summary
Four classic-Robocode APIs were missing from the original shim. Three were
**implemented in this change** because several bots failed on the same call —
each is now a one-time cost of ~0.5–1 h (already paid):
| API | Was missing | Cost | Unlocked |
|---|---|---|---|
| `robocode.robotinterfaces.peer.ITeamRobotPeer` (`getTeammates`, `isTeammate`, `broadcastMessage`, `sendMessage`, `getMessageEvents`) | `TeamRobot.setPeer()` casts the peer to it, so every `TeamRobot`-derived bot died with `ClassCastException` | **DONE** (~1 h) | Shadow, Phoenix, RougeDC, DiamondStealer, Ascendant, Aurora |
| `robocode.StatusEvent` / `RobotStatus` synthesis each turn | never delivered; RougeDC busy-loops forever waiting for one (`ensureVersion` → “Got no status event! Dying now!”) | **DONE** (~1 h) | RougeDC; generally required by event-driven bots |
| Non-advanced **immediate** semantics of `peer.move` / `turnBody` / `turnGun` / `turnRadar` | they only set the intent; classic performs the turn **and ends the turn** (`Robot.ahead`, `AdvancedRobot.turnRadarRightRadians`, …). Bots whose `run()` is `while (true) turnRadarRightRadians(1);` spun forever | **DONE** (~0.5 h) | Komarious, KurtWaveSurfer, Cigaret/CigaretBH, RetroGirl, Jen, WallAvoider, PatternRobot, … |
| Re-entrancy guard for `execute()` called from inside an event handler | classic blocking robots call `Robot.fire()` inside `onScannedRobot`; the nested tick deadlocked | **DONE** (~0.5 h) | WallAvoider, PatternRobot |
**Still missing (not implemented):**
| Gap | Symptom | Bots | Cost estimate |
|---|---|---|---|
| Alternate driver for bots that implement `IBasicRobot`/`ITeamRobot` **directly** instead of extending `robocode.Robot` | `getRobotRunnable()` returns `null`; the bot is inert in battle. `cs.Azuma` even extends `java.io.PrintStream`. | Nene, Seraphim | **2–4 h** (second driver path: detect the null runnable and drive the bot’s own `execute()`/event listener instead). Plus **0.5–1 h** to diagnose the `ArithmeticException: divide by zero` Nene throws during construction. |
| Degraded gun/movement for several micro/mini bots | they scan and fire but land 0 hits; not a missing API — almost certainly the documented physics divergences (move/turn ordering, distance bookkeeping; see README §5.9) | FloodMini, Aristocles, PatternRobot, LightningBug, Aurora | **2–6 h** of measurement/diagnosis; not blocking (they still play). |
## Isolation guarantees (parallel battles)
* **Data directory:** the generated `<Name>.sh` sets
`SHIM_DATA="${SHIM_DATA:-${DRUSSGT_DATA:-$(mktemp -d …)}}"` — a fresh
per-process directory. Two bots, or two concurrent battles with the same bot,
can never share or clobber a classic data directory. Callers can still pin a
directory via `SHIM_DATA` (new) or `DRUSSGT_DATA` (legacy, e.g. `tools/ab/`).
* **Bot selection:** `SHIM_BOT_CLASS` + `SHIM_BOT_JAR` (+ optional
`SHIM_EXTRA_CP` for dependencies) are per-bot, so different legacy bots can
run concurrently in different JVMs.
* **Ports / processes:** the Tank Royale embedded server picks ephemeral ports
and each battle is a separate process (the existing `tools/ab` machinery
already relies on this and is unchanged).
## Generalizing the shim (what changed)
* `src/robocode_shim/LegacyBotBridge.java` — the generalized bridge (the old
DrussGT-specific logic, now configurable). Defaults reproduce DrussGT exactly:
`SHIM_BOT_CLASS=jk.mega.DrussGT`, `SHIM_BOT_JAR=${DRUSSGT_JAR:-/tmp/drussgt/DrussGT.jar}`,
`SHIM_DATA=${DRUSSGT_DATA:-/tmp/drussgt_bridge_data}`, `DRUSSGT_SHIELD=1` still
keeps the EnergyDome shield on.
* `src/robocode_shim/DrussGTBridge.java` — now a thin backwards-compatible alias
so already-generated bot directories keep launching.
* `src/robocode_shim/BotHost.java` — takes `(mainClass, jar, extraJars, dataDir)`;
`disableShield()` generalized to `disableStaticBoolean(field)` (a silent no-op
for bots without the field).
* `src/robocode_shim/ClassicPeer.java` — `ITeamRobotPeer`, `StatusEvent`
synthesis, immediate `move`/`turn*` semantics, re-entrant `execute()` guard,
delivered events recorded in `getAllEvents()`.
* `make_botdir.sh`, `run_bridge_battle.sh`, `run_smoke.sh` — generalized; all
legacy invocations (no args, positional path, `DRUSSGT_BOTDIR`/`DRUSSGT_DATA`)
still behave identically.
## Reproducing the validation
```bash
export ROBOCODE_JAR=/tmp/robocode/install/libs/robocode.jar
export TR_BOT_API_JAR=$HOME/Downloads/sample-bots-java-1.0.2/lib/robocode-tankroyale-bot-api-1.0.2.jar
tools/robocode_shim/build.sh
tools/robocode_shim/compile_drussgt.sh # DrussGT acceptance: 20/22 files, 43 classes
tools/robocode_shim/run_smoke.sh # DrussGT default path unchanged: OK
tools/robocode_shim/run_bridge_battle.sh \
/home/davide/Projects/tank-royale/sample-bots/java/build/archive/SpinBot 2
```
### MEASURED vs INFERRED
* **MEASURED:** every status in the table, all fire/hit/taken counts, the
missing-API symptoms (exception + method), the jar HTTP 200 downloads, the
DrussGT default-path smoke/acceptance/battle.
* **INFERRED:** the “style” column (from bot names/docs, not decompilation) and
the cost estimates. The cause of the degraded micro/mini bots is inferred
from the known physics divergences, not isolated to a single call.