diff --git a/.clue/id-ledger.yaml b/.clue/id-ledger.yaml index 9136cad..e0368a6 100644 --- a/.clue/id-ledger.yaml +++ b/.clue/id-ledger.yaml @@ -5,18 +5,18 @@ counters: ARCH: "3" C: "7" CAP: "8" - CH: "4" + CH: "5" CRIT: "8" DES: "8" - EVT: "13" + EVT: "14" G: "2" - IDR: "3" + IDR: "4" OQ: "2" P: "1" - PDR: "1" + PDR: "2" REH: "1" ROUTE: "12" - TASKS: "3" + TASKS: "4" entries: - id: ADR-001 kind: numeric @@ -213,6 +213,11 @@ entries: state: live prefix: CH component: "4" + - id: CH-005 + kind: numeric + state: live + prefix: CH + component: "5" - id: CRIT-001 kind: numeric state: live @@ -325,7 +330,7 @@ entries: component: "6" - id: EVT-007 kind: numeric - state: live + state: retired prefix: EVT component: "7" - id: EVT-008 @@ -358,6 +363,11 @@ entries: state: live prefix: EVT component: "13" + - id: EVT-014 + kind: numeric + state: live + prefix: EVT + component: "14" - id: G-001 kind: numeric state: live @@ -383,6 +393,11 @@ entries: state: live prefix: IDR component: "3" + - id: IDR-004 + kind: numeric + state: live + prefix: IDR + component: "4" - id: OQ-001 kind: numeric state: live @@ -390,7 +405,7 @@ entries: component: "1" - id: OQ-002 kind: numeric - state: reserved + state: live prefix: OQ component: "2" - id: P-001 @@ -403,6 +418,11 @@ entries: state: live prefix: PDR component: "1" + - id: PDR-002 + kind: numeric + state: live + prefix: PDR + component: "2" - id: REH-001 kind: numeric state: live @@ -483,3 +503,8 @@ entries: state: live prefix: TASKS component: "3" + - id: TASKS-004 + kind: numeric + state: live + prefix: TASKS + component: "4" diff --git a/AGENTS.md b/AGENTS.md index 826a52c..a7ff52b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,6 +54,8 @@ cd compat-test && python compat_test.py --trace # per-turn behavio Tier 2 **skips** rather than fails when the environment is absent, so a clean checkout still builds. It needs a classic Robocode installation, the classic source repository's compiled test robots, and the Tank Royale runner jar; point it with `-Probocode.home=` and `-Probocode.source=`. +**Tank Royale conformance artifacts are local builds.** When a conformance check needs an unreleased upstream repair, build the Bot API and runner from the same Tank Royale revision, then rebuild this bridge with `-PtankRoyaleBotApiVersion=` before running tier 2. Do not wait for, request, or create a Tank Royale release merely to run bridge evidence; `PDR-002` records the policy and `C-002` still requires a compatible pair. + **The classic side needs its own JDK.** Classic installs a `SecurityManager`, which JDK 24 removed outright, so `-Djava.security.manager=allow` is a fatal VM error there and classic cannot start at all. The harness auto-detects a JDK 23 or older; override with `COMPAT_RC_JAVA`. **Evidence conventions.** A test carries its criterion, proof type, and direction in its own name — `testAPI001_UnitPositive_...`. Give each field of a wide positional constructor a distinct value in tests: two defects found so far were positional or dispatch faults that placeholder data would have passed. `PDR-001` explains why evidence is layered in three tiers and what each can prove. diff --git a/compat-test/compat_test.py b/compat-test/compat_test.py index b122d02..aba6acc 100644 --- a/compat-test/compat_test.py +++ b/compat-test/compat_test.py @@ -43,29 +43,38 @@ # ---------------------------------------------------------------------------------- BASE_DIR = Path(__file__).resolve().parent +TANK_ROYALE_HOME = Path(os.environ.get("COMPAT_TANK_ROYALE_HOME", r"C:\Code\tank-royale")) + + +def local_bot_api_jar(): + """Returns the locally built Bot API jar, or the expected path when it has not been built.""" + libs = TANK_ROYALE_HOME / "bot-api" / "java" / "build" / "libs" + jars = [path for path in libs.glob("robocode-tankroyale-bot-api-*.jar") + if not path.name.endswith(("-javadoc.jar", "-sources.jar"))] + if jars: + return str(max(jars, key=lambda path: path.stat().st_mtime)) + return str(libs / "robocode-tankroyale-bot-api-local.jar") DEFAULTS = { "collection_dir": os.environ.get("COMPAT_COLLECTION_DIR", r"C:\Code\LiteRumble robots"), "robocode_home": os.environ.get("COMPAT_ROBOCODE_HOME", r"C:\robocode"), "runner_jar": os.environ.get( "COMPAT_RUNNER_JAR", - r"C:\Code\tank-royale\runner\examples\lib\robocode-tankroyale-runner.jar"), + str(TANK_ROYALE_HOME / "runner" / "examples" / "lib" / "robocode-tankroyale-runner.jar")), "bridge_api_jar": os.environ.get( "COMPAT_BRIDGE_API_JAR", r"C:\Code\robocode-api-bridge\robocode-api\build\libs\robocode-api-0.5.0.jar"), "wrapper_jar": os.environ.get( "COMPAT_WRAPPER_JAR", r"C:\Code\robocode-api-bridge\robots-wrapper\build\libs\robots-wrapper-0.3.1.jar"), - # NOTE: must be protocol/API compatible with what the bridge's robocode-api jar was - # compiled against AND with the server embedded in the runner jar. Publish it with - # `gradlew :bot-api:java:publishToMavenLocal` in the tank-royale repository. + # The bridge uses a locally built Bot API and runner from the same Tank Royale revision; + # override only with another matched local pair. Build the API with + # `gradlew :bot-api:java:publishToMavenLocal` and runner with `:runner:copyRunnerJar`. # (0.33.1 had an event-queue bug dropping deferred same-priority events, e.g. every # other scan event for bots that call blocking methods inside onScannedRobot.) "bot_api_jar": os.environ.get( "COMPAT_BOT_API_JAR", - os.path.expanduser(r"~\.m2\repository\dev\robocode\tankroyale" - r"\robocode-tankroyale-bot-api\1.0.2" - r"\robocode-tankroyale-bot-api-1.0.2.jar")), + local_bot_api_jar()), # Classic Robocode installs a SecurityManager to sandbox robots. JDK 24 removed # SecurityManager support outright, so -Djava.security.manager=allow is no longer a # deprecation warning but a fatal VM error, and the classic side cannot start at all. @@ -880,6 +889,8 @@ def parse_args(): conf.add_argument("--conformance", metavar="JAR", help="run one robot jar on one engine and print the result, " "including each participant's console output, as JSON") + conf.add_argument("--conformance-source", type=Path, + help="compile this bridge-owned robot source against classic before running it") conf.add_argument("--engine", choices=("rc", "tr"), default="rc", help="which engine --conformance drives") conf.add_argument("--robot-class", @@ -1044,6 +1055,24 @@ def compile_trace_robot(opts): return out_dir, None +def compile_conformance_robot(opts, source: Path): + """Compiles a bridge-owned conformance probe against the classic API.""" + out_dir = WORK_DIR / "conformance-probe-classes" + clean_dir(out_dir) + if not source.exists(): + return None, f"conformance probe source not found: {source}" + + classpath = str(Path(opts.robocode_home) / "libs" / "*") + cmd = [javac_beside(opts.rc_java_exe), "-cp", classpath, "-d", str(out_dir), str(source)] + try: + completed = subprocess.run(cmd, capture_output=True, text=True, timeout=180) + except (OSError, subprocess.SubprocessError) as e: + return None, f"could not compile conformance probe: {e}" + if completed.returncode != 0: + return None, f"conformance probe did not compile:\n{completed.stdout}\n{completed.stderr}" + return out_dir, None + + def javac_beside(java_exe): """The javac that ships next to a given java executable. @@ -1204,7 +1233,17 @@ def run_conformance(opts): if opts.participants is not None: setup["participants"] = opts.participants - if jar.is_dir(): + if opts.conformance_source: + class_dir, error = compile_conformance_robot(opts, opts.conformance_source) + if error: + print(json.dumps({"ok": False, "fatal": error})) + return 2 + packaged, error = package_test_robot_jar(class_dir, classname, WORK_DIR / "conformance") + if error: + print(json.dumps({"ok": False, "fatal": error})) + return 2 + jar, version = packaged, "1.0" + elif jar.is_dir(): # Package the one robot under test, for both engines. # # The bridge side needs a real robot jar because the wrapper finds robots by their diff --git a/compat-test/conformance-robots/conformance/probes/DeathEventProbe.java b/compat-test/conformance-robots/conformance/probes/DeathEventProbe.java new file mode 100644 index 0000000..ebfa1d3 --- /dev/null +++ b/compat-test/conformance-robots/conformance/probes/DeathEventProbe.java @@ -0,0 +1,35 @@ +package conformance.probes; + +import robocode.DeathEvent; +import robocode.Robot; +import robocode.RobotDeathEvent; +import robocode.ScannedRobotEvent; + +/** A two-instance probe that reports its own and another robot's death handlers. */ +public class DeathEventProbe extends Robot { + + @Override + public void run() { + while (true) { + ahead(100); + turnGunRight(360); + back(100); + turnGunRight(360); + } + } + + @Override + public void onScannedRobot(ScannedRobotEvent event) { + fire(2); + } + + @Override + public void onRobotDeath(RobotDeathEvent event) { + out.println("OtherDeath!"); + } + + @Override + public void onDeath(DeathEvent event) { + out.println("OwnDeath!"); + } +} diff --git a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/ConformanceHarness.java b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/ConformanceHarness.java index 7aa61f6..b1d5895 100644 --- a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/ConformanceHarness.java +++ b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/ConformanceHarness.java @@ -39,6 +39,11 @@ private ConformanceHarness(String python, Path robocodeHome, Path testRobotClass this.rounds = rounds; } + /** Repository root, for bridge-owned probe sources passed to the Python harness. */ + static Path repoRoot() { + return REPO_ROOT; + } + /** The number of rounds every battle this harness runs is configured for. */ int rounds() { return rounds; @@ -89,7 +94,7 @@ static String missingEnvironment() { * * @param robotClass fully qualified, e.g. {@code tested.robots.InteruptibleEvent} */ - BattleOutcome run(Engine engine, String robotClass) { + BattleOutcome run(Engine engine, String robotClass, Path source) { List command = new ArrayList<>(List.of( python, HARNESS.toString(), @@ -98,6 +103,10 @@ BattleOutcome run(Engine engine, String robotClass) { "--engine", engine.harnessName(), "--rounds", String.valueOf(rounds), "--robocode-home", robocodeHome.toString())); + if (source != null) { + command.add("--conformance-source"); + command.add(source.toString()); + } try { Process process = new ProcessBuilder(command) diff --git a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/ConformanceTestBase.java b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/ConformanceTestBase.java index ceab7e7..1ba1656 100644 --- a/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/ConformanceTestBase.java +++ b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/ConformanceTestBase.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.Map; +import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -43,8 +44,13 @@ interface Expectation { * Runs the robot on both engines and applies the same expectation to each. */ void assertOnBothEngines(String robotClass, Expectation expectation) { + assertOnBothEngines(robotClass, null, expectation); + } + + /** Runs a locally held probe source on both engines after compiling it against classic. */ + void assertOnBothEngines(String robotClass, Path source, Expectation expectation) { for (Engine engine : Engine.values()) { - BattleOutcome outcome = outcomeFor(engine, robotClass); + BattleOutcome outcome = outcomeFor(engine, robotClass, source); assertTrue(outcome.completed(), () -> "the battle did not complete on " + engine + " (" + outcome.summary() + ")"); expectation.check(outcome, engine); @@ -59,8 +65,12 @@ void assertOnBothEngines(String robotClass, Expectation expectation) { * reason that has nothing to do with what it claims to check. */ BattleOutcome outcomeFor(Engine engine, String robotClass) { + return outcomeFor(engine, robotClass, null); + } + + private BattleOutcome outcomeFor(Engine engine, String robotClass, Path source) { return ran.computeIfAbsent(engine.name() + " " + robotClass, - key -> harness.run(engine, robotClass)); + key -> harness.run(engine, robotClass, source)); } /** The number of rounds every battle in this run is configured for. */ 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 new file mode 100644 index 0000000..ddddec1 --- /dev/null +++ b/conformance-test/src/test/java/dev/robocode/tankroyale/bridge/conformance/RobotDeathEventsConformanceTest.java @@ -0,0 +1,26 @@ +package dev.robocode.tankroyale.bridge.conformance; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** Acceptance evidence for EVT-014 — a survivor receives another robot's death. */ +class RobotDeathEventsConformanceTest extends ConformanceTestBase { + + private static final String ROBOT = "conformance.probes.DeathEventProbe"; + private static final Path SOURCE = ConformanceHarness.repoRoot().resolve(Path.of("compat-test", + "conformance-robots", "conformance", "probes", "DeathEventProbe.java")); + private static final String OTHER_DEATH = "OtherDeath!"; + + @Test + @DisplayName("EVT-014: a surviving robot reports another robot's death on both engines") + void testEVT014_IntegrationPositive_SurvivorReceivesAnotherRobotsDeath() { + assertOnBothEngines(ROBOT, SOURCE, (outcome, engine) -> + assertTrue(outcome.anyConsoleContains(OTHER_DEATH), + () -> "no survivor reported another robot's death on " + engine + " (" + + 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 ac310d3..c03d992 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 @@ -1,6 +1,5 @@ package dev.robocode.tankroyale.bridge.conformance; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -33,12 +32,6 @@ class RoundOutcomeEventsConformanceTest extends ConformanceTestBase { private static final String ROBOT = "tested.robots.BattleWin"; @Test - @Disabled("Fails against the bridge: onDeath is never called, because no death event reaches " - + "any bot at all. AN-009 has the cause -- the Tank Royale server emits a death before " - + "the turn's bot snapshot exists, so it fans out over nobody -- and the repair is " - + "committed upstream and unreleased. Kept rather than deleted because it is the only " - + "thing that detects the defect, and disabled rather than left failing so the build " - + "stays honest. Re-enable when a Tank Royale release carries the repair.") @DisplayName("EVT-004: a robot's own death reaches onDeath on both engines") void testEVT004_IntegrationPositive_OwnDeathReachesTheDeathHandler() { assertOnBothEngines(ROBOT, (outcome, engine) -> @@ -47,6 +40,15 @@ void testEVT004_IntegrationPositive_OwnDeathReachesTheDeathHandler() { + ", so onDeath was never called (" + outcome.summary() + ")")); } + @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() + ")")); + } + @Test @DisplayName("EVT-012: winning a round reaches onWin on both engines") void testEVT012_IntegrationPositive_WinningARoundReachesTheWinHandler() { diff --git a/docs/capabilities/CAP-001-event-dispatch-parity/README.md b/docs/capabilities/CAP-001-event-dispatch-parity/README.md index 4384292..6031d7e 100644 --- a/docs/capabilities/CAP-001-event-dispatch-parity/README.md +++ b/docs/capabilities/CAP-001-event-dispatch-parity/README.md @@ -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. `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. +The conformance tier now reaches some of them. `EVT-004`, `EVT-011`, `EVT-012`, `EVT-013`, and `EVT-014` 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`/`EVT-007` retired rather than be credited with evidence they cannot honestly claim ([`IDR-003`](../../decisions/IDR-003-evt-003-scoped-to-what-classic-actually-proves.md), [`IDR-004`](../../decisions/IDR-004-evt-007-scoped-to-observable-survivor-delivery.md)). `EVT-004` and `EVT-014` are proven with a locally built matched Tank Royale Bot API and runner pair under [`PDR-002`](../../decisions/PDR-002-locally-built-tank-royale-artifacts-for-conformance.md), which contains the server repair [`AN-009`](../../analysis/AN-009-the-server-never-sends-a-death-to-any-bot.md) identified. The capability still holds at `draft` because most criteria remain unproven. diff --git a/docs/capabilities/CAP-001-event-dispatch-parity/criteria.md b/docs/capabilities/CAP-001-event-dispatch-parity/criteria.md index 8a570f8..269c8cc 100644 --- a/docs/capabilities/CAP-001-event-dispatch-parity/criteria.md +++ b/docs/capabilities/CAP-001-event-dispatch-parity/criteria.md @@ -42,16 +42,15 @@ Feature: Event dispatch and timing parity # classic's own InteruptibleEvent deliberately uses the SAME priority. See IDR-003. # Superseded by EVT-013. - @EVT-004 @draft + @EVT-004 Scenario: A robot's own death reaches its death handler Test-type: Integration Given a robot that reports from onDeath When the robot is destroyed on each engine Then the report appears on both engines - # Draft because the behaviour is missing, not because nothing tests it: the test exists and - # is disabled. AN-009 establishes the cause -- the Tank Royale server emits a death before the - # turn's bot snapshot exists, so it reaches no bot at all -- and the repair is committed - # upstream and unreleased. Promote when a release carries it. Plan door: M-001. + # Proven by the ported BattleWin robot in RoundOutcomeEventsConformanceTest against a locally + # built matched Tank Royale Bot API and runner pair. AN-009 establishes the original server + # cause; PDR-002 records why bridge evidence uses this local upstream build. Plan door: M-001. @EVT-005 @draft Scenario: New-turn events arrive at the classic point in the turn @@ -70,14 +69,24 @@ Feature: Event dispatch and timing parity Then the firing and the silence after removal match on both engines # Proven by the ported CustomEvents robot. Plan door: M-001. - @EVT-007 @draft + @EVT-007 @retired Scenario: The death of another robot reaches the survivors Test-type: Integration Given a robot that reports each robot death it observes When the same battle runs on both engines Then the same deaths are reported in the same order on both engines - # Proven by the ported RobotDeathEvents robot. Blocked by the same cause as EVT-004: no death - # event reaches any bot, so the survivors are not told either. See AN-009. Plan door: M-001. + # Retired: Tank Royale has no deterministic seed, so cross-engine death ordering cannot be + # compared honestly. Superseded by EVT-014; see IDR-004. + + @EVT-014 + Scenario: A survivor receives another robot's death event + Test-type: Integration + Given two robots that report from onRobotDeath + When one robot is destroyed on each engine + Then a surviving robot reports the other robot's death on both engines + # Proven by the bridge-owned DeathEventProbe in RobotDeathEventsConformanceTest. Successor + # to EVT-007; see IDR-004. (single-direction): a battle with a death necessarily has a + # survivor, so the missing marker is the behavior this criterion detects. @EVT-008 @draft Scenario: Skipped turns are reported to the robot diff --git a/docs/constraints/C-002-bot-api-protocol-compatibility.md b/docs/constraints/C-002-bot-api-protocol-compatibility.md index a3e999b..93af2a5 100644 --- a/docs/constraints/C-002-bot-api-protocol-compatibility.md +++ b/docs/constraints/C-002-bot-api-protocol-compatibility.md @@ -4,7 +4,7 @@ type: constraint status: active links: [CAP-001, ARCH-001] title: The Bot API must be protocol-compatible with the server embedded in the runner -source: compat-test/README.md prerequisites, and the incident recorded in AN-001 +source: PDR-002, compat-test/README.md prerequisites, and the incident recorded in AN-001 enforcement: human provenance: inferred reversal-cost: high @@ -14,6 +14,8 @@ reversal-cost: high The bridge links one version of the Tank Royale Bot API; battles run against a server embedded in the Tank Royale runner jar. The two speak a versioned protocol, and a mismatched pairing is not a build error — it is a battle in which the bots never act. +Conformance may use unreleased Tank Royale repairs, but it builds the Bot API and runner locally from the same upstream revision rather than requiring a Tank Royale release. `PDR-002` records that boundary. + ## Why this is a constraint rather than a build detail The failure is silent and looks like a bridge defect. Under an incompatible pairing the robots sit idle and score zero, which reads exactly like a bridge that cannot drive its robots. diff --git a/docs/decisions/IDR-004-evt-007-scoped-to-observable-survivor-delivery.md b/docs/decisions/IDR-004-evt-007-scoped-to-observable-survivor-delivery.md new file mode 100644 index 0000000..8a5a25b --- /dev/null +++ b/docs/decisions/IDR-004-evt-007-scoped-to-observable-survivor-delivery.md @@ -0,0 +1,23 @@ +--- +id: IDR-004 +type: decision +status: inferred +author: agent +accepted-by: [] +links: [CAP-001, PDR-001, AN-009] +title: EVT-007's cross-engine death-order claim is retired; survivor delivery is measured directly +--- + +# IDR-004 — EVT-007's cross-engine death-order claim is retired; survivor delivery is measured directly + +## Decision + +Retire `EVT-007` and mint `EVT-014`: a surviving robot receives another robot's death event on each engine. The conformance probe reports its handler marker on both engines. + +## Context + +`EVT-007` required the same deaths in the same order across the engines. Tank Royale has no deterministic seed, so those are different battles; ordering cannot be compared honestly. `AN-009` established the observable defect: survivors received no death event at all. + +## Consequences + +The new criterion measures that missing behavior directly without claiming cross-engine sequence equality. The exact order of otherwise valid deaths remains outside this evidence boundary. diff --git a/docs/decisions/PDR-002-locally-built-tank-royale-artifacts-for-conformance.md b/docs/decisions/PDR-002-locally-built-tank-royale-artifacts-for-conformance.md new file mode 100644 index 0000000..3dcc593 --- /dev/null +++ b/docs/decisions/PDR-002-locally-built-tank-royale-artifacts-for-conformance.md @@ -0,0 +1,23 @@ +--- +id: PDR-002 +type: decision +status: verified +author: agent +accepted-by: [Flemming N. Larsen] +links: [PDR-001, C-002, CAP-001] +title: Conformance uses locally built Tank Royale artifacts rather than waiting for releases +--- + +# PDR-002 — Conformance uses locally built Tank Royale artifacts rather than waiting for releases + +## Decision + +When bridge conformance needs a Tank Royale repair that is not released, build the Tank Royale Bot API and runner locally from the same upstream revision and use that pair for the comparison. Do not wait for, request, or create a Tank Royale release solely to establish bridge evidence. + +## Context + +Tank Royale releases are a separate, consequential product operation. They are not a prerequisite for checking whether an upstream repair restores classic Robocode behaviour through this bridge. The bridge needs a compatible Bot API and runner server, not published coordinates. + +## Consequences + +The conformance setup identifies the Tank Royale source revision and builds both artifacts from it. The bridge is rebuilt with that local Bot API version before the comparison, while the CI-only unit tier retains its published dependency default. `C-002` remains in force: a local pair must still be protocol-compatible and a battle must show that bots act before its results are trusted. A Tank Royale release may later consume the same repair, but that release is outside the bridge change. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 2de4dbe..93d3144 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -26,4 +26,6 @@ A decision that changes a methodology contract inventories every live carrier th - [IDR-002 — getDataDirectory resolves through the same robot data lookup getDataFile uses](IDR-002-data-directory-resolves-through-robot-data.md) · `verified` — One question about where a robot's data lives should have one answer. Alignment, not confinement. - [PDR-001 — Evidence comes in three tiers, separated by cost and by what each can prove](PDR-001-three-tier-evidence-strategy.md) · `verified` — Unit, conformance, and sweep: what each tier can establish, and what is rejected because Tank Royale has no seed. - [IDR-003 — EVT-003's higher-priority claim is retired; interruptible re-entry evidence is scoped to what classic's own robot proves](IDR-003-evt-003-scoped-to-what-classic-actually-proves.md) · `inferred` — `EVT-003` retires. +- [PDR-002 — Conformance uses locally built Tank Royale artifacts rather than waiting for releases](PDR-002-locally-built-tank-royale-artifacts-for-conformance.md) · `verified` — When bridge conformance needs a Tank Royale repair that is not released, build the Tank Royale Bot API and runner locally from the same upstream revision and use that pair for the comparison. +- [IDR-004 — EVT-007's cross-engine death-order claim is retired; survivor delivery is measured directly](IDR-004-evt-007-scoped-to-observable-survivor-delivery.md) · `inferred` — Retire `EVT-007` and mint `EVT-014`: a surviving robot receives another robot's death event on each engine. diff --git a/docs/plans/P-001-bridge-parity-campaign.md b/docs/plans/P-001-bridge-parity-campaign.md index 0196f14..a33e04e 100644 --- a/docs/plans/P-001-bridge-parity-campaign.md +++ b/docs/plans/P-001-bridge-parity-campaign.md @@ -43,10 +43,10 @@ They are bookkeeping rather than a second plan. A door closes when its criterion | M-101 | `EVT-001` | `EVT-001` is active, with evidence attributable to it. Work lands under M-001. | todo | | M-102 | `EVT-002` | `EVT-002` is active, with evidence attributable to it. Work lands under M-001. | todo | | M-103 | `EVT-003` | Dropped: `EVT-003` retired (`IDR-003`); see `M-141` for its successor `EVT-013`. | dropped | -| M-104 | `EVT-004` | `EVT-004` is active, with evidence attributable to it. Work lands under M-001. | todo | +| M-104 | `EVT-004` | `EVT-004` is active, with evidence attributable to it. Work lands under M-001. | done | | M-105 | `EVT-005` | `EVT-005` is active, with evidence attributable to it. Work lands under M-001. | todo | | M-106 | `EVT-006` | `EVT-006` is active, with evidence attributable to it. Work lands under M-001. | todo | -| M-107 | `EVT-007` | `EVT-007` is active, with evidence attributable to it. Work lands under M-001. | todo | +| M-107 | `EVT-007` | Dropped: `EVT-007` retired (`IDR-004`); see `M-142` for its successor `EVT-014`. | dropped | | M-108 | `EVT-008` | `EVT-008` is active, with evidence attributable to it. Work lands under M-001. | todo | | M-109 | `EVT-009` | `EVT-009` is active, with evidence attributable to it. Work lands under M-001. | todo | | M-110 | `EVT-010` | `EVT-010` is active, with evidence attributable to it. Work lands under M-001. | todo | @@ -81,6 +81,7 @@ They are bookkeeping rather than a second plan. A door closes when its criterion | M-139 | `EVT-011` | `EVT-011` is active, with evidence attributable to it. Work lands under M-001. | done | | M-140 | `EVT-012` | `EVT-012` is active, with evidence attributable to it. Work lands under M-001. | done | | M-141 | `EVT-013` | `EVT-013` is active, with evidence attributable to it. Successor to `EVT-003` (`M-103`). Work lands under M-001. | done | +| M-142 | `EVT-014` | `EVT-014` is active, with evidence attributable to it. Successor to `EVT-007` (`M-107`). Work lands under M-001. | done | ## Why this order diff --git a/robocode-api/build.gradle.kts b/robocode-api/build.gradle.kts index cbb637d..006a9e0 100644 --- a/robocode-api/build.gradle.kts +++ b/robocode-api/build.gradle.kts @@ -7,17 +7,17 @@ plugins { group = "dev.robocode" version = "0.5.0" +val tankRoyaleBotApiVersion = providers.gradleProperty("tankRoyaleBotApiVersion").orElse("1.0.2") + repositories { - // bot-api 1.0.2 is on Maven Central. mavenLocal stays first so an unreleased build can - // be tried against the bridge by publishing it with `gradlew :bot-api:java:publishToMavenLocal` - // in the tank-royale repository -- but C-002 applies: the version must stay protocol - // compatible with the server embedded in the runner jar, and nothing checks that. + // CI resolves the published default. A live conformance run overrides its version with a + // locally published Tank Royale Bot API from the same revision as the runner (C-002). mavenLocal() mavenCentral() } dependencies { - implementation("dev.robocode.tankroyale:robocode-tankroyale-bot-api:1.0.2") + implementation("dev.robocode.tankroyale:robocode-tankroyale-bot-api:${tankRoyaleBotApiVersion.get()}") // Tier 1 of the evidence strategy (PDR-001): unit tests over the adapter's value // conversions. No engine, so this is the only tier that runs in CI. @@ -47,4 +47,4 @@ tasks { attributes["Package"] = project.group } } -} \ No newline at end of file +} diff --git a/robots-wrapper/build.gradle.kts b/robots-wrapper/build.gradle.kts index ffa1433..0abc846 100644 --- a/robots-wrapper/build.gradle.kts +++ b/robots-wrapper/build.gradle.kts @@ -8,8 +8,8 @@ group = "dev.robocode" version = "0.3.1" repositories { - mavenLocal() // bot-api 1.0.2 is not published on Maven Central (yet); publish it with - // `gradlew :bot-api:java:publishToMavenLocal` in the tank-royale repository + mavenLocal() // The bridge's local Tank Royale Bot API build is carried transitively from + // :robocode-api; publish it with `gradlew :bot-api:java:publishToMavenLocal`. mavenCentral() } @@ -52,4 +52,4 @@ tasks { build { dependsOn(fatJar) // Trigger fat jar creation during build } -} \ No newline at end of file +}