feat(tools): capture real DrussGT movement from classic Robocode as fixtures
There are no genuinely competitive adversaries for Tank Royale, and the in-repo ones were broken until recently. Classic Robocode 1.9.5.5 is obtainable (SourceForge, 20.4 MB) and its programmatic control API (robocode.control.RobocodeEngine + BattleAdaptor.onTurnEnded) can run real battles headless and expose per-turn robot state. So a legacy leader bot's MOVEMENT can be captured and used as a gun-testing fixture with no port. Captured unmodified DrussGT 3.1.4159 vs spinbot/ramfire/crazy/corners and a mirror match: 28,797 ticks, plus two trivial-bot contrasts. Conversion to the Tank Royale convention is validated to 0.000-0.001 deg by recomputing the direction implied by (heading, speed) and comparing it against the recorded per-tick displacement -- i.e. the data is proven to be genuine recorded motion rather than a mangled export. (A first attempt treated the snapshot API's headings as degrees; they are radians, ~95 deg off.) The statistics confirm it is really a wave surfer: perpendicular to the opponent 65-96% of ticks, radial ~0.001, 41-72% of ticks at full speed, reversing on 42-46% of ticks, holding range at a 283-526 px median. The straight-line contrast is radial-dominant (0.75) with ZERO reversals. Discovery: DrussGT detects predictable guns and switches to a bullet-shield stand-still mode, so captures against sample.Walls/TrackFire had to be rejected as non-movement. CAVEATS, recorded in DRUSSGT_FIXTURES.md: these are open-loop (replayed DrussGT never dodges OUR bullets) and perfect-information (the observer gives true positions every tick, unlike our stale live WorldState). Both make our guns look better than in live play, so use them for RELATIVE gun ranking, not absolute hit rates. Jars stay out of git; capture tooling is reproducible via capture.sh.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# robocode_fixture_capture
|
||||
|
||||
Reproduces the classic-Robocode **DrussGT movement fixtures** in
|
||||
`tools/fixtures/` (see `tools/fixtures/DRUSSGT_FIXTURES.md` for the data,
|
||||
conversion and caveats).
|
||||
|
||||
## Run it
|
||||
|
||||
```sh
|
||||
tools/robocode_fixture_capture/capture.sh # writes to tools/fixtures
|
||||
tools/robocode_fixture_capture/capture.sh /tmp/out # or another directory
|
||||
```
|
||||
|
||||
The script:
|
||||
|
||||
1. downloads classic Robocode 1.9.5.5 from SourceForge into `/tmp/robocode/`
|
||||
(its "setup jar" is a plain zip of the full install),
|
||||
2. downloads the third-party `DrussGT.jar` from the Robocode archive,
|
||||
3. builds the trivial `trivial.StraightLine` contrast bot as a jar,
|
||||
4. compiles `Capture.java` and runs the headless battles,
|
||||
5. writes `*.jsonl` fixtures plus `drussgt_meta/*.rounds.json` sidecars.
|
||||
|
||||
**The Robocode distribution and the DrussGT jar are third-party
|
||||
redistributables and are intentionally kept out of the repository** — they live
|
||||
only under `/tmp/robocode/`. Nothing under `/tmp` is committed.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `capture.sh` | download + build + run everything |
|
||||
| `Capture.java` | headless `RobocodeEngine` observer that dumps per-turn state to JSONL |
|
||||
| `StraightLine.java` / `StraightLine.properties` | trivial constant-speed contrast bot |
|
||||
| `analyze.py` | conversion validation + movement statistics |
|
||||
|
||||
## Running the engine headlessly
|
||||
|
||||
Robocode 1.9.5.5 installs a `SecurityManager` and reflects into
|
||||
`sun.net.www.protocol.jar`. On Java 21 both are blocked by default, so the JVM
|
||||
needs (this is what `capture.sh` uses):
|
||||
|
||||
```
|
||||
-Djava.security.manager=allow
|
||||
--add-opens=java.base/sun.net.www.protocol.jar=ALL-UNNAMED
|
||||
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED
|
||||
--add-opens=java.desktop/sun.awt=ALL-UNNAMED
|
||||
```
|
||||
|
||||
Java 17/11 work with fewer flags (the `--add-opens` for `sun.net` is still
|
||||
required on 17+).
|
||||
|
||||
## Key API gotchas found while building this
|
||||
|
||||
- `RobocodeEngine.getLocalRepository(name)` requires the **full class name**
|
||||
(`jk.mega.DrussGT`, not `DrussGT`). A robot added as loose `.class` files is
|
||||
indexed but marked unusable (`trivial.StraightLine*`) and cannot be selected;
|
||||
packaging it as a **jar** with a `robots:` manifest entry fixes it.
|
||||
Delete `robots/robot.database` to force a repository rescan.
|
||||
- `IRobotSnapshot.getBodyHeading()` / `getGunHeading()` / `getRadarHeading()`
|
||||
return **radians** (classic convention: 0=north, clockwise) — the rest of the
|
||||
classic robot API uses degrees. `getVelocity()` is signed px/turn.
|
||||
See the conversion in `tools/fixtures/DRUSSGT_FIXTURES.md`.
|
||||
|
||||
## Validate / analyse
|
||||
|
||||
```sh
|
||||
python3 tools/robocode_fixture_capture/analyze.py \
|
||||
tools/fixtures/drussgt_vs_*.jsonl tools/fixtures/contrast_*.jsonl
|
||||
```
|
||||
|
||||
`analyze.py` reports the coordinate-conversion check (mean angular error between
|
||||
recorded displacement and the direction implied by `eh`+`es`; expect 0.000°),
|
||||
speed / heading-change distributions, range statistics and the
|
||||
perpendicular-vs-radial split.
|
||||
Reference in New Issue
Block a user