Files
settled-reach/tests/run-atlas-determinism
T
jpmschweitzerandClaude Opus 4.6 b86bb00a55 refactor(simulation): PR #137 review — audit override + regression tests + docs
Addresses Tyre, Hoshe, and lead review comments on PR #137:

- **Audit doc amendment** (Tyre E1 / Hoshe H1 / Lead): add "Lead override
  (2026-04-21)" section at top of docs/architecture/sprint-37-878-audit.md.
  Rewrites the conclusion to "DECISION: STRIP" with the cascade-based
  rationale. Preserves the original audit body as the pre-override record.

- **Regression tests** (Lead 2a-2b / Hoshe H2 / H3): add POSITIVE
  assertions of the new uniform behavior so silent reintroduction fails.
  - `phase2_container_verb_labels_uniform_regardless_of_player_state` —
    two trials (empty KG, POI-bearing KG) assert container verb labels
    equal Phase-1 defaults.
  - `monologue_pool_selection_uniform_no_archetype_key` — two observers
    with divergent MonologueState both draw from OBSERVE_NPC_LINES.

- **Decision record amendments** (Lead 3 / Tyre S2): D-032, D-035, and
  D-057 amended with Phase 6 deferral wording. "Retired pending Phase 6,
  not deferred with scaffolding." Reintroduction gate: a confirmed
  Phase 6 character-model design.

- **types.rs doc fixes** (Tyre S1 / Hoshe H5): StartupMessage protocol-
  flow comment updated to reflect no-version handshake (D-192).
  ObserverSnapshot version-history block grows a "Sprint 37 wire-format
  shifts" section documenting D-192 + #878 schema drops.

- **observer/tests.rs:944 comment** (Hoshe H6): rewritten to cite
  cascade rationale instead of the stale D-032-SUPERSEDED premise.

- **tests/run-atlas-determinism exit** (Hoshe H7): exit 0 when EXIT_CODE=2
  (venv/DB missing = skip, not fail). Preserves skip semantics for
  tests/run-all on machines without the Python venv.

Follow-up tickets filed:
- #895 (server, low): expand check-systems-db-stamp GENERATOR_SOURCES
  to cover gemma_naming.py + naming_core.py (Tyre S3).
- #896 (planning, low): add CLAUDE.md carveout for server wiki writes
  closing coverage gates (Tyre S4 / Hoshe H8).

H4 investigation: v01_integration_playthrough.rs was not the only E2E
handshake→tick→snapshot test; coverage preserved by bridge_ipc.rs,
bridge_tcp.rs, and game_loop.rs (the latter is pre-existing-broken
per #885). No replacement test needed.

1142/1142 lib tests pass. cargo clippy -- -D warnings clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 10:09:35 +02:00

140 lines
4.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# tests/run-atlas-determinism: Determinism smoke test for generate_atlas.py (#847)
#
# Imports generate_atlas as a Python module, calls process_body() twice with
# seed=42 and dry_run=True, compares the returned markers dicts as JSON.
# No wiki files are written or modified.
#
# Purpose: cheap guardrail against determinism regressions in terrain analysis,
# city placement, A* road routing, infrastructure MST, and gate terminal
# placement. GJ892f is a domed body (population=300, 1 city) — the smallest
# well-exercised case in the atlas pipeline.
#
# Spec ref: #847
# Exit: 0 = deterministic (pass), non-zero = failure
# Stdout: {"suite":"atlas-determinism","total":1,"passed":N,"failed":N,"duration_ms":N}
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
START_MS=$(date +%s%3N)
# Write the comparison script to a real file so that the generate_atlas venv
# bootstrap (os.execv) can re-exec it from the venv Python when needed.
# A heredoc (python3 - <<'EOF') does not work after os.execv because stdin
# has already been consumed.
HELPER=$(mktemp /tmp/atlas_det_helper.XXXXXX.py)
trap "rm -f '$HELPER'" EXIT
cat > "$HELPER" << 'PYEOF'
"""Atlas determinism helper — called by tests/run-atlas-determinism (#847).
Imports generate_atlas as a module and calls process_body() twice with
dry_run=True. Compares the returned markers dicts as JSON. Exits 0 if
identical, 1 if they differ, 2 on setup/import failure.
"""
import json
import os
import sqlite3
import sys
from pathlib import Path
REPO_ROOT = Path(os.environ["SR_REPO_ROOT"])
sys.path.insert(0, str(REPO_ROOT / "tooling" / "planet-gen"))
try:
import generate_atlas
except ImportError as e:
print(f"SKIP: generate_atlas import failed (missing deps?): {e}", file=sys.stderr)
sys.exit(2)
DB_PATH = REPO_ROOT / "server" / "data" / "systems.db"
BODY_ID = "GJ892f" # domed, pop=300 → exactly 1 city; minimal and fast
SEED = 42
if not DB_PATH.exists():
print(f"SKIP: systems.db not found: {DB_PATH}", file=sys.stderr)
sys.exit(2)
conn = sqlite3.connect(str(DB_PATH))
conn.execute("PRAGMA foreign_keys=ON")
row = conn.execute("""
SELECT b.body_id, b.system_id, b.terrain_reference, b.population,
b.settlement_pattern, b.planet_class, b.economic_role,
COALESCE(b.cultural_corridor, s.cultural_corridor)
FROM bodies b JOIN star_systems s ON b.system_id = s.system_id
WHERE b.body_id = ?
""", (BODY_ID,)).fetchone()
conn.close()
if not row:
print(f"SKIP: {BODY_ID} not found in systems.db", file=sys.stderr)
sys.exit(2)
body_info = dict(zip(
("body_id", "system_id", "terrain_reference", "population",
"settlement_pattern", "planet_class", "economic_role", "cultural_corridor"),
row,
))
body_info["population"] = body_info["population"] or 0
kwargs = dict(
body_info=body_info,
seed=SEED,
noise_factor=0.25,
dry_run=True, # no files written
force=True, # skip the already-populated check
verbose=False,
)
a = generate_atlas.process_body(**kwargs)
b = generate_atlas.process_body(**kwargs)
if a["status"] != "generated":
print(f"FAIL: run 1 status={a['status']} — {a.get('message', '')}", file=sys.stderr)
sys.exit(1)
if b["status"] != "generated":
print(f"FAIL: run 2 status={b['status']} — {b.get('message', '')}", file=sys.stderr)
sys.exit(1)
json_a = json.dumps(a["markers"], indent=2)
json_b = json.dumps(b["markers"], indent=2)
if json_a == json_b:
print(f"PASS: {BODY_ID} markers identical on two runs (seed={SEED})", file=sys.stderr)
sys.exit(0)
else:
import difflib
diff = "\n".join(list(difflib.unified_diff(
json_a.splitlines(), json_b.splitlines(), lineterm="",
fromfile="run1", tofile="run2",
))[:40])
print(f"FAIL: {BODY_ID} markers differ between run 1 and run 2 (seed={SEED})", file=sys.stderr)
print(diff, file=sys.stderr)
sys.exit(1)
PYEOF
set +e
SR_REPO_ROOT="$REPO_ROOT" python3 "$HELPER" 2>&1 >&2
EXIT_CODE=$?
set -e
END_MS=$(date +%s%3N)
DURATION_MS=$((END_MS - START_MS))
# Exit code 2 = setup failure / missing deps → count as 0 tests (skip, not fail)
if [[ $EXIT_CODE -eq 0 ]]; then
PASSED=1; FAILED=0; TOTAL=1
elif [[ $EXIT_CODE -eq 2 ]]; then
PASSED=0; FAILED=0; TOTAL=0
echo " [atlas-determinism] SKIPPED (import failure or missing DB)" >&2
else
PASSED=0; FAILED=1; TOTAL=1
fi
printf '{"suite":"atlas-determinism","total":%d,"passed":%d,"failed":%d,"duration_ms":%d}\n' \
"$TOTAL" "$PASSED" "$FAILED" "$DURATION_MS"
# Exit code 2 = venv/DB missing → treated as skip, not failure (exit 0 for run-all).
# Exit code 1 = determinism failure → exit 1 to fail CI.
[[ $EXIT_CODE -eq 2 ]] && exit 0 || exit $EXIT_CODE