Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f88f433
Propose CH-017 continue M-006 parity sweep
flemming-n-larsen Sep 11, 2026
8ded29f
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 11, 2026
28c35e2
Record second CH-017 roborumble checkpoint
flemming-n-larsen Sep 11, 2026
458b1a5
Record third CH-017 roborumble checkpoint
flemming-n-larsen Sep 11, 2026
9a866c0
Record fourth CH-017 roborumble checkpoint
flemming-n-larsen Sep 11, 2026
f44e32e
Record fifth CH-017 roborumble checkpoint
flemming-n-larsen Sep 11, 2026
ec27660
Record sixth CH-017 roborumble checkpoint
flemming-n-larsen Sep 11, 2026
bf9963e
Record seventh CH-017 roborumble checkpoint
flemming-n-larsen Sep 11, 2026
fc1bb82
Record eighth CH-017 roborumble checkpoint
flemming-n-larsen Sep 11, 2026
78c338d
Record ninth CH-017 roborumble checkpoint
flemming-n-larsen Sep 11, 2026
520560c
Fix checkpoint registry replay
flemming-n-larsen Sep 11, 2026
7c7e232
Repair CH-017 registry replay artifacts
flemming-n-larsen Sep 11, 2026
7e4b06b
Record tenth CH-017 roborumble checkpoint
flemming-n-larsen Sep 11, 2026
94fdbe3
Handle legacy metadata encoding
flemming-n-larsen Sep 12, 2026
3bfc0db
Record recovered CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
df9075a
Promote CH-017 metadata identities
flemming-n-larsen Sep 12, 2026
36549f1
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
7f69420
Record CH-017 matched failure checkpoint
flemming-n-larsen Sep 12, 2026
d2d1a23
Record CH-017 harness failure checkpoint
flemming-n-larsen Sep 12, 2026
7a4932b
Keep compatibility workers Java 11 compatible
flemming-n-larsen Sep 12, 2026
975ed11
Retest CH-017 after worker compatibility repair
flemming-n-larsen Sep 12, 2026
cdeb97a
Record CH-017 roborumble checkpoint after harness repair
flemming-n-larsen Sep 12, 2026
5939e54
Recover interrupted CH-017 checkpoint
flemming-n-larsen Sep 12, 2026
2e03617
Record CH-017 roborumble checkpoint after worker repair
flemming-n-larsen Sep 12, 2026
b685c55
Pin classic Robocode version in parity evidence
flemming-n-larsen Sep 12, 2026
64f9ea7
Record Robocode version pin proof
flemming-n-larsen Sep 12, 2026
a5a2657
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
cf50844
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
28d4a79
Restore packaged robot data files
flemming-n-larsen Sep 12, 2026
9972f00
Confirm packaged robot data parity
flemming-n-larsen Sep 12, 2026
1498474
Retest unresolved roborumble cases with packaged data
flemming-n-larsen Sep 12, 2026
aafcebc
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
07f66a1
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
3bcf751
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
886afdc
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
d177578
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
d30d1b3
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
eb9dd70
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
14e9cb0
Record CH-017 roborumble checkpoint
flemming-n-larsen Sep 12, 2026
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
6 changes: 6 additions & 0 deletions .clue/id-ledger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ events:
- {id: AN-015, kind: numeric, state: live, prefix: AN, component: "15"}
- {id: FIO-005, kind: numeric, state: reserved, prefix: FIO, component: "5"}
- {id: FIO-005, kind: numeric, state: live, prefix: FIO, component: "5"}
- {id: CH-017, kind: numeric, state: reserved, prefix: CH, component: "17"}
- {id: TASKS-010, kind: numeric, state: reserved, prefix: TASKS, component: "10"}
- {id: OQ-008, kind: numeric, state: reserved, prefix: OQ, component: "8"}
- {id: CH-017, kind: numeric, state: live, prefix: CH, component: "17"}
- {id: TASKS-010, kind: numeric, state: live, prefix: TASKS, component: "10"}
- {id: OQ-008, kind: numeric, state: live, prefix: OQ, component: "8"}
high-water:
- {id: ADR-002, kind: numeric, state: reserved, prefix: ADR, component: "2"}
- {id: AN-014, kind: numeric, state: reserved, prefix: AN, component: "14"}
Expand Down
11 changes: 11 additions & 0 deletions changes/CH-017-complete-m006-parity-sweep/open-questions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
id: OQ-008
type: open-question
status: active
links: [CH-017]
title: CH-017 open questions
---

# Open questions

No blocking question is known at proposal time. Any question that would change the accepted comparison shape, collection boundary, repair obligation, or evidence interpretation will be recorded here and paused for human resolution before implementation continues.
15 changes: 15 additions & 0 deletions changes/CH-017-complete-m006-parity-sweep/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
id: CH-017
type: change
status: open
links: [P-001, CH-016]
title: Continue the all-division versioned parity campaign
---

# Proposal

The merged CH-016 checkpoint established the fixed melee comparison, completed official team coverage, and added diagnosis and containment for the first melee error clusters, but M-006 remains open. The tracked registry still contains unresolved cases and does not yet establish complete parity across the discoverable roborumble and melee collections.

This change continues from that accepted checkpoint. It will run the remaining official-parameter cases with the read-only collection intact, diagnose every unresolved outcome, implement bridge-owned repairs where the evidence supports them, and append focused retests with complete artifact manifests. It will update the permanent plan, CAP-005 evidence/design bookkeeping, generated registry report, and changelog only to the extent the resulting registry proves those claims.

The change proceeds under classic Robocode as the behavioural reference, PDR-003's append-only registry rules, and PDR-004's pinned melee opponent pool. It does not rewrite or replace prior observations or collection jars.
17 changes: 17 additions & 0 deletions changes/CH-017-complete-m006-parity-sweep/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
id: TASKS-010
type: tasks
status: open
links: [CH-017]
title: CH-017 implementation tasks
---

# Tasks

- [x] Confirm the post-CH-016 registry frontier, discoverable collection, official parameters, pinned melee pool, and available engine artifacts; serves `SCORE-006`.
- [x] Correct normal checkpoint synchronization to append only subjects completed in the current invocation, and add regression evidence preventing replay of accumulated progress; serves `HARN-001` and `SCORE-006`.
- [ ] Run the remaining official-parameter roborumble and melee subjects in bounded checkpoints, preserving every completed observation and its artifact manifest in the tracked registry; serves `SCORE-001` and `SCORE-006`.
- [ ] Diagnose every unresolved bridge-versus-classic error, completion, hang, or confirmed score gap, including opponent-contaminated melee outcomes, and record each diagnosis without rewriting earlier evidence; serves `SCORE-001`.
- [ ] Implement and verify each bridge-owned repair supported by the diagnoses, including the classic five-open-stream limit and legacy team-message serialization failure shape where still applicable; serves `SCORE-001`.
- [ ] Run a focused retest for every named repair or record the external blocker, then sync the retest observations and generated report; serves `SCORE-001`.
- [ ] Update the permanent plan, CAP-005 evidence/design bookkeeping, generated indexes, and user-facing changelog to reflect only what the completed registry proves; serves `P-001#M-006`, `SCORE-001`, and `SCORE-006`.
3 changes: 2 additions & 1 deletion compat-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ the failure.
JDK; override with `--rc-java` or `COMPAT_RC_JAVA`. The Tank Royale side is unaffected.
- Robot collection at `C:\Code\LiteRumble robots` with `roborumble`, `meleerumble`,
`teamrumble` subdirectories of `.jar` files.
- Classic Robocode installation at `C:\robocode` (1.10.3 tested).
- Classic Robocode installation at `C:\robocode`; LiteRumble currently accepts client versions
1.10.3, 1.11.0, and 1.11.1, and the harness records and validates the installed version.
- Built artifacts (all present after building the respective repos):
- Tank Royale runner fat jar: `C:\Code\tank-royale\runner\examples\lib\robocode-tankroyale-runner.jar`
- Bridge adapter: `robocode-api\build\libs\robocode-api-0.5.0.jar` (this repo, `gradlew :robocode-api:build`)
Expand Down
27 changes: 15 additions & 12 deletions compat-test/RcBattleWorker.java
Original file line number Diff line number Diff line change
Expand Up @@ -344,11 +344,13 @@ static String write(Object value) {
private static void writeValue(StringBuilder sb, Object value) {
if (value == null) {
sb.append("null");
} else if (value instanceof String s) {
} else if (value instanceof String) {
String s = (String) value;
sb.append('"').append(escape(s)).append('"');
} else if (value instanceof Number || value instanceof Boolean) {
sb.append(value);
} else if (value instanceof Map<?, ?> map) {
} else if (value instanceof Map) {
Map<?, ?> map = (Map<?, ?>) value;
sb.append('{');
boolean first = true;
for (Map.Entry<?, ?> e : map.entrySet()) {
Expand All @@ -358,7 +360,8 @@ private static void writeValue(StringBuilder sb, Object value) {
writeValue(sb, e.getValue());
}
sb.append('}');
} else if (value instanceof Iterable<?> it) {
} else if (value instanceof Iterable) {
Iterable<?> it = (Iterable<?>) value;
sb.append('[');
boolean first = true;
for (Object o : it) {
Expand All @@ -377,21 +380,21 @@ private static String escape(String s) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '"' -> sb.append("\\\"");
case '\\' -> sb.append("\\\\");
case '\n' -> sb.append("\\n");
case '\r' -> sb.append("\\r");
case '\t' -> sb.append("\\t");
case '\b' -> sb.append("\\b");
case '\f' -> sb.append("\\f");
default -> {
case '"': sb.append("\\\""); break;
case '\\': sb.append("\\\\"); break;
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
case '\b': sb.append("\\b"); break;
case '\f': sb.append("\\f"); break;
default:
if (c < 0x20) {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
break;
}
}
}
return sb.toString();
}
Expand Down
27 changes: 15 additions & 12 deletions compat-test/TrBattleWorker.java
Original file line number Diff line number Diff line change
Expand Up @@ -269,11 +269,13 @@ static String write(Object value) {
private static void writeValue(StringBuilder sb, Object value) {
if (value == null) {
sb.append("null");
} else if (value instanceof String s) {
} else if (value instanceof String) {
String s = (String) value;
sb.append('"').append(escape(s)).append('"');
} else if (value instanceof Number || value instanceof Boolean) {
sb.append(value);
} else if (value instanceof Map<?, ?> map) {
} else if (value instanceof Map) {
Map<?, ?> map = (Map<?, ?>) value;
sb.append('{');
boolean first = true;
for (Map.Entry<?, ?> e : map.entrySet()) {
Expand All @@ -283,7 +285,8 @@ private static void writeValue(StringBuilder sb, Object value) {
writeValue(sb, e.getValue());
}
sb.append('}');
} else if (value instanceof Iterable<?> it) {
} else if (value instanceof Iterable) {
Iterable<?> it = (Iterable<?>) value;
sb.append('[');
boolean first = true;
for (Object o : it) {
Expand All @@ -302,21 +305,21 @@ private static String escape(String s) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '"' -> sb.append("\\\"");
case '\\' -> sb.append("\\\\");
case '\n' -> sb.append("\\n");
case '\r' -> sb.append("\\r");
case '\t' -> sb.append("\\t");
case '\b' -> sb.append("\\b");
case '\f' -> sb.append("\\f");
default -> {
case '"': sb.append("\\\""); break;
case '\\': sb.append("\\\\"); break;
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
case '\b': sb.append("\\b"); break;
case '\f': sb.append("\\f"); break;
default:
if (c < 0x20) {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
break;
}
}
}
return sb.toString();
}
Expand Down
79 changes: 75 additions & 4 deletions compat-test/compat_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ def local_bot_api_jar():
)
RC_JAVA_MAX_FEATURE = 23 # the last release that still allowed a SecurityManager

# LiteRumble currently accepts these classic Robocode client versions. Keep this
# list aligned with robo-code/literumble/structures.py; the collection is ranked
# by the classic client, so an unrecognised install must not create evidence.
LITERUMBLE_ALLOWED_ROBOCODE_CLIENTS = ("1.10.3", "1.11.0", "1.11.1")

STATE_FILE = BASE_DIR / "test_progress.json"
REPORT_FILE = BASE_DIR / "compatibility_report.md"
ERRORS_DIR = BASE_DIR / "errors"
Expand Down Expand Up @@ -305,6 +310,49 @@ def resolve_rc_java(opts):
return best[1] if best else None


def resolve_robocode_version(robocode_home):
"""Return the classic Robocode release installed at *robocode_home*.

The release notes are the authoritative version marker in a normal install.
A uniquely versioned engine library is a fallback for stripped-down installs.
"""
root = Path(robocode_home)
versions_file = root / "versions.md"
try:
text = versions_file.read_text(encoding="utf-8")
except (OSError, UnicodeError):
text = ""
match = re.search(r"(?m)^## Version\s+([^\s(]+)", text)
if match:
return match.group(1)

versions = set()
for jar in (root / "libs").glob("robocode.*-*.jar"):
match = re.search(r"-(\d+(?:\.\d+)+)\.jar$", jar.name)
if match:
versions.add(match.group(1))
return next(iter(versions)) if len(versions) == 1 else None


def robocode_version_error(robocode_home, version=None):
"""Return a friendly validation error, or None for a LiteRumble client."""
version = version or resolve_robocode_version(robocode_home)
if version is None:
return (f"classic Robocode version could not be determined at {robocode_home}; "
"refusing to create unpinned LiteRumble evidence")
if version not in LITERUMBLE_ALLOWED_ROBOCODE_CLIENTS:
allowed = ", ".join(LITERUMBLE_ALLOWED_ROBOCODE_CLIENTS)
return (f"classic Robocode {version} is not in LiteRumble's allowed client list "
f"({allowed}); use one of those versions for comparable evidence")
return None


def validate_robocode_version(opts):
"""Resolve and validate the classic client, retaining it on the options object."""
opts.robocode_version = resolve_robocode_version(opts.robocode_home)
return robocode_version_error(opts.robocode_home, opts.robocode_version)


def kill_process_tree(proc: subprocess.Popen):
"""Kills a process and all of its children (bot JVMs, embedded server, booter)."""
if proc.poll() is not None:
Expand Down Expand Up @@ -457,6 +505,8 @@ def attach_error_signatures(result):
def registry_manifest(opts):
"""Pins the artifacts that produced a registry observation."""
manifest = {
"robocode_version": getattr(opts, "robocode_version", None)
or resolve_robocode_version(opts.robocode_home),
"bridge_commit": subprocess.run(
["git", "rev-parse", "HEAD"], cwd=BASE_DIR.parent,
capture_output=True, text=True, check=False).stdout.strip() or None,
Expand Down Expand Up @@ -694,8 +744,19 @@ def team_member_dirs(team_dir: Path):
"""Returns the generated sibling directories named by a team boot entry."""
config = team_dir / f"{team_dir.name}.json"
try:
data = json.loads(config.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
text = config.read_text(encoding="utf-8")
except UnicodeDecodeError:
# Legacy robot metadata can contain Windows-1252 author names even though the
# wrapper's JSON structure and member names are otherwise ordinary text.
try:
text = config.read_text(encoding="cp1252")
except (OSError, UnicodeError):
return []
except OSError:
return []
try:
data = json.loads(text)
except json.JSONDecodeError:
return []
members = data.get("teamMembers")
if not isinstance(members, list):
Expand Down Expand Up @@ -1107,6 +1168,9 @@ def check_prerequisites(opts):
]:
if not Path(path).exists():
problems.append(f" - {label} not found: {path}")
version_error = validate_robocode_version(opts)
if version_error:
problems.append(f" - {version_error}")
if shutil.which("java") is None:
problems.append(" - 'java' not found on PATH (JDK 17+ required)")
if "meleerumble" in opts.collections:
Expand Down Expand Up @@ -1792,6 +1856,10 @@ def main():
return 0

if opts.sync_registry:
version_error = validate_robocode_version(opts)
if version_error:
print(version_error, file=sys.stderr)
return 2
added = sync_parity_registry(state, opts)
print(f"Parity registry synchronized: {added} observation(s) added.")
return 0
Expand Down Expand Up @@ -1823,6 +1891,7 @@ def main():
f"{len(todo)} to test.")

tested = 0
checkpoint_state = {"robots": {}}
session_started = time.time()
try:
for collection, jar in todo:
Expand Down Expand Up @@ -1866,6 +1935,7 @@ def main():
}
if retest:
state["robots"][key]["retest"] = retest
checkpoint_state["robots"][key] = state["robots"][key]
save_state(state)
regenerate_report(state)
tested += 1
Expand Down Expand Up @@ -1903,6 +1973,7 @@ def main():
}
if retest:
state["robots"][key]["retest"] = retest
checkpoint_state["robots"][key] = state["robots"][key]
save_state(state)
regenerate_report(state)
tested += 1
Expand All @@ -1917,12 +1988,12 @@ def main():
print("\nInterrupted — progress saved. Re-run to resume.", file=sys.stderr)
save_state(state)
regenerate_report(state)
sync_parity_registry(state, opts)
sync_parity_registry(checkpoint_state, opts)
return 130

save_state(state)
regenerate_report(state)
sync_parity_registry(state, opts)
sync_parity_registry(checkpoint_state, opts)
elapsed_min = (time.time() - session_started) / 60
print(f"\nDone. Tested {tested} robots in {elapsed_min:.1f} min. "
f"Report: {REPORT_FILE}")
Expand Down
Loading
Loading