diff --git a/.clue/id-ledger.yaml b/.clue/id-ledger.yaml index 2f91534b..25b5ae0f 100644 --- a/.clue/id-ledger.yaml +++ b/.clue/id-ledger.yaml @@ -1612,6 +1612,10 @@ events: - {id: AN-705, kind: numeric, state: reserved, prefix: AN, component: "705"} - {id: AN-704, kind: numeric, state: live, prefix: AN, component: "704"} - {id: AN-705, kind: numeric, state: live, prefix: AN, component: "705"} + - {id: CH-020, kind: numeric, state: reserved, prefix: CH, component: "20"} + - {id: ROUTE-013, kind: numeric, state: reserved, prefix: ROUTE, component: "13"} + - {id: ROUTE-013, kind: numeric, state: live, prefix: ROUTE, component: "13"} + - {id: CH-020, kind: numeric, state: live, prefix: CH, component: "20"} high-water: - {id: ADR-002, kind: numeric, state: reserved, prefix: ADR, component: "2"} - {id: AN-014, kind: numeric, state: reserved, prefix: AN, component: "14"} diff --git a/CHANGELOG.md b/CHANGELOG.md index 40bc7c0f..57949362 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Route legacy `Robot.stop(boolean)` overwrite calls to the matching Tank Royale API, preserving saved movement when the robot calls `stop(true)`. - Truncate legacy robot and team author metadata to the Bot API's 50-character limit so long author names do not prevent wrapped bots from starting. - Trim legacy robot and team author metadata in generated bot JSON so it matches the Bot API's canonical author identity and does not block bot startup. - Copy a robot's bundled data file when `getDataFile()` resolves it, and create an empty file when no bundled resource exists, matching Classic's file initialization behavior. diff --git a/compat-test/parity-registry.json b/compat-test/parity-registry.json index f215fdd6..a627e3f2 100644 --- a/compat-test/parity-registry.json +++ b/compat-test/parity-registry.json @@ -184319,6 +184319,12 @@ "id": "diagnosis-1", "owner": "bridge", "recorded_at": "2026-10-19T18:54:25Z" + }, + { + "cause": "melee-opponent-pool-contamination", + "id": "diagnosis-2", + "owner": "harness", + "recorded_at": "2026-10-19T19:00:51Z" } ], "identity": { @@ -184329,7 +184335,7 @@ "key": "meleerumble/jaybot.bots.Oddball_4.0.jar", "kind": "robot" }, - "latest_observation": "e15d719a76e461c3", + "latest_observation": "50ea318f51f84718", "observations": [ { "classic": { @@ -184638,9 +184644,387 @@ "status": "incomplete" } } + }, + { + "classic": { + "elapsed": 5.7, + "error_count": 384, + "error_signatures": [ + { + "exception": "java.lang.ArrayIndexOutOfBoundsException", + "origin": "amk.guns.Aristocles.prepare" + }, + { + "exception": "java.lang.ArrayIndexOutOfBoundsException", + "origin": "unknown" + }, + { + "exception": "java.lang.SecurityException", + "origin": "amk.ChumbaMini.saveData" + }, + { + "exception": "java.lang.SecurityException", + "origin": "unknown" + } + ], + "errors": [ + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.WinEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.SecurityException occurred on robocode.DeathEvent", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException", + "SYSTEM: java.lang.ArrayIndexOutOfBoundsException occurred on robocode.ScannedRobotEvent", + "java.lang.ArrayIndexOutOfBoundsException" + ], + "has_log": true, + "ok": true, + "score": 115687.0, + "scores": [ + 13361.0, + 13989.0, + 13637.0, + 9013.0, + 12296.0, + 7550.0, + 10773.0, + 11283.0, + 7084.0, + 16701.0 + ], + "selected": "jaybot.bots.Oddball 4.0" + }, + "completed_at": "2026-10-19T19:00:35Z", + "confirmation": null, + "delta_pct": -2.2, + "id": "50ea318f51f84718", + "manifest": { + "artifacts": { + "bot_api_jar": { + "path": "C:\\Code\\tank-royale\\bot-api\\java\\build\\libs\\robocode-tankroyale-bot-api-1.4.0.jar", + "sha256": "ab65c4d5cec1808adeb71375adae6d15341ae250def89a6c10fc9da879de0752" + }, + "bridge_api_jar": { + "path": "C:\\Code\\robocode-api-bridge\\robocode-api\\build\\libs\\robocode-api-0.5.0.jar", + "sha256": "154d52aa5024d135754acc0cf988877b31445dc5ed2606f1329ab314a8c5a703" + }, + "runner_jar": { + "path": "C:\\Code\\tank-royale\\runner\\examples\\lib\\robocode-tankroyale-runner.jar", + "sha256": "4f208f8047d0f0d49d119255fc713b042fb9c34fb1d01c585fcfa8730cb940fc" + }, + "wrapper_jar": { + "path": "C:\\Code\\robocode-api-bridge\\robots-wrapper\\build\\libs\\robots-wrapper-0.3.1.jar", + "sha256": "c1e9c32a7ec454fc722c4b7f0c3df38ec8fba510b9cacfceaf50d4cfcda0b0a4" + } + }, + "bridge_commit": "55de3fc5a191daadb83e455663ca4473bf6656c4", + "official_parameters": true, + "robocode_version": "1.11.1", + "tank_royale_commit": "d5a9e3acc4b65466ab8668c0d3884c3eb3b35621", + "threshold": 25.0 + }, + "retest": { + "cause": "classic-stop-overwrite-explicitly-rejected-by-bridge", + "diagnosis_id": "diagnosis-1", + "owner": "bridge", + "repair": "55de3fc" + }, + "setup": { + "height": 1000, + "opponent_pool": [ + { + "jar": "adt.Ar1_2.1.jar", + "sha256": "37857b3fe3d082f121da8dd8ca81a5ea58edcab65fad3c552d71dfa65f58b14e" + }, + { + "jar": "adt.Ar2_1.0.jar", + "sha256": "2adf54760aefa12c1e79667c064a9529b85aed2218e8a7457c335fb91153394e" + }, + { + "jar": "amk.ChumbaMini_0.2.jar", + "sha256": "5770270c23f9223e4df93f5455fcf461ecd30309fc318f3d53cbbdf93e133d6b" + }, + { + "jar": "amk.ChumbaWumba_0.3.jar", + "sha256": "f7594cdfa02419ae112013ec923387736a89840712162020ecb899e39d6127e9" + }, + { + "jar": "amk.Punbot.Punbot_0.01.jar", + "sha256": "44e10755757c7cb523ad7f6a746f5524f6a054fe7d047528da8330829284b389" + }, + { + "jar": "amk.ShizzleStiX.ShizzleStiX_0.6.jar", + "sha256": "3e93e50b7380b8b2f98ab1c30911bc56a98c6eb27ccb5125d1aa8cbbc5987850" + }, + { + "jar": "amk.jointstrike.JointStrike_0.2.jar", + "sha256": "3f45fd832f7bb6b6ceb3841c1ab108583ba11b49ba06cbde2f8e61637dc28297" + }, + { + "jar": "ap.Frederick_1.1.jar", + "sha256": "d7f1709e4ea064f0662f383c339f57390977292fdd921933adc36e305a375899" + }, + { + "jar": "apc.botM_3.0.jar", + "sha256": "9348a9b3c2e0f175bacf361dc3b20a0842f186c228564bcafd8b6d853d345d94" + }, + { + "jar": "ara.Shera_0.88.jar", + "sha256": "9474b68598ae5e71010b835259467b6dc35427e2826ace23e484a6a11160a040" + }, + { + "jar": "arthord.NanoSatanMelee_Beta.jar", + "sha256": "a0737bc6d25f6b89618c119a6df62ae89a44d9a2a7d662056dddefdde04f37fe" + }, + { + "jar": "axeBots.HataMoto_3.09.jar", + "sha256": "293a92846b40195a5de25c7379f72e0dcb08297f79d2b6fffd7d3a95b50afb2c" + } + ], + "opponents": [ + { + "jar": "adt.Ar1_2.1.jar", + "sha256": "37857b3fe3d082f121da8dd8ca81a5ea58edcab65fad3c552d71dfa65f58b14e" + }, + { + "jar": "adt.Ar2_1.0.jar", + "sha256": "2adf54760aefa12c1e79667c064a9529b85aed2218e8a7457c335fb91153394e" + }, + { + "jar": "amk.ChumbaMini_0.2.jar", + "sha256": "5770270c23f9223e4df93f5455fcf461ecd30309fc318f3d53cbbdf93e133d6b" + }, + { + "jar": "amk.ChumbaWumba_0.3.jar", + "sha256": "f7594cdfa02419ae112013ec923387736a89840712162020ecb899e39d6127e9" + }, + { + "jar": "amk.Punbot.Punbot_0.01.jar", + "sha256": "44e10755757c7cb523ad7f6a746f5524f6a054fe7d047528da8330829284b389" + }, + { + "jar": "amk.ShizzleStiX.ShizzleStiX_0.6.jar", + "sha256": "3e93e50b7380b8b2f98ab1c30911bc56a98c6eb27ccb5125d1aa8cbbc5987850" + }, + { + "jar": "amk.jointstrike.JointStrike_0.2.jar", + "sha256": "3f45fd832f7bb6b6ceb3841c1ab108583ba11b49ba06cbde2f8e61637dc28297" + }, + { + "jar": "ap.Frederick_1.1.jar", + "sha256": "d7f1709e4ea064f0662f383c339f57390977292fdd921933adc36e305a375899" + }, + { + "jar": "apc.botM_3.0.jar", + "sha256": "9348a9b3c2e0f175bacf361dc3b20a0842f186c228564bcafd8b6d853d345d94" + } + ], + "participants": 10, + "rounds": 35, + "width": 1000 + }, + "source_identity": { + "classic_selected": "jaybot.bots.Oddball 4.0", + "division": "meleerumble", + "jar": "jaybot.bots.Oddball_4.0.jar", + "jar_sha256": "12d0d549ecee200b1bb2a0b9dcd0774ca1d53a2b562c7f5d0bbece999e1c09ca", + "key": "meleerumble/jaybot.bots.Oddball_4.0.jar", + "kind": "robot" + }, + "status": "DISCREPANCY (errors)", + "tank_royale": { + "bridge_only_signatures": null, + "elapsed": 34.2, + "error_count": 30, + "error_signatures": [ + { + "exception": "java.lang.SecurityException", + "origin": "amk.ChumbaMini.saveData" + }, + { + "exception": "java.lang.SecurityException", + "origin": "unknown" + } + ], + "errors": [ + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time.", + "java.lang.SecurityException: You may only have 5 streams open at a time." + ], + "has_log": true, + "ok": true, + "score": 113137.0, + "scores": [ + 18209.0, + 13575.0, + 13531.0, + 13373.0, + 12809.0, + 10913.0, + 9872.0, + 8754.0, + 7985.0, + 4116.0 + ], + "skipped": null, + "skipped_turn_telemetry": { + "events": [ + { + "bot_id": 1, + "round": 1, + "turn": 1 + }, + { + "bot_id": 2, + "round": 1, + "turn": 1 + }, + { + "bot_id": 3, + "round": 1, + "turn": 1 + }, + { + "bot_id": 6, + "round": 1, + "turn": 1 + }, + { + "bot_id": 8, + "round": 1, + "turn": 1 + }, + { + "bot_id": 10, + "round": 1, + "turn": 1 + } + ], + "status": "captured" + } + } } ], - "status": "DISCREPANCY (outcome)" + "status": "DISCREPANCY (errors)" }, "meleerumble/jcs.Seth_1.8.jar": { "diagnosis_events": [ diff --git a/compat-test/parity-registry.md b/compat-test/parity-registry.md index 85cc3995..b746de4c 100644 --- a/compat-test/parity-registry.md +++ b/compat-test/parity-registry.md @@ -7,9 +7,9 @@ This generated table is the reviewed index of the append-only observations in `p | Status | Subjects | |---|---:| | CONFIRMED (score) | 233 | -| DISCREPANCY (errors) | 114 | +| DISCREPANCY (errors) | 115 | | DISCREPANCY (no score) | 9 | -| DISCREPANCY (outcome) | 67 | +| DISCREPANCY (outcome) | 66 | | MATCHED (failure) | 49 | | MATCHED (score noise) | 139 | | PASS | 712 | @@ -168,7 +168,7 @@ This generated table is the reviewed index of the append-only observations in `p | meleerumble/jab.DiamondStealer_5.jar | meleerumble | robot | DISCREPANCY (errors) | fae68a2757f1e40a | melee-opponent-pool-contamination | harness | | meleerumble/jangs.ns51_1.0.jar | meleerumble | robot | DISCREPANCY (errors) | f6f5b19abc44f9b5 | melee-opponent-pool-contamination | harness | | meleerumble/jaybot.adv.bots.JayBot_2.0.jar | meleerumble | robot | DISCREPANCY (errors) | 737e8e86d63ba6d5 | melee-opponent-pool-contamination | harness | -| meleerumble/jaybot.bots.Oddball_4.0.jar | meleerumble | robot | DISCREPANCY (outcome) | e15d719a76e461c3 | classic-stop-overwrite-explicitly-rejected-by-bridge | bridge | +| meleerumble/jaybot.bots.Oddball_4.0.jar | meleerumble | robot | DISCREPANCY (errors) | 50ea318f51f84718 | melee-opponent-pool-contamination | harness | | meleerumble/jcs.Seth_1.8.jar | meleerumble | robot | DISCREPANCY (errors) | 9b9138a2f5fc680b | melee-opponent-pool-contamination | harness | | meleerumble/jdw.Hurricane_1.4.jar | meleerumble | robot | DISCREPANCY (errors) | 0c2ae0cbac2e6851 | melee-opponent-pool-contamination | harness | | meleerumble/jekl.StoneGhost_0.7.jar | meleerumble | robot | DISCREPANCY (errors) | 8de300b1a4e97d53 | melee-opponent-pool-contamination | harness | diff --git a/docs/capabilities/CAP-008-call-routing-fidelity/criteria.md b/docs/capabilities/CAP-008-call-routing-fidelity/criteria.md index 1c4ae8fe..297c990c 100644 --- a/docs/capabilities/CAP-008-call-routing-fidelity/criteria.md +++ b/docs/capabilities/CAP-008-call-routing-fidelity/criteria.md @@ -48,7 +48,7 @@ Feature: Bot API call-routing fidelity Then each value comes from the corresponding Bot API value, converted into Robocode's frame And no getter returns a neighbouring component's value - @ROUTE-005 + @ROUTE-005 @retired Scenario: Stopping, resuming, and the independence flags route individually Test-type: Unit Given a robot that stops, resumes, or sets one independence flag @@ -56,6 +56,14 @@ Feature: Bot API call-routing fidelity Then it reaches that call alone, carrying its argument rather than an assumed value And an unsupported variant is refused rather than silently downgraded + @ROUTE-013 + Scenario: Stopping, resuming, overwrite, and independence flags route individually + Test-type: Unit + Given a robot that stops with an overwrite flag, resumes, or sets one independence flag + When the call is routed + Then it reaches the matching Bot API call alone, carrying its argument rather than an assumed value + And stop(overwrite) reaches IBot.stop(overwrite) with the boolean preserved + @ROUTE-006 Scenario: A queued command is queued, not executed Test-type: Unit diff --git a/docs/capabilities/CAP-008-call-routing-fidelity/design.md b/docs/capabilities/CAP-008-call-routing-fidelity/design.md index 07fef2c1..4f4ef799 100644 --- a/docs/capabilities/CAP-008-call-routing-fidelity/design.md +++ b/docs/capabilities/CAP-008-call-routing-fidelity/design.md @@ -32,6 +32,8 @@ Not "the call succeeded". Each names the Bot API method reached and the argument That negative half is doing most of the work. A method that routes to the wrong neighbour still compiles, still runs, and still produces a battle; only an assertion that the neighbour was untouched distinguishes it. +`Robot.stop(boolean)` preserves its overwrite flag by calling the matching `IBot.stop(boolean)` overload. The routing tests check the `true` flag reaches that overload and that no neighbouring stop or resume call is made. + ## Completeness, and why it is enforced rather than reviewed `PeerSurfaceCoverageTest` walks the peer interfaces reflectively from `ITeamRobotPeer` and `IJuniorRobotPeer`, and fails naming any method no routing test calls. diff --git a/robocode-api/src/main/java/dev/robocode/tankroyale/bridge/BotPeer.java b/robocode-api/src/main/java/dev/robocode/tankroyale/bridge/BotPeer.java index 3f74b366..413e41d3 100644 --- a/robocode-api/src/main/java/dev/robocode/tankroyale/bridge/BotPeer.java +++ b/robocode-api/src/main/java/dev/robocode/tankroyale/bridge/BotPeer.java @@ -806,13 +806,7 @@ public void rescan() { @Override public void stop(boolean overwrite) { log("stop()"); - if (overwrite) { - // flemming-n-larsen: I don't expect any bots to use this functionality, and hence it is not supported (yet) - // in Robocode Tank Royale. - throw new UnsupportedOperationException( - "stop(overwrite=true) is unsupported. Contact Robocode Tank Royale author for support"); - } - bot.stop(); + bot.stop(overwrite); } @Override diff --git a/robocode-api/src/test/java/dev/robocode/tankroyale/bridge/StandardPeerRoutingTest.java b/robocode-api/src/test/java/dev/robocode/tankroyale/bridge/StandardPeerRoutingTest.java index dad1aeb5..264d3ce6 100644 --- a/robocode-api/src/test/java/dev/robocode/tankroyale/bridge/StandardPeerRoutingTest.java +++ b/robocode-api/src/test/java/dev/robocode/tankroyale/bridge/StandardPeerRoutingTest.java @@ -7,11 +7,10 @@ import static java.lang.Math.toRadians; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** - * Acceptance evidence for ROUTE-005 — the {@code IStandardRobotPeer} surface: stopping, + * Acceptance evidence for ROUTE-013 — the {@code IStandardRobotPeer} surface: stopping, * resuming, turning the radar, and the three independence flags. * * The flags are the interesting part. Each decouples one component from another's rotation, @@ -33,10 +32,11 @@ void setUp() { } @Test - @DisplayName("ROUTE-005 positive: stopping and resuming route to their Bot API calls") - void testROUTE005_UnitPositive_RoutesStopAndResume() { + @DisplayName("ROUTE-013 positive: stopping and resuming route to their Bot API calls") + void testROUTE013_UnitPositive_RoutesStopAndResume() { peer.stop(false); assertTrue(bot.called("stop"), bot.names()); + assertEquals(Boolean.FALSE, bot.onlyCall("stop").args[0]); bot.clear(); peer.resume(); @@ -44,26 +44,34 @@ void testROUTE005_UnitPositive_RoutesStopAndResume() { } @Test - @DisplayName("ROUTE-005 negative: stop with overwrite is refused rather than silently downgraded") - void testROUTE005_UnitNegative_RefusesStopWithOverwriteRatherThanIgnoringIt() { - // Tank Royale has no equivalent. Refusing is the honest answer: quietly treating it as - // an ordinary stop would discard the robot's saved movement state, and the robot would - // resume into a different action than the one it stopped. - assertThrows(UnsupportedOperationException.class, () -> peer.stop(true)); - assertFalse(bot.called("stop"), "the unsupported call must not reach the bot: " + bot.names()); + @DisplayName("ROUTE-013 positive: stop overwrite reaches the Bot API with its argument") + void testROUTE013_UnitPositive_RoutesStopOverwriteArgument() { + peer.stop(true); + + assertEquals(Boolean.TRUE, bot.onlyCall("stop").args[0]); + } + + @Test + @DisplayName("ROUTE-013 negative: stop overwrite does not call a neighboring Bot API method") + void testROUTE013_UnitNegative_DoesNotCallAnotherStopAction() { + peer.stop(true); + + assertEquals(1, bot.calls().size(), bot.names()); + assertEquals("stop", bot.calls().get(0).name); + assertEquals(Boolean.TRUE, bot.calls().get(0).args[0]); } @Test - @DisplayName("ROUTE-005 positive: turning the radar reaches the radar call in degrees") - void testROUTE005_UnitPositive_RoutesRadarTurnInDegrees() { + @DisplayName("ROUTE-013 positive: turning the radar reaches the radar call in degrees") + void testROUTE013_UnitPositive_RoutesRadarTurnInDegrees() { peer.turnRadar(toRadians(120)); assertEquals(120.0, bot.onlyCall("turnRadarRight").doubleArg(0), EPSILON); } @Test - @DisplayName("ROUTE-005 positive: each independence flag reaches its own Bot API setter") - void testROUTE005_UnitPositive_RoutesEachIndependenceFlagToItsOwnSetter() { + @DisplayName("ROUTE-013 positive: each independence flag reaches its own Bot API setter") + void testROUTE013_UnitPositive_RoutesEachIndependenceFlagToItsOwnSetter() { peer.setAdjustGunForBodyTurn(true); assertEquals(true, bot.onlyCall("setAdjustGunForBodyTurn").args[0]); @@ -77,8 +85,8 @@ void testROUTE005_UnitPositive_RoutesEachIndependenceFlagToItsOwnSetter() { } @Test - @DisplayName("ROUTE-005 negative: setting one independence flag sets no other") - void testROUTE005_UnitNegative_SettingOneIndependenceFlagLeavesTheOthersAlone() { + @DisplayName("ROUTE-013 negative: setting one independence flag sets no other") + void testROUTE013_UnitNegative_SettingOneIndependenceFlagLeavesTheOthersAlone() { peer.setAdjustRadarForGunTurn(true); assertFalse(bot.called("setAdjustGunForBodyTurn"), bot.names()); @@ -86,8 +94,8 @@ void testROUTE005_UnitNegative_SettingOneIndependenceFlagLeavesTheOthersAlone() } @Test - @DisplayName("ROUTE-005 positive: the flag value is passed through rather than assumed true") - void testROUTE005_UnitPositive_PassesTheFlagValueThroughRatherThanAssumingTrue() { + @DisplayName("ROUTE-013 positive: the flag value is passed through rather than assumed true") + void testROUTE013_UnitPositive_PassesTheFlagValueThroughRatherThanAssumingTrue() { // A routing that ignored its argument would pass every test that only ever sets true, // and would leave a robot unable to turn the independence off again. peer.setAdjustGunForBodyTurn(false); @@ -96,8 +104,8 @@ void testROUTE005_UnitPositive_PassesTheFlagValueThroughRatherThanAssumingTrue() } @Test - @DisplayName("ROUTE-005 positive: reading an independence flag reads the matching Bot API flag") - void testROUTE005_UnitPositive_ReadsEachIndependenceFlagFromItsOwnSource() { + @DisplayName("ROUTE-013 positive: reading an independence flag reads the matching Bot API flag") + void testROUTE013_UnitPositive_ReadsEachIndependenceFlagFromItsOwnSource() { bot.returning("isAdjustGunForBodyTurn", true) .returning("isAdjustRadarForGunTurn", false) .returning("isAdjustRadarForBodyTurn", true);