Files
SirStone 4f18c8ce07 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.
2026-09-20 23:44:42 +02:00

2.9 KiB

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

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

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.