Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 78 additions & 2 deletions .clue/id-ledger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ counters:
ARCH: "3"
C: "7"
CAP: "8"
CH: "3"
CH: "4"
CRIT: "8"
DES: "8"
EVT: "13"
G: "2"
IDR: "2"
IDR: "3"
OQ: "2"
P: "1"
PDR: "1"
Expand Down Expand Up @@ -207,6 +208,11 @@ entries:
state: live
prefix: CH
component: "3"
- id: CH-004
kind: numeric
state: live
prefix: CH
component: "4"
- id: CRIT-001
kind: numeric
state: live
Expand Down Expand Up @@ -287,6 +293,71 @@ entries:
state: live
prefix: DES
component: "8"
- id: EVT-001
kind: numeric
state: live
prefix: EVT
component: "1"
- id: EVT-002
kind: numeric
state: live
prefix: EVT
component: "2"
- id: EVT-003
kind: numeric
state: retired
prefix: EVT
component: "3"
- id: EVT-004
kind: numeric
state: live
prefix: EVT
component: "4"
- id: EVT-005
kind: numeric
state: live
prefix: EVT
component: "5"
- id: EVT-006
kind: numeric
state: live
prefix: EVT
component: "6"
- id: EVT-007
kind: numeric
state: live
prefix: EVT
component: "7"
- id: EVT-008
kind: numeric
state: live
prefix: EVT
component: "8"
- id: EVT-009
kind: numeric
state: live
prefix: EVT
component: "9"
- id: EVT-010
kind: numeric
state: live
prefix: EVT
component: "10"
- id: EVT-011
kind: numeric
state: live
prefix: EVT
component: "11"
- id: EVT-012
kind: numeric
state: live
prefix: EVT
component: "12"
- id: EVT-013
kind: numeric
state: live
prefix: EVT
component: "13"
- id: G-001
kind: numeric
state: live
Expand All @@ -307,6 +378,11 @@ entries:
state: live
prefix: IDR
component: "2"
- id: IDR-003
kind: numeric
state: live
prefix: IDR
component: "3"
- id: OQ-001
kind: numeric
state: live
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ private ConformanceHarness(String python, Path robocodeHome, Path testRobotClass
this.rounds = rounds;
}

/** The number of rounds every battle this harness runs is configured for. */
int rounds() {
return rounds;
}

/** Resolves the environment, or returns null when this machine cannot run the tier. */
static ConformanceHarness resolveOrNull() {
if (!Files.isRegularFile(HARNESS)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,9 @@ BattleOutcome outcomeFor(Engine engine, String robotClass) {
return ran.computeIfAbsent(engine.name() + " " + robotClass,
key -> harness.run(engine, robotClass));
}

/** The number of rounds every battle in this run is configured for. */
static int configuredRounds() {
return harness.rounds();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,29 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Acceptance evidence for EVT-003 — an interruptible handler is re-entered when a
* higher-priority event arrives.
* Acceptance evidence for EVT-013 — an interruptible handler is re-entered for a
* same-priority event once marked interruptible.
*
* Classic's {@code InteruptibleEvent} robot turns its radar from inside {@code onHitWall}
* and prints a marker if {@code onScannedRobot} is then entered. Classic's own suite
* asserts exactly this, and the expectation ports unchanged because it is about a marker
* the robot printed rather than about where the robot ended up.
* and prints a marker if {@code onScannedRobot} is then entered. The robot sets
* {@code HitWallEvent} to the same priority as {@code ScannedRobotEvent} ("make same as
* scan"), so this is same-priority re-entry, not pre-emption by a higher-priority event.
* Classic's own suite asserts exactly this, and the expectation ports unchanged because
* it is about a marker the robot printed rather than about where the robot ended up.
*
* This is the criterion the earlier event-queue defect broke: with deferred same-priority
* events discarded, the scan never arrived and the marker never appeared.
* EVT-013 is the successor to EVT-003, retired because no robot in the source tree
* exercises genuine higher-priority re-entry (IDR-003). This is the criterion the earlier
* event-queue defect broke: with deferred same-priority events discarded, the scan never
* arrived and the marker never appeared.
*/
class InterruptibleEventConformanceTest extends ConformanceTestBase {

private static final String ROBOT = "tested.robots.InteruptibleEvent";
private static final String SCANNED = "Scanned!!!";

@Test
@DisplayName("EVT-003: turning the radar inside onHitWall leads to onScannedRobot on both engines")
void testEVT003_IntegrationPositive_ScanHandlerIsEnteredFromWithinTheWallHandler() {
@DisplayName("EVT-013: turning the radar inside onHitWall leads to onScannedRobot on both engines")
void testEVT013_IntegrationPositive_ScanHandlerIsEnteredFromWithinTheWallHandler() {
assertOnBothEngines(ROBOT, (outcome, engine) ->
assertTrue(outcome.anyConsoleContains(SCANNED),
() -> "the robot never reported being scanned on " + engine
Expand All @@ -33,8 +37,8 @@ void testEVT003_IntegrationPositive_ScanHandlerIsEnteredFromWithinTheWallHandler
}

@Test
@DisplayName("EVT-003 negative: the bridge does not report a scan the classic engine never saw")
void testEVT003_IntegrationNegative_DoesNotReportAScanClassicDidNotSee() {
@DisplayName("EVT-013 negative: the bridge does not report a scan the classic engine never saw")
void testEVT013_IntegrationNegative_DoesNotReportAScanClassicDidNotSee() {
BattleOutcome classic = outcomeFor(Engine.CLASSIC, ROBOT);
BattleOutcome bridge = outcomeFor(Engine.BRIDGE, ROBOT);

Expand All @@ -49,8 +53,8 @@ void testEVT003_IntegrationNegative_DoesNotReportAScanClassicDidNotSee() {
}

@Test
@DisplayName("EVT-003: neither engine throws while dispatching the interrupted handler")
void testEVT003_IntegrationNegative_DispatchingAnInterruptedHandlerThrowsOnNeitherEngine() {
@DisplayName("EVT-013: neither engine throws while dispatching the interrupted handler")
void testEVT013_IntegrationNegative_DispatchingAnInterruptedHandlerThrowsOnNeitherEngine() {
assertOnBothEngines(ROBOT, (outcome, engine) ->
assertTrue(outcome.errors().isEmpty(),
() -> "errors on " + engine + ": " + outcome.errors()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Acceptance evidence for EVT-004 — a robot's own death reaches its death handler — and for
* the round and battle completion events alongside it.
* Acceptance evidence for EVT-004 (own death reaches the death handler), EVT-012 (winning a
* round reaches the win handler), and EVT-011 (round and battle completion each reach their
* handler exactly once).
*
* Classic's {@code BattleWin} robot prints a marker from each of {@code onWin},
* {@code onDeath}, {@code onRoundEnded}, and {@code onBattleEnded}. In a robot-against-itself
Expand All @@ -22,9 +23,10 @@
* score report shows it. Routing through the queue did not fix it either, and AN-009 now has
* the reason: the death never reaches the bot, so there is nothing for any dispatcher to route.
*
* Every passing test in this class is tagged for a criterion it does not prove -- the win-handler
* test and both round-completion tests assert behaviour no criterion in CAP-001 covers. G-002
* carries that; do not read a passing test here as evidence for the criterion in its name.
* The win-handler and round-completion tests were originally tagged EVT-004 and EVT-005; G-002
* found neither tag matched what the test asserted, since no criterion in CAP-001 covered
* round or battle completion at all. EVT-012 and EVT-011 were minted for what these tests
* actually prove, and the tests below are retagged and renamed accordingly.
*/
class RoundOutcomeEventsConformanceTest extends ConformanceTestBase {

Expand All @@ -46,16 +48,16 @@ void testEVT004_IntegrationPositive_OwnDeathReachesTheDeathHandler() {
}

@Test
@DisplayName("EVT-004: winning a round reaches onWin on both engines")
void testEVT004_IntegrationPositive_WinningARoundReachesTheWinHandler() {
@DisplayName("EVT-012: winning a round reaches onWin on both engines")
void testEVT012_IntegrationPositive_WinningARoundReachesTheWinHandler() {
assertOnBothEngines(ROBOT, (outcome, engine) ->
assertTrue(outcome.anyConsoleContains("Win!"),
() -> "no robot reported winning on " + engine + " (" + outcome.summary() + ")"));
}

@Test
@DisplayName("EVT-005: round and battle completion reach their handlers on both engines")
void testEVT005_IntegrationPositive_RoundAndBattleCompletionReachTheirHandlers() {
@DisplayName("EVT-011: round and battle completion reach their handlers on both engines")
void testEVT011_IntegrationPositive_RoundAndBattleCompletionReachTheirHandlers() {
assertOnBothEngines(ROBOT, (outcome, engine) -> {
assertTrue(outcome.anyConsoleContains("RoundEnded!"),
() -> "onRoundEnded was not reported on " + engine + " (" + outcome.summary() + ")");
Expand All @@ -65,21 +67,23 @@ void testEVT005_IntegrationPositive_RoundAndBattleCompletionReachTheirHandlers()
}

@Test
@DisplayName("EVT-005 negative: round completion is reported once per round, not once per participant")
void testEVT005_IntegrationNegative_RoundCompletionIsNotReportedMoreThanOncePerRound() {
@DisplayName("EVT-011 negative: round completion is reported once per round, not once per participant")
void testEVT011_IntegrationNegative_RoundCompletionIsNotReportedMoreThanOncePerRound() {
int expectedRounds = configuredRounds();
assertOnBothEngines(ROBOT, (outcome, engine) -> {
// Each participant sees each round end exactly once. A dispatcher that delivered
// the event once per participant to every participant would satisfy the positive
// test above and fail here.
// 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!");
assertTrue(battles == 1,
() -> "a participant reported the battle ending " + battles
+ " times on " + engine);
assertTrue(rounds >= battles,
assertTrue(rounds == expectedRounds,
() -> "a participant reported " + rounds + " round endings on " + engine
+ ", fewer than the battles it saw end");
+ ", expected exactly " + expectedRounds);
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion docs/capabilities/CAP-001-event-dispatch-parity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ The physics the events describe. That a `ScannedRobotEvent` arrives at the right

`draft`. The redesign that routed events through the Bot API's own event queue is implemented and believed correct, but it was verified by running battles and reading scores. Every criterion here is unproven in the sense that matters: nothing would tell us if it broke again. `M-001` is the plan door.

The conformance tier now reaches some of them, and the capability still holds at `draft` for two separate reasons. `EVT-004` and `EVT-007` have an established cause and an unreleased repair — [`AN-009`](../../analysis/AN-009-the-server-never-sends-a-death-to-any-bot.md): the Tank Royale server sends no death to any bot, so neither the dying robot nor the survivors are told. And the tests that do pass here are not all tagged for what they prove; [`G-002`](../../goals/G-002-conformance-evidence-proves-the-criterion-it-names.md) carries that, and until it is repaired a passing test in this capability is not on its own grounds for promoting the criterion it names.
The conformance tier now reaches some of them. `EVT-011`, `EVT-012`, and `EVT-013` are active — the tests that already proved them were retagged after [`G-002`](../../goals/G-002-conformance-evidence-proves-the-criterion-it-names.md) found them mistagged, and `EVT-003` retired rather than be credited with evidence no available robot can produce ([`IDR-003`](../../decisions/IDR-003-evt-003-scoped-to-what-classic-actually-proves.md)). The capability still holds at `draft` because most criteria remain unproven, and `EVT-004`/`EVT-007` specifically have an established cause and an unreleased repair — [`AN-009`](../../analysis/AN-009-the-server-never-sends-a-death-to-any-bot.md): the Tank Royale server sends no death to any bot, so neither the dying robot nor the survivors are told.
46 changes: 36 additions & 10 deletions docs/capabilities/CAP-001-event-dispatch-parity/criteria.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ reversal-cost: low

# CAP-001 — acceptance criteria

Every criterion here is `@draft` against `M-001`. The behaviour is implemented; the evidence is not. Each names the classic test robot that will prove it, because classic's own conformance suite already encodes these expectations and the conformance tier restates them against both engines.
Most criteria here are `@draft` against `M-001`; three (`EVT-011`, `EVT-012`, `EVT-013`) are active. Each names the classic test robot that will prove it, because classic's own conformance suite already encodes these expectations and the conformance tier restates them against both engines.

```gherkin
Feature: Event dispatch and timing parity
Expand All @@ -32,17 +32,15 @@ Feature: Event dispatch and timing parity
Then every scan event the classic run delivered is also delivered under the bridge
# The defect this criterion exists for discarded exactly these events. Plan door: M-001.

@EVT-003 @draft
@EVT-003 @retired
Scenario: An interruptible handler is re-entered when a higher-priority event arrives
Test-type: Integration
Given a robot that turns its radar from inside onHitWall and marks when it is scanned
When the same battle runs on both engines
Then the robot reports being scanned on both engines
# The ported InteruptibleEvent robot is tested in InterruptibleEventConformanceTest and passes,
# but it does not prove this scenario's name: the robot sets HitWallEvent to the SAME priority as
# ScannedRobotEvent, so nothing higher-priority arrives, and the assertion cannot separate
# re-entry from an ordinary later delivery. Promotion was attempted in CH-003 and reverted.
# This is the same defect as the EVT-005 mistag; see G-002. Plan door: M-001.
# Retired: no robot in the source tree exercises genuine higher-priority re-entry, and
# classic's own InteruptibleEvent deliberately uses the SAME priority. See IDR-003.
# Superseded by EVT-013.

@EVT-004 @draft
Scenario: A robot's own death reaches its death handler
Expand All @@ -61,9 +59,8 @@ Feature: Event dispatch and timing parity
Given a robot that records the turn number at each handler entry
When the same battle runs on both engines
Then each handler is entered at the same point in the turn on both engines
# Draft, and not proven by the tests currently tagged EVT-005: those assert round and battle
# completion, which is different behaviour and which no criterion here covers. See G-002.
# Plan door: M-001.
# Draft: no test proves per-turn handler timing yet. The two tests once tagged EVT-005
# proved round/battle completion instead and are now EVT-011; see G-002. Plan door: M-001.

@EVT-006 @draft
Scenario: Custom events fire and can be removed
Expand Down Expand Up @@ -107,4 +104,33 @@ Feature: Event dispatch and timing parity
Then the per-turn scan counts match on both engines
# The division the harness has never run, and the one that carries the most
# same-priority events per turn. Plan door: M-001.

@EVT-011
Scenario: Round and battle completion each reach their handler exactly once
Test-type: Integration
Given a robot that reports from onRoundEnded and onBattleEnded
When the same battle runs on classic Robocode and on Tank Royale through the bridge
Then both handlers are reported on both engines
And a round ending is reported exactly once per round the robot saw end
# Proven by the ported BattleWin robot in RoundOutcomeEventsConformanceTest. Named for what
# those tests actually assert, after G-002 found them mistagged EVT-005. Plan door: M-001.

@EVT-012
Scenario: Winning a round reaches the win handler
Test-type: Integration
Given a robot that reports from onWin
When the robot wins a round on each engine
Then the report appears on both engines
# Proven by the ported BattleWin robot in RoundOutcomeEventsConformanceTest. Named for what
# that test actually asserts, after G-002 found it mistagged EVT-004. Plan door: M-001.

@EVT-013
Scenario: An interruptible handler is re-entered for a same-priority event once marked interruptible
Test-type: Integration
Given a robot that turns its radar from inside onHitWall, at the same event priority as a scan,
and has called setInterruptible(true)
When the same battle runs on both engines
Then the robot reports being scanned on both engines
# Proven by the ported InteruptibleEvent robot in InterruptibleEventConformanceTest. Successor
# to the retired EVT-003; see IDR-003 for why the claim is scoped to same-priority re-entry.
```
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ These are the corpus's only machine-proven criteria. Each is proven by unit test
Feature: Robocode API surface fidelity

@API-001
Scenario: Angles convert between the Robocode and Tank Royale conventions
Scenario: Angles convert from the Tank Royale convention to the Robocode convention (single-direction)
Test-type: Unit
Given an angle expressed in one engine's convention
When it is converted to the other
Then the result is the same direction expressed in the target convention
And converting it back yields the original angle
Given an angle expressed in Tank Royale's convention
When it is converted to Robocode's convention
Then the result is the same direction expressed in Robocode's convention
And the discontinuity where the circle wraps is handled rather than producing a reflected angle
# The adapter converts only this direction: outbound turn commands are relative rotations
# in the same winding sense on both engines and need no conversion, so nothing in the
# bridge ever converts a Robocode-convention angle back to Tank Royale's. AN-010 found the
# round-trip clause this scenario once had was untestable because no such method exists,
# and the asymmetry is architectural rather than a gap.

@API-002
Scenario: Colours convert between Robocode's Color and Tank Royale's representation
Expand All @@ -44,7 +48,7 @@ Feature: Robocode API surface fidelity
@API-004
Scenario: Bullets map to Robocode's Bullet with owner, power, and heading preserved
Test-type: Unit
Given a Tank Royale bullet
Given a Tank Royale bullet whose owner id does not match any bot currently in the battle
When it is mapped for a robot
Then its power, heading, and owning robot are preserved
And a bullet whose owner is no longer in the battle still maps rather than failing
Expand Down
Loading
Loading