From 54b0a3a0c704bf72c22a71e732373ce29497835a Mon Sep 17 00:00:00 2001 From: "Flemming N. Larsen" Date: Mon, 31 Aug 2026 23:06:01 +0200 Subject: [PATCH 1/3] Fix EVT-004 negative evidence for variable deaths --- .../RoundOutcomeEventsConformanceTest.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java index c03d992..4e13b5c 100644 --- a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java +++ b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java @@ -41,12 +41,16 @@ void testEVT004_IntegrationPositive_OwnDeathReachesTheDeathHandler() { } @Test - @DisplayName("EVT-004 negative: each destruction reaches onDeath once, not repeatedly") - void testEVT004_IntegrationNegative_DeathHandlerDoesNotRepeatForOneDestruction() { - assertOnBothEngines(ROBOT, (outcome, engine) -> - assertTrue(outcome.countOf("Death!") == configuredRounds(), - () -> "onDeath was reported other than once per destroyed robot on " + engine - + " (" + outcome.summary() + ")")); + @DisplayName("EVT-004 negative: a participant's onDeath is not reported more than once per round") + void testEVT004_IntegrationNegative_DeathHandlerDoesNotRepeatWithinRounds() { + assertOnBothEngines(ROBOT, (outcome, engine) -> { + for (String console : outcome.consoles()) { + int deaths = countIn(console, "Death!"); + assertTrue(deaths <= configuredRounds(), + () -> "a participant reported onDeath " + deaths + " times on " + engine + + ", more than once per configured round (" + outcome.summary() + ")"); + } + }); } @Test From 877791aff22b2df304e51d487b6fc74e1ce6b45e Mon Sep 17 00:00:00 2001 From: "Flemming N. Larsen" Date: Tue, 1 Sep 2026 19:54:57 +0200 Subject: [PATCH 2/3] Strengthen death event conformance evidence --- .../bridge/conformance/BattleOutcome.java | 27 ++++++++++++------- .../RobotDeathEventsConformanceTest.java | 13 +++++++++ .../RoundOutcomeEventsConformanceTest.java | 19 +++++-------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/BattleOutcome.java b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/BattleOutcome.java index 568bfe4..8c608d7 100644 --- a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/BattleOutcome.java +++ b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/BattleOutcome.java @@ -63,15 +63,24 @@ boolean everyConsoleContains(String marker) { int countOf(String marker) { int total = 0; for (String console : consoles) { - int from = 0; - while (true) { - int at = console.indexOf(marker, from); - if (at < 0) { - break; - } - total++; - from = at + marker.length(); - } + total += countIn(console, marker); + } + return total; + } + + /** How many times the marker appears in each participant's console. */ + List countsOf(String marker) { + List counts = new ArrayList<>(consoles.size()); + for (String console : consoles) { + counts.add(countIn(console, marker)); + } + return counts; + } + + private static int countIn(String text, String marker) { + int total = 0; + for (int from = 0; (from = text.indexOf(marker, from)) >= 0; from += marker.length()) { + total++; } return total; } diff --git a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RobotDeathEventsConformanceTest.java b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RobotDeathEventsConformanceTest.java index ddddec1..b31acd4 100644 --- a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RobotDeathEventsConformanceTest.java +++ b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RobotDeathEventsConformanceTest.java @@ -23,4 +23,17 @@ void testEVT014_IntegrationPositive_SurvivorReceivesAnotherRobotsDeath() { () -> "no survivor reported another robot's death on " + engine + " (" + outcome.summary() + ")")); } + + @Test + @DisplayName("EVT-014 negative: a survivor does not receive duplicate death notifications per round") + void testEVT014_IntegrationNegative_SurvivorDoesNotReceiveDuplicateDeathNotifications() { + assertOnBothEngines(ROBOT, SOURCE, (outcome, engine) -> { + for (int deaths : outcome.countsOf(OTHER_DEATH)) { + assertTrue(deaths <= configuredRounds(), + () -> "a participant received " + deaths + " other-robot death notifications on " + + engine + ", more than once per configured round (" + + outcome.summary() + ")"); + } + }); + } } diff --git a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java index 4e13b5c..ddaf3d1 100644 --- a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java +++ b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java @@ -44,8 +44,7 @@ void testEVT004_IntegrationPositive_OwnDeathReachesTheDeathHandler() { @DisplayName("EVT-004 negative: a participant's onDeath is not reported more than once per round") void testEVT004_IntegrationNegative_DeathHandlerDoesNotRepeatWithinRounds() { assertOnBothEngines(ROBOT, (outcome, engine) -> { - for (String console : outcome.consoles()) { - int deaths = countIn(console, "Death!"); + for (int deaths : outcome.countsOf("Death!")) { assertTrue(deaths <= configuredRounds(), () -> "a participant reported onDeath " + deaths + " times on " + engine + ", more than once per configured round (" + outcome.summary() + ")"); @@ -81,9 +80,11 @@ void testEVT011_IntegrationNegative_RoundCompletionIsNotReportedMoreThanOncePerR // the event once per participant to every participant would satisfy the positive // test above and fail here, because it would double (or worse) the count below // rather than merely clear a lower bound. - for (String console : outcome.consoles()) { - int rounds = countIn(console, "RoundEnded!"); - int battles = countIn(console, "BattleEnded!"); + var roundCounts = outcome.countsOf("RoundEnded!"); + var battleCounts = outcome.countsOf("BattleEnded!"); + for (int participant = 0; participant < outcome.consoles().size(); participant++) { + int rounds = roundCounts.get(participant); + int battles = battleCounts.get(participant); assertTrue(battles == 1, () -> "a participant reported the battle ending " + battles + " times on " + engine); @@ -93,12 +94,4 @@ void testEVT011_IntegrationNegative_RoundCompletionIsNotReportedMoreThanOncePerR } }); } - - private static int countIn(String text, String marker) { - int total = 0; - for (int from = 0; (from = text.indexOf(marker, from)) >= 0; from += marker.length()) { - total++; - } - return total; - } } From 813895b179a081ea6dec760b9e4af9687e60b5ae Mon Sep 17 00:00:00 2001 From: "Flemming N. Larsen" Date: Tue, 1 Sep 2026 20:03:04 +0200 Subject: [PATCH 3/3] Guard against duplicate win notifications --- .../RoundOutcomeEventsConformanceTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java index ddaf3d1..7d6d685 100644 --- a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java +++ b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RoundOutcomeEventsConformanceTest.java @@ -60,6 +60,18 @@ void testEVT012_IntegrationPositive_WinningARoundReachesTheWinHandler() { () -> "no robot reported winning on " + engine + " (" + outcome.summary() + ")")); } + @Test + @DisplayName("EVT-012 negative: a participant's win handler is not reported more than once per round") + void testEVT012_IntegrationNegative_WinHandlerDoesNotRepeatWithinRounds() { + assertOnBothEngines(ROBOT, (outcome, engine) -> { + for (int wins : outcome.countsOf("Win!")) { + assertTrue(wins <= configuredRounds(), + () -> "a participant reported onWin " + wins + " times on " + engine + + ", more than once per configured round (" + outcome.summary() + ")"); + } + }); + } + @Test @DisplayName("EVT-011: round and battle completion reach their handlers on both engines") void testEVT011_IntegrationPositive_RoundAndBattleCompletionReachTheirHandlers() {