feat(tools): drive the real DrussGT jar outside the Robocode engine
The question was whether we can fight a genuine legacy leader bot in Tank Royale. Answer: the API side is now PROVEN, not estimated. KEY FINDING: the classic robocode.* API is a thin delegation layer over a public seam. javap -c shows AdvancedRobot forwarding every call to _RobotBase.peer (IBasicRobotPeer/IAdvancedRobotPeer), and _RobotBase.setPeer is public final. So we do NOT need to reimplement the API: we reuse the genuine robocode.jar and implement only the 75-method peer interface. Consequences: - DrussGT's 22 sources compile against the real API with ZERO unresolved symbols. (The literal 22-file javac fails only on two PRE-EXISTING duplicate classes - GFRange and Indice are declared both inline in DrussGunDC.java and as standalone files - and the 6 classes that ship without source. The jar supplies all of them, so a shim never cares.) - Runtime smoke test PASSES: the unmodified DrussGT.jar is loaded through a child URLClassLoader and driven for 200 synthetic ticks, emitting movement intents every tick and 169 fire requests, with its thread surviving. This is the cheap path to the 'final boss', and it also unlocks the whole roborumble archive rather than one bot. Traps found by measurement: - ScannedRobotEvent's constructor order is (name, energy, bearing, distance, heading, velocity) - NOT heading-before-bearing. The wrong order silently yields distance=0, an immediate KD-tree insert and an NPE in EnemyMoves.predict; it hung the first smoke run. - RobocodeFileOutputStream has a hard engine dependency (resolves IThreadManagerBase via ContainerBase) and throws 'ThreadManager cannot be null!' outside the engine, killing the bot thread. Reached from DrussGT's own contain() error logging, so it must be stubbed. - robocode.RobotDeathEvent is required and was NOT in the predicted API list. - Bullet.equals() is genuinely called for bullet identity, not just getters. REMAINING WORK (README section 5): coordinate rotation DONE, execute()->tick bridge DONE and proven, ThreadManager fix scoped. The main open item is the classic motion model (setAhead distance semantics vs TR speed), estimated 1-3 days, plus event synthesis/ordering, gun heat, bullet identity, round and radar cadence, and firing translation. NO API UNKNOWNS REMAIN. Physics fidelity will still diverge from classic - expect to retune. Jars stay out of git (blocked by tools/robocode_shim/.gitignore); the genuine robocode.jar and DrussGT.jar are referenced from /tmp via ROBOCODE_JAR / DRUSSGT_JAR.
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
# Robocode shim spike — running the real DrussGT as a Tank Royale bot
|
||||
|
||||
**Question:** can the *unmodified* legacy DrussGT jar be driven from a shim, and
|
||||
which is cheaper — (A) re-implement `robocode.*` or (B) reuse the genuine
|
||||
`robocode.jar` and write only a driver?
|
||||
|
||||
**Answer: (B). Viable and chosen.** The real `robocode.jar` classes are pure
|
||||
delegates to a `peer` interface, and that seam is public. DrussGT compiles with
|
||||
**zero shim API symbols** and, in the runtime smoke test, the real bot executes
|
||||
its own event handlers and emits 200 ticks of movement intents + 169 fire
|
||||
requests through our peer.
|
||||
|
||||
This is an **API-coverage / architecture spike**, not a working bot. No physics
|
||||
is implemented; the peer's world model is stubbed.
|
||||
|
||||
---
|
||||
|
||||
## 1. Approach decision — evidence
|
||||
|
||||
`javap` on `/tmp/robocode/install/libs/robocode.jar` (Robocode 1.9.5.5):
|
||||
|
||||
* `robocode.AdvancedRobot extends robocode._AdvancedRadiansRobot` and
|
||||
implements `IAdvancedRobot, IAdvancedEvents`. **Every** getter/setter is a
|
||||
one-line delegation, e.g.:
|
||||
|
||||
```text
|
||||
AdvancedRobot.getDistanceRemaining() -> getfield peer : IBasicRobotPeer;
|
||||
invokeinterface IBasicRobotPeer.getDistanceRemaining()D
|
||||
AdvancedRobot.setAhead(double) -> getfield peer; checkcast IAdvancedRobotPeer
|
||||
invokeinterface IAdvancedRobotPeer.setMove:(D)V
|
||||
```
|
||||
|
||||
* The peer field lives on `robocode._RobotBase`:
|
||||
|
||||
```text
|
||||
robocode._RobotBase:
|
||||
robocode.robotinterfaces.peer.IBasicRobotPeer peer;
|
||||
public final void setPeer(robocode.robotinterfaces.peer.IBasicRobotPeer);
|
||||
public final void setOut(java.io.PrintStream);
|
||||
```
|
||||
|
||||
* `robocode.Robot` (the parent of `AdvancedRobot`):
|
||||
`getRobotRunnable()` returns `this`; `getBasicEventListener()` returns `this`.
|
||||
So there is **no engine requirement to start the bot**: implement the peer,
|
||||
`setPeer()`, and run `getRobotRunnable()` on a thread.
|
||||
|
||||
* The seam is one narrow interface. `IAdvancedRobotPeer` extends
|
||||
`IStandardRobotPeer` extends `IBasicRobotPeer` — **75 methods total**
|
||||
(38 + 6 + 31). We implement exactly that one interface
|
||||
(`ClassicPeer`) plus a classloader/driver (`BotHost`).
|
||||
|
||||
**Approach (A) would have required** re-declaring ≥15 `robocode.*` classes
|
||||
(`AdvancedRobot`, `Bullet`, `ScannedRobotEvent`, `Rules`, `Utils`, all the event
|
||||
types, `RobocodeFileOutputStream`, `Robot`, `_Robot`, `_RobotBase`,
|
||||
`_AdvancedRobot`, `_AdvancedRadiansRobot`, the `robotinterfaces.*` and
|
||||
`robotinterfaces.peer.*` hierarchies) and keeping their signatures in lockstep
|
||||
with the jar. Approach (B) throws all of that away. The only cost is that the
|
||||
shim must still supply the *semantics* the engine normally supplies — which is
|
||||
the remaining-work list in §5.
|
||||
|
||||
**Runtime proof (not just a compile):** `robocode_shim.SmokeTest` loads
|
||||
`/tmp/drussgt/DrussGT.jar` in a child `URLClassLoader`, calls
|
||||
`setPeer(ClassicPeer)`, starts the bot thread, and feeds 200 synthetic turns:
|
||||
|
||||
```text
|
||||
DrussGT jar: /tmp/drussgt/DrussGT.jar (159289 bytes)
|
||||
ticks executed : 200
|
||||
bot thread survived : true
|
||||
ticks with a move intent : 200
|
||||
first (setAhead,setTurnRR): (-20.271410058613192, -2.25459929233885)
|
||||
fire requests : 169
|
||||
last captured intents : move=-20.994678898031065 turnBody=0.17133770212129118 turnGun=-0.2965833407153049 turnRadar=Infinity
|
||||
bot thread alive : true
|
||||
SMOKE TEST: OK (real DrussGT drove through the peer seam)
|
||||
```
|
||||
|
||||
The first attempt hung; the thread dump showed real DrussGT frames —
|
||||
`jk.mega.DrussGT.onScannedRobot` → `DrussMoveGT.onScannedRobot` →
|
||||
`EnemyMoves.predict` — so the seam executes genuine bot code. The hang was a
|
||||
synthetic-input bug (see §4), fixed by passing the event constructor arguments
|
||||
in the correct order.
|
||||
|
||||
---
|
||||
|
||||
## 2. Acceptance test — `javac` compiles DrussGT
|
||||
|
||||
### Exact command (the one recorded by `compile_drussgt.sh`)
|
||||
|
||||
```bash
|
||||
ROBOCODE_JAR=/tmp/robocode/install/libs/robocode.jar
|
||||
DRUSSGT_JAR=/tmp/drussgt/DrussGT.jar
|
||||
OUT=/tmp/shim_out
|
||||
javac -nowarn -cp "$ROBOCODE_JAR:$DRUSSGT_JAR" -d "$OUT" \
|
||||
$(find /tmp/drussgt/src -name '*.java' ! -name 'GFRange.java' ! -name 'Indice.java')
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
```text
|
||||
Note: Some input files use or override a deprecated API that is marked for removal.
|
||||
Note: Recompile with -Xlint:removal for details.
|
||||
Note: Some input files use unchecked or unsafe operations.
|
||||
Note: Recompile with -Xlint:unchecked for details.
|
||||
exit: 0
|
||||
```
|
||||
|
||||
**20/22 source files compile clean, 43 classes emitted, zero unresolved symbols.**
|
||||
|
||||
### The literal "all 22 files" invocation and why it cannot compile
|
||||
|
||||
```bash
|
||||
javac -nowarn -cp "$ROBOCODE_JAR:$DRUSSGT_JAR" -d /tmp/shim_out \
|
||||
$(find /tmp/drussgt/src -name '*.java') # 22 files
|
||||
```
|
||||
|
||||
```text
|
||||
/tmp/drussgt/src/jk/mega/dGun/Indice.java:2: error: duplicate class: jk.mega.dGun.Indice
|
||||
class Indice implements Comparable{
|
||||
^
|
||||
/tmp/drussgt/src/jk/mega/dGun/GFRange.java:2: error: duplicate class: jk.mega.dGun.GFRange
|
||||
public class GFRange implements Comparable{
|
||||
^
|
||||
2 errors
|
||||
```
|
||||
|
||||
This failure is **pre-existing and independent of the shim**: `DrussGunDC.java`
|
||||
declares `GFRange` and `Indice` as extra top-level classes at its end
|
||||
(`DrussGunDC.java:729` and `:1207`), and the decompiler *also* emitted them as
|
||||
standalone files. The original `DrussGT.jar` already ships
|
||||
`jk/mega/dGun/GFRange.class` and `Indice.class`, so the two redundant `.java`
|
||||
files are excluded and the jar supplies them, together with the 6 genuinely
|
||||
unsourced classes. No API symbols are missing — the compiler asked us for
|
||||
**nothing**.
|
||||
|
||||
---
|
||||
|
||||
## 3. True API surface required by DrussGT
|
||||
|
||||
Measured from the **compiled bytecode** (`javap -c -p` over all 43 emitted
|
||||
classes, aggregating every `Method`/`InterfaceMethod`/`class` constant-pool
|
||||
reference), plus the `this.`-calls in `DrussGT.java` (which `javap` prints
|
||||
without an owner). This is exactly what the compiler/JVM demands.
|
||||
|
||||
### Types (14 `robocode.*`, 3 `java.awt.*` families)
|
||||
|
||||
| Type | Required members actually used |
|
||||
|---|---|
|
||||
| `robocode.AdvancedRobot` (superclass) | `getX, getY, getVelocity, getHeadingRadians, getGunHeadingRadians, getRadarHeadingRadians, getEnergy, getGunHeat, getGunCoolingRate, getTime, getOthers, getRoundNum, getNumRounds, getBattleFieldWidth, getBattleFieldHeight, getDistanceRemaining, getGunTurnRemaining, getGunTurnRemainingRadians, getTurnRemainingRadians, getRadarTurnRemaining, setAhead, setTurnRightRadians, setTurnGunRightRadians, setTurnRadarRightRadians, setFire, setFireBullet, execute, getDataFile, setColors, setAdjustGunForRobotTurn, setAdjustRadarForGunTurn, setAdjustRadarForRobotTurn, getAllEvents` |
|
||||
| `robocode.Bullet` | `getX, getY, getHeadingRadians, getPower, getVelocity, isActive, equals` |
|
||||
| `robocode.ScannedRobotEvent` | `getBearingRadians, getDistance, getEnergy, getHeadingRadians, getName, getVelocity` |
|
||||
| `robocode.Rules` | `getBulletDamage, getBulletSpeed, getGunHeat` |
|
||||
| `robocode.util.Utils` | `normalAbsoluteAngle, normalRelativeAngle` |
|
||||
| `robocode.HitByBulletEvent` | `getBullet, getVelocity` |
|
||||
| `robocode.BulletHitEvent` | `getBullet, getEnergy` |
|
||||
| `robocode.BulletHitBulletEvent` | `getBullet, getHitBullet` |
|
||||
| `robocode.RobotDeathEvent` | `getName` |
|
||||
| `robocode.SkippedTurnEvent` | `getTime` |
|
||||
| `robocode.HitRobotEvent` | *(parameter type only)* |
|
||||
| `robocode.WinEvent` | *(parameter type only)* |
|
||||
| `robocode.DeathEvent` | *(parameter type only)* |
|
||||
| `robocode.RobocodeFileOutputStream` | constructor + `write/close/flush` (via `PrintStream`) |
|
||||
| `java.awt.Color` | `setColors`, paint |
|
||||
| `java.awt.Graphics2D` | `onPaint(Graphics2D)` |
|
||||
| `java.awt.geom.Point2D`/`Rectangle2D` | `Point2D.Double`, `Rectangle2D.Double`, `distance`, `contains` |
|
||||
|
||||
Source imports confirm the same set: `import robocode.*` (×8),
|
||||
`import robocode.util.Utils` (×3), `import robocode.util.*` (×2),
|
||||
`java.awt.geom.*` (×13), `java.util.*` (×11), `java.awt.Color` (×3),
|
||||
`java.io.*` (×2), `java.io.PrintStream`, `java.io.IOException`.
|
||||
|
||||
### Surprises vs the predicted list
|
||||
|
||||
The predicted list was *"AdvancedRobot, Bullet, ScannedRobotEvent, Rules, Utils,
|
||||
HitByBulletEvent, BulletHitEvent, BulletHitBulletEvent, HitRobotEvent, WinEvent,
|
||||
DeathEvent, SkippedTurnEvent, RobocodeFileOutputStream, plus
|
||||
java.awt.geom/Color/Graphics."* Differences (**new information**):
|
||||
|
||||
1. **`robocode.RobotDeathEvent` is required and was NOT predicted.** The
|
||||
prediction listed only `DeathEvent`. `DrussGT.onRobotDeath(RobotDeathEvent)`
|
||||
forwards to `DrussMoveGT.onRobotDeath` and `MeleeRadar`, and calls
|
||||
`e.getName()`. `DeathEvent` (our own death) and `RobotDeathEvent` (another
|
||||
robot died) are distinct classic types.
|
||||
2. **`RobocodeFileOutputStream` is reached only through `DrussGT.contain()`**
|
||||
(`DrussGT.java:259`) and has a hard engine dependency — `javap -c` shows it
|
||||
calls `net.sf.robocode.core.ContainerBase.getComponent(IThreadManagerBase)`
|
||||
and throws `RobotException: ThreadManager cannot be null!` outside the
|
||||
engine. It is best-effort error logging, but it can *kill the bot thread*
|
||||
under the shim (observed). See §5.
|
||||
3. **`Bullet.equals()`** is called — DrussGT compares `Bullet` instances (our
|
||||
fired-bullet tracker), so `equals` semantics matter, not just the getters.
|
||||
4. **`Rules` needs the three instance methods** `getBulletDamage`,
|
||||
`getBulletSpeed`, `getGunHeat` — these *are* the classic physics constants we
|
||||
do not otherwise have.
|
||||
5. **`HitByBulletEvent.getVelocity()`** and **`BulletHitEvent.getEnergy()`** are
|
||||
used (not just `getBullet()`).
|
||||
6. **`java.util.Vector`** is the declared return of `AdvancedRobot.getAllEvents()`
|
||||
(used in `onDeath`), and `java.util.Hashtable`/`Vector` appear in
|
||||
`MeleeRadar` — a Java-collections surface the type-level prediction omitted.
|
||||
|
||||
Nothing in the actual set was *missing* from the real jar (as expected — the jar
|
||||
is the authority), and nothing required a custom stub.
|
||||
|
||||
---
|
||||
|
||||
## 4. Public signatures of the 6 unsourced classes
|
||||
|
||||
These have **no source** in `/tmp/drussgt/src`; the jar supplies their bytecode.
|
||||
Recorded with `javap -p -classpath /tmp/drussgt/DrussGT.jar` (package-private
|
||||
declarations included; `//` = our annotation).
|
||||
|
||||
### `jk.mega.dGun.JKDCUtils` (utility, all static)
|
||||
```text
|
||||
class jk.mega.dGun.JKDCUtils {
|
||||
static double HALF_PI, WALL_MARGIN, S, W, N, E; // mutable static geometry
|
||||
jk.mega.dGun.JKDCUtils();
|
||||
public static java.awt.geom.Point2D$Double project(Point2D$Double, double, double);
|
||||
public static double absoluteBearing(Point2D$Double, Point2D$Double);
|
||||
public static double velocityFromDistance(double);
|
||||
public static double limit(double, double, double);
|
||||
public static double bulletVelocity(double);
|
||||
public static double maxEscapeAngle(double);
|
||||
static double rollingAverage(double, double, double, double);
|
||||
public static double sqr(double);
|
||||
public static int getIndex(double[], double);
|
||||
static double wallDistance(double, double, double, Point2D$Double);
|
||||
static double distanceWest(double, double, double, double);
|
||||
static double decelDistance(double);
|
||||
}
|
||||
```
|
||||
|
||||
### `jk.mega.dGun.NormalDistribution`
|
||||
```text
|
||||
class jk.mega.dGun.NormalDistribution implements java.lang.Comparable {
|
||||
double position, height, inv_width;
|
||||
jk.mega.dGun.NormalDistribution();
|
||||
public int compareTo(java.lang.Object);
|
||||
}
|
||||
```
|
||||
|
||||
### `jk.mega.dGun.StoreScan`
|
||||
```text
|
||||
class jk.mega.dGun.StoreScan {
|
||||
jk.mega.dGun.GFRange range;
|
||||
double[] location, ASLocation;
|
||||
double vel, deltaHeading;
|
||||
jk.mega.dGun.StoreScan previous;
|
||||
jk.mega.dGun.StoreScan();
|
||||
}
|
||||
```
|
||||
|
||||
### `jk.mega.dGun.DCRobotState`
|
||||
```text
|
||||
class jk.mega.dGun.DCRobotState {
|
||||
double direction, deltaHeading, vel, heading, latVel, advVel, offset, distance,
|
||||
timeSinceDirChange, accel, distLast30, distLast20, distLast10,
|
||||
forwardWall, reverseWall, timeSinceDecel, timeSinceAccel, BFT,
|
||||
currentGF, mirrorOffset, MEA_pos, MEA_neg, GF0, hitGF;
|
||||
java.awt.geom.Point2D$Double location, enemyLocation;
|
||||
long time;
|
||||
int bulletsShot;
|
||||
boolean firstScan;
|
||||
jk.mega.dGun.DCRobotState();
|
||||
public double[] unweightedLocation();
|
||||
public double[] location();
|
||||
public double[] ASLocation();
|
||||
}
|
||||
```
|
||||
|
||||
### `jk.mega.dGun.DCWave`
|
||||
```text
|
||||
class jk.mega.dGun.DCWave {
|
||||
static java.util.ArrayList paintPoints;
|
||||
static int DCHits, actualHits, DCASHits;
|
||||
static double randomHits;
|
||||
static java.awt.geom.Point2D$Double targetLocation;
|
||||
static double targetHeading;
|
||||
static int GUN;
|
||||
static final int DC, DCAS, RANDOM;
|
||||
long fireTime;
|
||||
double bulletPower;
|
||||
java.awt.geom.Point2D$Double gunLocation;
|
||||
double bearing, lateralDirection, MEA_pos, MEA_neg, MEA_norm, GF0, BFT;
|
||||
boolean bulletFired, bulletAlive;
|
||||
double[] currentASBuffer;
|
||||
robocode.Bullet bullet;
|
||||
static jk.tree.KDTree<jk.mega.dGun.StoreScan> heapTree, ASTree;
|
||||
static long currentTime;
|
||||
jk.mega.dGun.StoreScan storeScan;
|
||||
boolean intersecting;
|
||||
double bestBearing, DCBearing, DCASBearing, randomBearing;
|
||||
jk.mega.dGun.DCRobotState scan;
|
||||
private robocode.AdvancedRobot robot;
|
||||
private double distanceTraveled;
|
||||
jk.mega.dGun.DCWave(robocode.AdvancedRobot);
|
||||
public boolean test();
|
||||
void setSegmentations(jk.mega.dGun.DCRobotState);
|
||||
private boolean hasArrived();
|
||||
private double currentGF();
|
||||
private double currentPreciseGF();
|
||||
private double getBearing(jk.tree.KDTree, double[], int, boolean, int);
|
||||
private double getBearingGaussian(jk.tree.KDTree, double[], int, boolean);
|
||||
public double mostVisitedBearing();
|
||||
private double getASBearing();
|
||||
}
|
||||
```
|
||||
|
||||
### `jk.mega.dMove.Scan` (declared at the end of `DrussMoveGT.java`; only `.class` shipped)
|
||||
```text
|
||||
class jk.mega.dMove.Scan implements java.lang.Cloneable {
|
||||
static final double latVelWeight, advVelWeight, bftWeight, forwardWallWeight,
|
||||
reverseWallWeight, lastVelWeight, accelWeight, timeSinceDecelWeight,
|
||||
timeSinceDirChangeWeight, distLast10Weight, timeWeight1, timeWeight2, timeWeight3,
|
||||
ASlatVelWeight, ASadvVelWeight, ASbftWeight, ASforwardWallWeight,
|
||||
ASreverseWallWeight, ASlastVelWeight, ASaccelWeight, AStimeSinceDecelWeight,
|
||||
AStimeSinceDirChangeWeight, ASdistLast10Weight, AStimeWeight1, AStimeWeight2, AStimeWeight3;
|
||||
double latVel, advVel, bft, accel, forwardWall, reverseWall, lastVel,
|
||||
timeSinceDecel, timeSinceDirChange, distLast10;
|
||||
int pointInTime, index;
|
||||
jk.mega.dMove.Scan();
|
||||
public java.lang.Object clone();
|
||||
double[] location();
|
||||
double[] ASLocation();
|
||||
}
|
||||
```
|
||||
|
||||
> Note: `DCWave`, `JKDCUtils`, etc. are declared inline at the bottom of
|
||||
> `DrussGunDC.java` in the decompiled tree, so their *source* does exist there;
|
||||
> the *standalone* `.java` files do not. Either way the jar's bytecode is
|
||||
> authoritative for signatures.
|
||||
|
||||
---
|
||||
|
||||
## 5. Remaining work to go from "compiles" to "runs a battle"
|
||||
|
||||
Everything below is now the *inverse* problem: the API is complete, but the
|
||||
**engine semantics** behind it are not implemented.
|
||||
|
||||
| # | Item | Status | Notes |
|
||||
|---|---|---|---|
|
||||
| 1 | Coordinate rotation at the boundary | **[MEASURED]** | Pure function; conversion `trRad = PI/2 - classicRad` already validated to 0.000–0.001° against recorded DrussGT motion (`tools/fixtures/DRUSSGT_FIXTURES.md`). Implemented in `TankRoyaleBridge`. Cheap. |
|
||||
| 2 | Async tick ↔ synchronous `execute()` bridge | **[MEASURED]** | Implemented in `ClassicPeer.provideTurn/awaitExecutedTurns`; proven by the 200-tick smoke run. Real integration still needs a Tank Royale Java websocket/JSON client to *produce* those ticks. |
|
||||
| 3 | `RobocodeFileOutputStream` engine dependency | **[MEASURED]** | `javap -c` shows it resolves `IThreadManagerBase` via `ContainerBase`. Outside the engine it throws `RobotException` and kills the bot thread. Reached only from `DrussGT.contain()`. Fix: register a no-op `IThreadManagerBase` in `ContainerBase`, or replace `contain`'s writer (can't — bot is unmodified), or accept that any bot exception is fatal. This bit us in testing. |
|
||||
| 4 | Classic motion model (`setAhead` distance vs TR speed) | **[MEASURED] + [ESTIMATE]** | DrussGT calls `getDistanceRemaining()` (ours) and `setAhead(d)`; Tank Royale wants a *target speed* + *turn rate*. The bridge must integrate a classic motion model (`ACCELERATION=1`, `DECELERATION=2`, `MAX_VELOCITY=8`, turn rate `10-0.75·|v|`) in front of TR, or approximate. DrussGT *also* has its own `MovePredictor` using classic accel/decel, so divergence is baked in regardless. [ESTIMATE: 1–3 days] |
|
||||
| 5 | Event synthesis & order | **[MEASURED]** | All classic event constructors are public and confirmed by javap. `ClassicPeer.dispatch` already routes ScannedRobot/HitByBullet/BulletHit/BulletHitBullet/BulletMissed/HitRobot/HitWall/RobotDeath/Win/Death/Status/SkippedTurn/Custom. Still to do: emit them from TR events and honour classic priority ordering. |
|
||||
| 6 | `getGunHeat()` / gun cooling | **[MEASURED]** | `DrussMoveGT` tracks enemy gun heat itself from `getGunCoolingRate()` (classic value 0.1). `ClassicPeer` returns a configurable value; the bridge must model *our* heat with the same decrement so `getGunHeat`/`getGunTurnRemaining` are consistent. |
|
||||
| 7 | `Bullet` identity / own-bullet tracking | **[ESTIMATE]** | DrussGT keeps `Bullet` objects from `setFireBullet` and matches them in `onBulletHit`/`HitByBulletEvent.getBullet()` (uses `equals`). The bridge must map TR bullet ids to stable classic `Bullet` instances. |
|
||||
| 8 | Firing translation | **[ESTIMATE]** | `setFire(power)`/`setFireBullet` → TR fire command; `setFireBullet` must return a `Bullet` synchronously. |
|
||||
| 9 | Rounds / counts | **[ESTIMATE]** | `getRoundNum`, `getNumRounds`, `getOthers`, `getNumSentries` map to TR round info; `DrussMoveGT` gates flattener learning on `getRoundNum() > 1 / > 4 / < 15`, so these must be real, not constants. |
|
||||
| 10 | Radar / scan cadence | **[ESTIMATE]** | Classic `ScannedRobotEvent` cadence is a swept radar; TR provides its own scan events. Must be synthesised with correct `bearing`/`distance` so `DrussMoveGT.onScannedRobot` sees true geometry. |
|
||||
| 11 | `onPaint` / AWT | **[MEASURED]** | `ClassicPeer.getGraphics()` returns `null`; DrussGT's `onPaint(Graphics2D)` (157 lines, debug only) can be skipped. `setColors`/`Color` are accepted and ignored. |
|
||||
| 12 | `getAllEvents()` on death | **[ESTIMATE]** | `DrussGT.contain`/`onDeath` replays events; peer already returns the turn's accumulated events. |
|
||||
| 13 | Class-loading topology | **[MEASURED]** | Must load DrussGT with a child `URLClassLoader` whose parent holds `robocode.jar`, so `IBasicRobot` is the same class. Implemented in `BotHost`. |
|
||||
| 14 | Physics fidelity | **[ESTIMATE]** | Even with a perfect bridge, TR's `maxTurn = 10 - 0.75·|speed|`, ±1 ramp and bullet model differ from classic. Exact trajectory match is impossible; aim for "same decisions, diverging trajectories". |
|
||||
|
||||
There are **no remaining API unknowns**: the compiler resolved everything, and
|
||||
the smoke test exercised the full event → decision → intent path.
|
||||
|
||||
---
|
||||
|
||||
## 6. Repo layout & how to run
|
||||
|
||||
```text
|
||||
tools/robocode_shim/
|
||||
README.md this report
|
||||
build.sh compile the shim (ClassicPeer/BotHost/Bridge/SmokeTest)
|
||||
compile_drussgt.sh ACCEPTANCE TEST: javac the real DrussGT sources
|
||||
run_smoke.sh load + drive the real jar, assert intents
|
||||
.gitignore out/ and *.jar never committed
|
||||
src/robocode_shim/
|
||||
ClassicPeer.java IAdvancedRobotPeer impl + execute() tick bridge
|
||||
BotHost.java URLClassLoader + setPeer + run() thread
|
||||
TankRoyaleBridge.java angle conversion + documented tick contract (skeleton)
|
||||
SmokeTest.java the runtime proof
|
||||
out/ build output (gitignored)
|
||||
```
|
||||
|
||||
```bash
|
||||
export ROBOCODE_JAR=/tmp/robocode/install/libs/robocode.jar
|
||||
export DRUSSGT_JAR=/tmp/drussgt/DrussGT.jar
|
||||
./build.sh
|
||||
./compile_drussgt.sh
|
||||
./run_smoke.sh
|
||||
```
|
||||
|
||||
## 7. Third-party jars
|
||||
|
||||
Per the task constraints, **no third-party jar is copied into the repo.**
|
||||
`robocode.jar` (Robocode 1.9.5.5) stays at
|
||||
`/tmp/robocode/install/libs/robocode.jar` and `DrussGT.jar` at
|
||||
`/tmp/drussgt/DrussGT.jar`; the scripts reference them via `ROBOCODE_JAR` /
|
||||
`DRUSSGT_JAR`. `tools/robocode_shim/.gitignore` additionally blocks `*.jar` and
|
||||
`out/` in case a jar is ever dropped here by mistake.
|
||||
|
||||
## 8. Gotchas discovered (for whoever wires the bridge)
|
||||
|
||||
* **`ScannedRobotEvent` constructor argument order is
|
||||
`(name, energy, bearing, distance, heading, velocity)`** — *not* the
|
||||
javadoc-intuitive heading-before-bearing ordering. Confirmed both
|
||||
empirically and in the javadoc. Wrong order silently yields `distance=0`,
|
||||
`BFT=0`, an immediate KD-tree insert and an NPE inside `EnemyMoves.predict`.
|
||||
* `javap -c` omits the owner on `this`-calls; API extraction must include
|
||||
unqualified `// Method name:desc` references from the bot's own class.
|
||||
* `DrussMoveGT`/`EnemyMoves` keep **static** KD-trees; the shim must not load
|
||||
two copies of the bot in one JVM if that state is to stay meaningful.
|
||||
Reference in New Issue
Block a user