10723ab5f3e77aeeb5f6139fdf3bc234ce87d028
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
17c99f542f |
feat(fixtures): closed-loop DrussGT captures from real Tank Royale battles
The classic captures were OPEN-LOOP: replayed DrussGT never dodged OUR
bullets. These come from real TR battles through the working Java bridge, so
the recording contains genuine reactions to ModularBot's live fire. The
open-loop caveat is gone (perfect-information remains).
PRIMARY RESULT - the boss beats us badly. DrussGT 1447 - ModularBot 300 over
15 rounds, ModularBot winning only round 5 (DrussGT died at tick 1893). Rounds
are long, not truncated: mean 1335 ticks, ModularBot got off 1134 shots.
ModularBot 1134 shots / 60 hits = 5.3% real hit rate
DrussGT 1400 shots / 169 hits = 12.1% real hit rate
So DrussGT's gun is ~2.3x more accurate than our entire rack, on top of far
better movement. That is the number to move.
Also captured: shield-on variant (DrussGT 939-287, 9/10 - ModularBot takes
round 1 to the known shield warm-up), and vs SpinBot 1175-0, Crazy 1080-1,
Corners 1659-0. 20,026 + 12,629 + 10,824 + 11,507 + 2,575 ticks.
Movement statistics match the classic set within ~0.04 on the perpendicular
and radial fractions, so this is the same wave surfer in TR physics:
TR vs modularbot: perp 0.967, radial 0.001, 52.8% at full speed,
reversing 46.3%, median range 464 px.
CLOSED LOOP PROVEN, not asserted. ModularBot's fire is a heat-limited near
metronome (median interval 14 ticks), which gives a usable exogenous clock:
- event-locked |delta heading| oscillates 0.96 -> 2.69 deg about a 1.47 deg
mean with the fire period, almost every lag outside the 95% band of a
400-iteration phase-shuffled null;
- cross-correlation of |delta heading| against the fire impulse peaks at
r = +0.111, lag 12 ticks, permutation p = 0.005 (null peak mean +0.016);
- OWN-FIRE CONTROL is flat, so the oscillation is enemy-driven rather than
an internal cadence;
- range response is weak (~3 px over 30 ticks, near noise) and is therefore
NOT claimed, and per-bullet dodging is not claimed either because the
bullet detector (shield) is off.
CAVEATS: still perfect-information (observer gives true positions every tick,
unlike the live bot's stale between-scan WorldState) so these remain optimistic
vs live play; and they are open-loop AT REPLAY TIME - 'closed_loop' describes
the capture, not a later replay. TR conversion residual is ~1.5 deg mean
because the TR server moves along the pre-turn heading, vs 0.000 deg for the
classic captures.
Adds analyze_closed_loop.py (PSTH event-locking, phase-shuffle permutation
null, cross-correlation, own-fire control) and per-round result sidecars.
|
||
|
|
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. |
||
|
|
e0bfa9b5e1 |
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. |