Compare commits

..

2 commits

3 changed files with 104 additions and 19 deletions

View file

@ -1,30 +1,30 @@
# SotS RE campaign — coverage dashboard
Generated 2026-09-08 05:30 UTC · `sots-re` @ cdada28,2026-09-08 · `sots-engine` @ e1cbb81,2026-09-08 (65 commits) · regenerate with `tools/dashboard.py`
Generated 2026-09-08 06:13 UTC · `sots-re` @ 3b5eca2,2026-09-08 · `sots-engine` @ 7584bad,2026-09-08 (68 commits) · regenerate with `tools/dashboard.py`
> **North star:** A functional reimplementation of the engine — behavior-equivalent, NOT byte-for-byte
## 1. Map coverage (campaign/board.md)
63 targets · mapped-or-better **48/63** `[████████░░] 76%` · verified **31/63** `[█████░░░░░] 49%`
65 targets · mapped-or-better **50/65** `[████████░░] 77%` · verified **33/65** `[█████░░░░░] 51%`
| Status | Count | % |
|---|---:|---:|
| verified | 31 | 49% |
| mapped | 17 | 27% |
| in-progress | 1 | 2% |
| backlog | 12 | 19% |
| verified | 33 | 51% |
| mapped | 17 | 26% |
| in-progress | 3 | 5% |
| backlog | 11 | 17% |
| blocked | 1 | 2% |
| Type | verified | mapped | in-progress | backlog | blocked | total |
|---|---:|---:|---:|---:|---:|---:|
| objects | 5 | 2 | 0 | 0 | 0 | 7 |
| control-flow | 0 | 2 | 0 | 0 | 0 | 2 |
| subsystems | 2 | 6 | 1 | 3 | 1 | 13 |
| subsystems | 2 | 6 | 2 | 3 | 1 | 14 |
| engine | 10 | 0 | 0 | 0 | 0 | 10 |
| verify | 5 | 2 | 0 | 6 | 0 | 13 |
| phase2 | 5 | 1 | 0 | 2 | 0 | 9 |
| meta | 4 | 4 | 0 | 1 | 0 | 9 |
| verify | 5 | 2 | 1 | 5 | 0 | 13 |
| phase2 | 6 | 1 | 0 | 2 | 0 | 9 |
| meta | 5 | 4 | 0 | 1 | 0 | 10 |
## 2. Binary understanding
@ -54,10 +54,10 @@ Generated 2026-09-08 05:30 UTC · `sots-re` @ cdada28,2026-09-08 · `sots-engine
| `mars/stream` | 3,703 | 6 | 222 | yes | mars-stream.md |
| `mars/text` | 899 | 8 | 245 | yes | mars-text.md |
| `mars/vfs` | 788 | 9 | 140 | yes | mars-vfs.md |
| `shim` | 7,551 | 0 | 0 | direct (WIN32) | M0.md |
| `shim/hooks` | 4,981 | 0 | 0 | direct (WIN32) | M0.md |
| `shim` | 7,620 | 0 | 0 | direct (WIN32) | M0.md |
| `shim/hooks` | 5,050 | 0 | 0 | direct (WIN32) | M0.md |
| `shim/trace` | 2,258 | 9 | 273 | direct (WIN32) | shim-trace.md |
| **total** | **28,506** | **89** | **2830** | | |
| **total** | **28,644** | **89** | **2830** | | |
Board `engine:` rows: verified **10**, mapped 0, in flight 0 (of 10) — verified & merged `[██████████] 100%`
@ -94,10 +94,10 @@ Most recent open:
## 8. Delta since previous dashboard
- verified targets: 30 → 31 (+1) · mapped-or-better: 47 → 48 (+1)
- engine LOC: 26,883 → 28,506 (+1,623) · test files: 88 → 89 (+1) · checks: 2,805 → 2,830 (+25)
- verified targets: 33 → 33 (+0) · mapped-or-better: 50 → 50 (+0)
- engine LOC: 28,644 → 28,644 (+0) · test files: 89 → 89 (+0) · checks: 2,830 → 2,830 (+0)
- addresses verified: 317 → 317 (+0) · recovered layouts: 21 → 21 (+0) · open questions: 27 → 27 (+0)
---
warnings: board.md line 66: unknown status 'code-complete' for 'P2-B4 colony + movement (behavioral)'; mars-rng.md: no oracle total row parsed; mars-stream.md: no oracle total row parsed; mars-vfs.md: no oracle total row parsed
<!-- dashboard-metrics {"verified": 31, "mapped_plus": 48, "targets": 63, "loc": 28506, "tests": 89, "checks": 2830, "addr_verified": 317, "addr_total": 324, "layouts": 21, "open_q": 27} -->
warnings: mars-rng.md: no oracle total row parsed; mars-stream.md: no oracle total row parsed; mars-vfs.md: no oracle total row parsed
<!-- dashboard-metrics {"verified": 33, "mapped_plus": 50, "targets": 65, "loc": 28644, "tests": 89, "checks": 2830, "addr_verified": 317, "addr_total": 324, "layouts": 21, "open_q": 27} -->

View file

@ -58,7 +58,7 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`).
| P2-B3 ProcessResearch (behavioral, RNG) | phase2 | mapped | high | 85% | 2026-09-08 | LIVE, PARTIAL PASS: 15 calls compared, 13 zero-divergence; RNG post-state matched 14/15 incl. every roll (validates MT19937 + draw mapping + odds together). 2 divergences are the declared SetResearched boundary. ORACLE FAILS by exactly one item across 40,300: an unposted EVENT_RESEARCH_OVERBUDGET - compare was blind because the event list was never a declared region. fpu_cw=0x127f => 53-bit double, x87 question SETTLED. No Zuul in the save: double roll still disassembly-only |
| RNG signatures (Ghidra) | meta | verified | high | 100% | 2026-09-08 | Seed/Twist/NextFloat/NextInt verified; draw = y/(2^32-1); NextInt [0,n] inclusive; lazy twist; left@+0x9c4. RUNTIME CONFIRMED: fpu_cw=0x127f (53-bit double, round-nearest) - our next_float model is right, float_from_pc24 is an unused contingency |
| engine: game/effects | engine | verified | high | 100% | 2026-09-08 | merged: TechId enum (196 slots @10000+i), 44 ids with typed strategic effects, species flag bits, ApplyTechEffect; 254 checks |
| VM140 exclusivity (lab rule) | meta | verified | high | 100% | 2026-09-08 | one agent at a time. Holder: B4 (window open). QUEUE: empty. Windows Update now DISABLED/paused on the VM (was rebooting mid-run). Non-holders build /srv/re-lab/build/sots-engine-<lane>, stage dist-<lane>, deploy C:\SOTS\shimdist-<lane> |
| VM140 exclusivity (lab rule) | meta | verified | high | 100% | 2026-09-08 | one agent at a time. Holder: R-recapture (window open 2026-09-08). QUEUE: empty. Windows Update now DISABLED/paused on the VM (was rebooting mid-run). Non-holders build /srv/re-lab/build/sots-engine-<lane>, stage dist-<lane>, deploy C:\SOTS\shimdist-<lane> |
| Zuul double-roll (behavioural) | verify | backlog | — | 0% | 2026-09-08 | CONFIRMED NEEDED: ref-turn2 has only species 0 and 2, so the double roll is verified by disassembly + host tests only. Needs one compare from a species-5 save; the check is just that `left` drops by 2 not 1 |
| budget tail coverage (expenses/aid/debt) | verify | backlog | — | 0% | 2026-09-08 | 8 ComputeBudget slots were always 0 in ref-turn2 (no sliders, no aid, no debt, no handicap). Need a save with expense sliders, a debtor and a research-aid treaty to exercise ExpenseTotal + the aid/bonus tail |
| hook GetDifficultyMods | meta | backlog | — | 0% | 2026-09-08 | B1 derived the two difficulty rows from trace values (AI maintenance divisor 3, research x1.5) instead of snapshotting them; hook it properly so they stop being constants |
@ -66,5 +66,7 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`).
| P2-B4 colony + movement (behavioral) | phase2 | verified | high | 100% | 2026-09-08 | LIVE: 36 calls compared, 0 divergences, tracecmp exit 0. Scout's headline: RNG left-delta 0 and mt hash identical on ALL 28 systems (only ProcessRebellion draws; none fired) - fpu_cw 0x127f confirmed. One real fleet move reproduced bit-for-bit; PlanFleetMovement's schedule matched. 3 prototypes + 6 helpers VERIFIED and written back to Ghidra; 22 formula corrections. THREE hook bugs found by reading the trace, not the verdict (stale args from describe_args-before-regions; StrategyServer has TWO bases 4 bytes apart; off_Fleets was a Ghidra-base number used as raw) - each would have given a clean compare that checked nothing. COVERAGE IS THIN: only 3 owned systems, 1 moving fleet, gate traffic all-zero; bats2, plague, rebellion, slaves, terraform, jumps, arrivals untested. No replace mode (input boundary). VM released: main menu, hooks=trace, build b4-fix2-20260908T0615Z |
| harness gap: undeclared side-effect lists | verify | verified | high | 100% | 2026-09-08 | FIXED STRUCTURALLY (engine 3f0721f+): compile-time-required Coverage on every descriptor (a hook without one does not compile); Guard regions that catch AND localise undeclared writes (names player+0x2b0, not 'the hash moved'); replace mode now emits records; tracecmp prints coverage on every report + --strict-coverage. A hook claiming 'complete' while a guard caught an undeclared write now counts as a DIVERGENCE. Audit found 23 undeclared side effects: docs/harness-audit.md |
| RollResearchEvent draw (behavioural) | verify | backlog | — | 0% | 2026-09-08 | ServerPlayer::RollResearchEvent draws one NextFloat when the completing def is the current target and +0x3b4 is set. Region declared, ours reproduces it, but the branch never fired on ref-turn2 - needs a workload that sets research_roll_pending |
| golden-trace recapture (post-guards) | verify | backlog | — | 0% | 2026-09-08 | region sets changed, so goldens must be recaptured: ProcessResearch (expect NEW divergences - the event defect becoming visible), ComputeBudget (verdict should stay 0), and first captures for OnTechResearched / ServerSystem::ProcessTurn / MoveFleet |
| golden-trace recapture (post-guards) | verify | in-progress | — | 0% | 2026-09-08 | region sets changed, so goldens must be recaptured: ProcessResearch (expect NEW divergences - the event defect becoming visible), ComputeBudget (verdict should stay 0), and first captures for OnTechResearched / ServerSystem::ProcessTurn / MoveFleet |
| B1 replace double-run | verify | backlog | — | 0% | 2026-09-08 | ComputeBudget replace mode runs the original a second time to harvest budget slots; ComputeOutput repairs ships in orbit as a side effect, so this is a real per-turn double effect on objects no region covers. Needs a design fix (harvest without re-running, or declare+revert) |
| ReVa MCP link drop (workaround) | meta | verified | high | 100% | 2026-09-08 | The ReVa MCP client link dropped mid-session while the CT111 server stayed healthy (systemd active, :8080 listening, valid key -> 200). `tools/reva_call.py <tool> '<json>'` calls the same server over plain HTTP (initialize -> notifications/initialized -> tools/call; replies are SSE with a leading `id:` line, initialize is plain JSON). Key is NEVER stored in the repo: $REVA_KEY, else ~/.claude.json, else ssh to the CT properties file. Use this whenever mcp__plugin_ReVa_ReVa__* is unavailable |
| event posting API | subsystem | in-progress | — | 0% | 2026-09-08 | Closes B3's oracle gap (unposted EVENT_RESEARCH_OVERBUDGET, the ONE item wrong across 40,300) and B2's known gap (ours posts no events, so both clean compares bound economy fields only). Needs: the player event list container, the post/append entry point, the event record layout |

83
tools/reva_call.py Executable file
View file

@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""Call a ReVa (Ghidra) MCP tool over plain HTTP.
Fallback for when the ReVa MCP client link drops but the server on CT111 is fine.
Usage:
tools/reva_call.py <tool-name> '<json-args>'
tools/reva_call.py --list
The API key is never stored here. It is read from, in order:
$REVA_KEY, then ~/.claude.json, then CT111's reva-lab.properties over ssh.
"""
import json
import os
import re
import subprocess
import sys
import urllib.request
URL = os.environ.get("REVA_URL", "http://192.168.10.138:8080/mcp/message")
def api_key():
if os.environ.get("REVA_KEY"):
return os.environ["REVA_KEY"]
cfg = os.path.expanduser("~/.claude.json")
if os.path.exists(cfg):
m = re.search(r'"X-API-Key"\s*:\s*"([0-9a-f]{16,})"', open(cfg, encoding="utf-8").read())
if m:
return m.group(1)
out = subprocess.run(
["ssh", "spicy", "pct exec 111 -- grep '^reva.server.options.api.key=' /opt/reva-src/reva-lab.properties"],
capture_output=True, text=True, timeout=30)
m = re.search(r"api\.key=(\S+)", out.stdout)
if m:
return m.group(1)
sys.exit("no ReVa API key found (set $REVA_KEY)")
def rpc(key, method, params, session=None, notify=False):
msg = {"jsonrpc": "2.0", "method": method, "params": params}
if not notify:
msg["id"] = 1
req = urllib.request.Request(URL, data=json.dumps(msg).encode(), headers={
"X-API-Key": key,
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
**({"Mcp-Session-Id": session} if session else {}),
})
with urllib.request.urlopen(req, timeout=300) as r:
sid = r.headers.get("Mcp-Session-Id") or session
raw = r.read().decode("utf-8", "replace").strip()
if not raw:
return {}, sid
# tools/* replies come back as SSE (id:/event:/data: lines); initialize is plain JSON.
if not raw.startswith("{"):
raw = "".join(ln[5:].strip() for ln in raw.splitlines() if ln.startswith("data:"))
return json.loads(raw), sid
def main():
if len(sys.argv) < 2:
sys.exit(__doc__)
key = api_key()
_, sid = rpc(key, "initialize", {
"protocolVersion": "2024-11-05", "capabilities": {},
"clientInfo": {"name": "reva_call", "version": "1"}})
rpc(key, "notifications/initialized", {}, sid, notify=True)
if sys.argv[1] == "--list":
res, _ = rpc(key, "tools/list", {}, sid)
for t in res.get("result", {}).get("tools", []):
print(f"{t['name']}: {t.get('description','')[:110]}")
return
args = json.loads(sys.argv[2]) if len(sys.argv) > 2 else {}
res, _ = rpc(key, "tools/call", {"name": sys.argv[1], "arguments": args}, sid)
if "error" in res:
print(json.dumps(res["error"], indent=2))
sys.exit(1)
for c in res.get("result", {}).get("content", []):
print(c.get("text", json.dumps(c)))
if __name__ == "__main__":
main()