8e2be6a4c6
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.
92 lines
4.0 KiB
Java
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);
|
|
}
|
|
}
|