Files
SirRoboGarage/tools/robocode_shim/src/robocode_shim/SmokeTest.java
T
SirStone 8e2be6a4c6 feat(tools): the real DrussGT now plays and wins Tank Royale battles
The unmodified DrussGT.jar connects, wave-surfs, fires and beats every
adversary we have. 5 rounds each, all rounds won:
  SpinBot 542-16, Corners 823-4, Crazy 604-0, RamFire 900-0,
  ModularBot 506-72.

It is genuinely surfing, not drifting or stalling. Movement statistics
against the classic captures, same metrics, same analyzer:

  opp        perp TR/classic   reversing TR/cl   median range TR/cl
  SpinBot    0.967 / 0.962     0.466 / 0.464     462 / 402
  Corners    0.894 / 0.920     0.462 / 0.419     520 / 504
  Crazy      0.841 / 0.829     0.527 / 0.439     370 / 378
  RamFire    0.762 / 0.651     0.533 / 0.417     326 / 283
  ModularBot 0.956 / -         0.466 / -         453 / -

Every round starts moving within 3-11 ticks and runs at 75-82% full speed.

Implemented: the classic remaining-quantity motion model (delegated to the
TR Bot's own Nat-Pavasant model - identical constants: accel 1, decel -2,
max 8, body turn 10-0.75|v|, gun 20, radar 45 - so getDistanceRemaining and
getTurnRemaining are exactly self-consistent with what is emulated); event
synthesis with classic ordering; bullet identity via object identity; gun
heat; rounds; radar cadence; firing translation; and the ThreadManager
landmine is killed by installing a no-op IThreadManagerBase in
ContainerBase.instance (verified: without it 'RobotException: ThreadManager
cannot be null!' kills the bot thread; with it the write succeeds).

Also adds TrBattleCapture, an observer that dumps per-tick state in the SAME
JSONL fixture format as the classic capture, so legacy bots can be captured
from Tank Royale battles too.

HONEST DIVERGENCES (README section 5.9): the TR server moves along the
PRE-turn heading and then turns, while classic aligns displacement with the
POST-turn heading, so the analyzer's conversion error is ~1.5 deg rather than
0.000 deg; distanceRemaining decrements by target speed rather than actual
distance; collision clamping differs; BulletMissedEvent can fire less often
because age-expiry has no TR event; bulletId is a local temp id;
StatusEvent/onPaint/SkippedTurnEvent are never delivered. Physics fidelity
diverges by construction - expect to retune.

EnergyDomeWorker (the bullet shield) is off by default: its precise
bullet-detection warm-up makes round 1 up to 79% stationary vs 35% in
classic. Rounds 2+ match classic closely with it on, but the pure surfer is
the consistent path. DRUSSGT_SHIELD=1 re-enables it.

Jars remain out of git.
2026-09-21 00:29:37 +02:00

92 lines
4.0 KiB
Java

package robocode_shim;
import robocode.ScannedRobotEvent;
import java.io.File;
import java.util.Collections;
/**
* Runtime proof for APPROACH (B): instantiate the real DrussGT from its jar,
* setPeer(), run its own run() loop, feed it synthetic ScannedRobotEvents and
* observe that it emits movement/fire intents through ClassicPeer.
*
* This is an architecture spike: there is no physics here and the peer's world
* state does not integrate the intents. If the bot starts, survives its
* constructor (DrussMoveGT/MeleeRadar/DrussGunDC/EnergyDomeWorker) and produces
* non-zero setAhead/setTurnRight intents, the seam is proven operational.
*/
public class SmokeTest {
public static void main(String[] args) throws Exception {
ThreadManagerFix.install();
File jar = new File(args.length > 0 ? args[0] : "/tmp/drussgt/DrussGT.jar");
File work = new File(args.length > 1 ? args[1] : "/tmp/shim_smoke");
if (!work.exists()) work.mkdirs();
System.out.println("DrussGT jar: " + jar + " (" + jar.length() + " bytes)");
BotHost host = new BotHost(jar, work);
host.disableShield(); // run the pure surfer path (skip EnergyDome shield)
host.start();
int ticks = 200;
int movementIntentTicks = 0;
double firstMove = Double.NaN, firstTurn = Double.NaN;
int fireRequests = 0;
boolean botSurvived = true;
for (int t = 0; t < ticks; t++) {
ClassicPeer p = host.peer;
p.time = t;
p.x = 400;
p.y = 300;
p.energy = 100 - (t * 0.001);
p.bodyHeading = 0.0;
p.gunHeading = 0.0;
p.radarHeading = 0.0;
p.gunHeat = Math.max(0, 3.0 - t * 0.1);
p.distanceRemaining = 0;
// A moving, orbiting enemy: enough feature variation that the
// KD-tree in EnemyMoves does not see degenerate duplicate points.
double theta = Math.toRadians(t * 7.0);
double bearing = theta; // bot heading is 0
double distance = 300.0 + 80.0 * Math.sin(theta / 2.3);
double enemyHeading = theta + Math.PI / 2; // perpendicular (surfer)
double enemyEnergy = 100.0 - 0.1 * (t % 10);
ScannedRobotEvent scan = new ScannedRobotEvent(
"Enemy", enemyEnergy, bearing, distance, enemyHeading, 8.0);
if (!host.tick(Collections.singletonList(scan))) { botSurvived = false; break; }
if (p.lastMove != 0 || p.lastTurnBody != 0) {
if (movementIntentTicks == 0) { firstMove = p.lastMove; firstTurn = p.lastTurnBody; }
movementIntentTicks++;
}
if (!Double.isNaN(p.lastFirePower)) {
fireRequests++;
p.lastFirePower = Double.NaN;
}
}
System.out.println("ticks executed : " + host.peer.getExecutedTurns());
System.out.println("bot thread survived : " + botSurvived);
if (host.getBotError() != null) {
System.out.println("bot thread error : " + host.getBotError());
}
System.out.println("ticks with a move intent : " + movementIntentTicks);
System.out.println("first (setAhead,setTurnRR): (" + firstMove + ", " + firstTurn + ")");
System.out.println("fire requests : " + fireRequests);
System.out.println("last captured intents : move=" + host.peer.lastMove
+ " turnBody=" + host.peer.lastTurnBody
+ " turnGun=" + host.peer.lastTurnGun
+ " turnRadar=" + host.peer.lastTurnRadar);
System.out.println("bot thread alive : " + host.getThread().isAlive());
host.stop();
boolean ok = movementIntentTicks > 0 && botSurvived;
System.out.println(ok ? "SMOKE TEST: OK (real DrussGT drove through the peer seam)"
: "SMOKE TEST: no movement intents observed");
System.exit(ok ? 0 : 1);
}
}