Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
117 lines
6.3 KiB
Python
117 lines
6.3 KiB
Python
"""Reproduce preservation defects, then verify an isolated SaveService fix."""
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import time
|
|
import uuid
|
|
|
|
ROOT = Path(__file__).resolve().parents[4]
|
|
OUT = ROOT / "artifacts/original-completion-2026-10-02/p03-save-preservation-r02"
|
|
PACKET = ROOT / "artifacts/original-completion-2026-10-02/p03-recovery-readonly-candidate"
|
|
TARGET = "scripts/core/save_service.gd"
|
|
|
|
|
|
def sha(path):
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def write(path, value):
|
|
path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n")
|
|
|
|
|
|
def main():
|
|
OUT.mkdir(parents=True, exist_ok=False)
|
|
spec = importlib.util.spec_from_file_location("source_regression", Path(__file__).with_name("run_current_regression.py"))
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
source = module.source_map()
|
|
assert sha(ROOT / TARGET) == sha(PACKET / "save_service.base.gd")
|
|
write(OUT / "source-start.json", source)
|
|
run_id = uuid.uuid4().hex[:12]
|
|
stages = Path("/private/tmp") / ("thetree-save-preservation-" + run_id)
|
|
driver_sha = sha(PACKET / "test_recovery_readonly_candidate.gd")
|
|
report = {"scope": "identified foreign format preservation and explicit read-only recovery", "stages": {}, "rows": [], "production_applied": False, "driver_sha256": driver_sha}
|
|
for variant in ("base", "candidate"):
|
|
stage = stages / variant
|
|
stage.mkdir(parents=True)
|
|
for name, expected in source.items():
|
|
dest = stage / name
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(ROOT / name, dest)
|
|
assert sha(dest) == expected
|
|
profile = "TheTreeSavePreservation-" + run_id + "-" + variant
|
|
config = (stage / "project.godot").read_text()
|
|
(stage / "project.godot").write_text(config.replace("[application]", '[application]\nconfig/use_custom_user_dir=true\nconfig/custom_user_dir_name="' + profile + '"', 1))
|
|
qa = stage / "tests/diagnostic/save_preservation"
|
|
qa.mkdir(parents=True)
|
|
shutil.copyfile(PACKET / "test_recovery_readonly_candidate.gd", qa / "acceptance.gd")
|
|
assert sha(qa / "acceptance.gd") == driver_sha
|
|
if variant == "candidate":
|
|
shutil.copyfile(PACKET / "save_service.candidate.gd", stage / TARGET)
|
|
report["stages"][variant] = {"path": str(stage), "profile": profile, "target_sha256": sha(stage / TARGET)}
|
|
write(OUT / "execution.json", report)
|
|
|
|
def run(variant, label, script=None, reproduction=False):
|
|
stage = Path(report["stages"][variant]["path"])
|
|
command = ["godot", "--headless", "--path", str(stage)]
|
|
command += ["--script", "res://" + script] if script else ["--editor", "--import"]
|
|
log = OUT / (label + ".log")
|
|
started = time.monotonic()
|
|
with log.open("w") as stream:
|
|
try:
|
|
code = subprocess.run(command, cwd=stage, env=dict(os.environ), stdout=stream, stderr=subprocess.STDOUT, timeout=900).returncode
|
|
except subprocess.TimeoutExpired:
|
|
code = 124
|
|
content = log.read_text()
|
|
counts = re.findall(r"^Recovery read-only candidate: (\d+) checks, (\d+) failures$", content, re.MULTILINE)
|
|
actual_checks = int(counts[0][0]) if len(counts) == 1 else None
|
|
if script and not reproduction and actual_checks is None:
|
|
old_report = json.loads((ROOT / "artifacts/facility-need-pricing-r02-full155-independent-review-2026-10-01-r01/actual155-source-marker-review.json").read_text())
|
|
old_markers = {row["test"]: row["actual_marker"] for row in old_report["rows"]}
|
|
marker = old_markers.get(script)
|
|
if marker is None:
|
|
matches = re.findall(r"^.*?: \d+ checks, 0 failures\n", content, re.MULTILINE)
|
|
marker = matches[0] if len(matches) == 1 else None
|
|
assert marker is not None and content.count(marker) == 1, label
|
|
match = re.search(r"checks=(\d+)|(\d+)(?: [A-Za-z]+)* checks", marker)
|
|
assert match is not None
|
|
actual_checks = int(next(g for g in match.groups() if g is not None))
|
|
failures = int(counts[0][1]) if len(counts) == 1 else None
|
|
if reproduction:
|
|
passed = code != 0 and failures is not None and failures > 0 and "SCRIPT ERROR:" not in content
|
|
else:
|
|
passed = code == 0 and "SCRIPT ERROR:" not in content and "\nERROR:" not in content and "Unicode parsing error" not in content
|
|
row = {"variant": variant, "label": label, "exit_code": code, "passed": passed,
|
|
"expected_defect_reproduction": reproduction, "checks": actual_checks,
|
|
"failures": failures, "seconds": round(time.monotonic() - started, 3), "log": log.name, "log_sha256": sha(log)}
|
|
report["rows"].append(row)
|
|
write(OUT / "execution.json", report)
|
|
print(json.dumps(row), flush=True)
|
|
assert passed, label
|
|
|
|
run("base", "base-import")
|
|
run("candidate", "candidate-import")
|
|
run("base", "base-defects", "tests/diagnostic/save_preservation/acceptance.gd", True)
|
|
run("candidate", "candidate-acceptance", "tests/diagnostic/save_preservation/acceptance.gd")
|
|
for script in ("tests/s6/test_save_preflight.gd", "tests/s6/test_save_operations.gd", "tests/s6/test_runtime_save_lifecycle.gd", "tests/run_tests.gd", "tests/transport/test_transport_save.gd", "tests/ui/test_dimension_migration_ui.gd", "tests/ui/test_supply_management.gd"):
|
|
if not (Path(report["stages"]["candidate"]["path"]) / script).is_file():
|
|
# Resolve only exact repository test filenames, never silently skip.
|
|
raise AssertionError("missing required suite " + script)
|
|
run("candidate", script.replace("/", "__").replace(".gd", ""), script)
|
|
after = module.source_map()
|
|
changes = [p for p in sorted(set(source) | set(after)) if source.get(p) != after.get(p)]
|
|
write(OUT / "source-end.json", after)
|
|
report.update(focused_acceptance_passed=True, source_changes=changes, full_merged_regression_pending=True, original_full_acceptance=False)
|
|
write(OUT / "execution.json", report)
|
|
assert not changes
|
|
print("Save preservation focused acceptance passed; production merge and full regression remain pending.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|