From 990481709c7aa8643cce4fd3c662b314fcb4a306 Mon Sep 17 00:00:00 2001 From: Agent Control Runner Date: Tue, 20 Oct 2026 20:47:01 +0900 Subject: [PATCH] =?UTF-8?q?P28-01-COLLECTOR:=20=E7=8F=BEHEAD=E3=81=AE?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E5=9F=BA=E6=BA=96=EF=BC=9Awindowed=20x1/x3?= =?UTF-8?q?=E3=83=BBOperations=20ON=20collector?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../p28-01/collector/check_p28_01_report.py | 277 +++++++++++++ .../p28-01/collector/collect_p28_01.py | 322 ++++++++++++++++ .../p28-01/collector/measure_p28_01.gd | 364 ++++++++++++++++++ .../p28-01/collector/test_collect_p28_01.py | 211 ++++++++++ 4 files changed, 1174 insertions(+) create mode 100644 docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py create mode 100644 docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py create mode 100644 docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd create mode 100644 docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py new file mode 100644 index 0000000..abb9a53 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +"""Structural/consistency gate, not proof of execution or truthful observations.""" +import argparse +import hashlib +import json +import math +from pathlib import Path +import re + +PREFIX = "P28_01_PERF_REPORT=" +PHASES = {"R0": ("R", 1, 0, 10), "P1": ("R", 1, 30, 120), + "P2": ("R", 3, 30, 120), "O0": ("O", 1, 0, 10), + "P3": ("O", 1, 30, 120), "P4": ("O", 3, 30, 120)} +FORBIDDEN = ("_toggle_pause", "clear(", "enable_production_", "set_paused(false)", + "advance_", "spawn_guest", "SimulationRuntime.new", "initialize(") + + +def need(ok, reason): + if not ok: + raise ValueError(reason) + + +def keys(obj, names): + need(isinstance(obj, dict), "expected object") + need(set(names) <= obj.keys(), "missing fields: " + str(set(names) - obj.keys())) + + +def number(value): + return type(value) in (int, float) and math.isfinite(value) + + +def sha_string(value): + return isinstance(value, str) and re.fullmatch("[0-9a-f]{64}", value) is not None + + +def nullable(obj, field): + need(field in obj, "missing " + field) + if obj[field] is None: + need(bool(obj.get(field + "_reason")), "null without reason: " + field) + + +def stats(values, reason="no_samples"): + if not values: + return dict(count=0, mean=None, p95=None, max=None, reason=reason) + ordered = sorted(values) + return dict(count=len(values), mean=sum(values) / len(values), + p95=ordered[math.ceil(len(values) * .95) - 1], max=max(values), reason=None) + + +def equal(actual, expected, label): + if isinstance(expected, dict): + need(isinstance(actual, dict) and actual.keys() == expected.keys(), label + " keys") + for key in expected: + equal(actual[key], expected[key], label + "." + key) + elif number(expected): + need(number(actual) and math.isclose(actual, expected, rel_tol=1e-9, abs_tol=1e-7), label) + else: + need(actual == expected, label) + + +def metadata(meta): + keys(meta, ("machine", "build")) + machine = meta["machine"] + keys(machine, ("hw.model", "cores", "memory_bytes")) + need(isinstance(machine["hw.model"], str) and machine["hw.model"], "model") + need(type(machine["cores"]) is int and machine["cores"] > 0, "cores") + need(type(machine["memory_bytes"]) is int and machine["memory_bytes"] > 0, "memory") + build = meta["build"] + keys(build, ("engine_string", "engine_hash", "engine_sha256", "project_commit", "source_map_sha256")) + need(build["engine_string"] == "4.7.2.stable.official.ed1daf0bf", "engine string") + need(build["engine_hash"] == "ed1daf0bf", "engine hash") + need(re.fullmatch("[0-9a-f]{40}", build["project_commit"]) is not None, "commit") + need(sha_string(build["engine_sha256"]) and sha_string(build["source_map_sha256"]), "build SHA") + + +def interval(report, name): + rows = [f for f in report["frames"] if f["segment"] == name] + value = report[name] + keys(value, ("frames", "actual_minutes", "start_minute", "end_minute", "start_wall", "end_wall", + "zero_minute_frames_excluded", "count_ranges", "budget", "stall_budget")) + need(value["frames"] == len(rows), "frame count") + need(value["actual_minutes"] == sum(f["minutes_advanced"] for f in rows), "false interval duration") + for field, expected in (("start_minute", rows[0]["game_start"] if rows else None), + ("end_minute", rows[-1]["game_end"] if rows else None), + ("start_wall", rows[0]["wall_start"] if rows else None), + ("end_wall", rows[-1]["wall"] if rows else None)): + equal(value[field], expected, field) + need(value.get("empty_reason") == ("no_frames" if not rows else None), "empty interval reason") + wall = [f["frame_wall_us"] for f in rows] + minutes = [f["frame_wall_us"] / f["minutes_advanced"] for f in rows if f["minutes_advanced"]] + equal(value["frame_wall_us"], stats(wall), "frame stats") + equal(value["sim_us_per_minute"], stats(minutes), "minute stats") + equal(value["stall_over_50000_us"], stats([x for x in wall if x > 50000]), "stalls") + equal(value["time_process_seconds"], stats([f["time_process_seconds"] for f in rows]), "process") + need(value["zero_minute_frames_excluded"] == sum(f["minutes_advanced"] == 0 for f in rows), "excluded frames") + for kind in ("cpu", "gpu"): + samples = [f["render_" + kind + "_raw_ms"] for f in rows] + expected = (stats([], "zero_from_api_on_gl_compatibility_unsupported_or_unmeasured") + if any(x <= 0 for x in samples) else stats(samples)) + equal(value["render_" + kind + "_ms"], expected, "render " + kind) + ranges = {} + for kind in ("registered", "active"): + ranges[kind] = {key: {"min": min(f[kind][key] for f in rows), "max": max(f[kind][key] for f in rows)} + for key in rows[0][kind]} if rows else {} + equal(value["count_ranges"], ranges, "count ranges") + metric = stats(minutes)["mean" if report["speed"] == 1 else "p95"] + limit = 4000 if report["speed"] == 1 else 12000 + equal(value["budget"], {"limit_us": limit, "value": metric, + "passed": metric <= limit if metric is not None else None, + "reason": "no_samples" if metric is None else None}, "budget") + maximum = max(wall) if wall else None + equal(value["stall_budget"], {"limit_us": 50000, "value": maximum, + "passed": maximum <= 50000 if maximum is not None else None, + "reason": "no_samples" if maximum is None else None}, "stall budget") + + +def check_report(report): + keys(report, ("schema", "pid", "phase", "input", "speed", "metadata", "input_sha256", + "frames", "stop_reason", "warmup", "measurement", "actual_minutes", + "elapsed_wall_seconds", "kernel_at_phase_end", "operations_at_end", "end_wall", + "window_note", "toggle_pause_calls", "performance_clear_calls", "overloads", "checkpoints")) + need(report["schema"] == "p28-01-v1", "schema") + need(type(report["pid"]) is int and report["pid"] > 0, "pid") + need(report["phase"] in PHASES, "unknown phase") + source, speed, warmup, target = PHASES[report["phase"]] + need((report["input"], report["speed"], report["warmup_target"], report["measurement_target"]) == + (source, speed, warmup, target), "phase parameters") + need(report["speed_path"] == "main._set_speed->SimulationKernel._process", "speed route") + need(report["toggle_pause_calls"] == 0 and report["performance_clear_calls"] == 0, "forbidden mutation") + need("512" in report["window_note"] and "warm-up" in report["window_note"], "rolling report note") + metadata(report["metadata"]) + nullable(report, "input_sha256") + if source == "R": + need(report["input_sha256"] == "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd", + "input SHA") + else: + need(report["input_sha256"] is None, "fresh input cannot claim fixture") + frames = report["frames"] + need(isinstance(frames, list), "frames") + need(report["stop_reason"] in ("complete", "input_shortfall", "simulation_overloaded", + "operations_runtime_failure", "operations_terminal", "operations_pause", "clock_paused", + "camera_changed", "timeout"), "stop reason") + # Shortfall reports preserve partial startup observations rather than invent them. + if report["stop_reason"] == "input_shortfall": + need(not frames and report["actual_minutes"] == 0, "shortfall after measurement") + return + keys(report, ("startup", "preparation_sha256", "seed", "rng_streams", "camera", + "start_minute", "start_wall", "speed_after", "paused_after_speed", "renderer")) + need(sha_string(report["preparation_sha256"]), "preparation SHA") + need(report["speed_after"] == speed and report["paused_after_speed"] is False, "start speed") + startup = report["startup"] + keys(startup, ("pause_fresh_opening", "save_policy", "save_policy_after", "start_report", + "operations_enabled_after", "opening_minute")) + need(startup["pause_fresh_opening"]["return"] is None and + startup["pause_fresh_opening"]["reason"] == "void_function", "pause return invented") + need(type(startup["pause_fresh_opening"]["paused_before"]) is bool and + type(startup["pause_fresh_opening"]["paused_after"]) is bool, "pause observations") + equal(startup["save_policy"], {"return": None, "reason": "return_discarded_by_main(main.gd:222)"}, + "save policy return") + need(isinstance(startup["save_policy_after"], dict), "save policy actual") + start = startup["start_report"] + need(isinstance(start, dict), "start_report must retain dictionary") + if not start: + need(bool(startup.get("start_report_reason")), "empty start report reason") + if source == "O": + need(start.get("ok") is True and startup["operations_enabled_after"] is True + and startup["opening_minute"] == 360, "O startup") + else: + need(start.get("code") == "INVALID_START_TIME" and startup["operations_enabled_after"] is False, "R startup") + previous = report["start_minute"] + previous_wall = report["start_wall"] + previous_drawing = None + for index, frame in enumerate(frames): + keys(frame, ("frame", "game_start", "game_end", "minutes_advanced", "frame_wall_us", "wall", "wall_start", + "speed", "paused", "segment", "registered", "active", "drawn", "seed", "rng_streams", + "metadata", "input_sha256", "preparation_sha256", "render_cpu_raw_ms", + "render_gpu_raw_ms", "time_process_seconds")) + need(frame["frame"] == index and frame["game_start"] == previous, "frame continuity") + need(type(frame["minutes_advanced"]) is int and 0 <= frame["minutes_advanced"] <= 8, "kernel frame cap") + need(frame["game_end"] - previous == frame["minutes_advanced"], "minute delta") + need(frame["speed"] == speed, "Clock speed disagreement") + need(number(frame["frame_wall_us"]) and frame["frame_wall_us"] > 0, "frame timing") + need(number(frame["wall"]) and frame["wall"] >= previous_wall, "wall chronology") + equal(frame["wall_start"], previous_wall, "frame wall continuity") + need(frame["segment"] == ("warmup" if previous - report["start_minute"] < warmup else "measurement"), + "segment boundary") + for field in ("seed", "rng_streams"): + nullable(frame, field) + need(frame["seed"] is None or type(frame["seed"]) is int, "seed is not scalar") + need(frame["rng_streams"] is None or isinstance(frame["rng_streams"], dict), "RNG state") + need(frame["seed"] != frame["rng_streams"] or frame["seed"] is None, "seed/RNG conflation") + metadata(frame["metadata"]) + need(frame["metadata"] == report["metadata"] and frame["seed"] == report["seed"], "sample metadata") + need(frame["input_sha256"] == report["input_sha256"] and + frame["preparation_sha256"] == report["preparation_sha256"], "sample SHA") + need(set(frame["registered"]) == {"guests", "staff", "facilities", "walkable_cells"}, "registered schema") + need(set(frame["active"]) == {"guests", "staff", "facilities"}, "active copied from registered") + for kind in ("registered", "active"): + need(all(type(x) is int and x >= 0 for x in frame[kind].values()), "counts") + need(all(frame["active"][k] <= frame["registered"][k] for k in frame["active"]), "active > registered") + drawn = frame["drawn"] + keys(drawn, ("actors_in_layer", "actors_in_viewport", "facilities", "cells", + "observation_serial", "carried_from_serial", "camera")) + need(all(type(drawn[k]) is int and drawn[k] >= 0 for k in + ("actors_in_layer", "actors_in_viewport", "facilities", "cells", "observation_serial")), "draw counts") + need(drawn["actors_in_viewport"] <= drawn["actors_in_layer"], "draw viewport") + if previous_drawing and drawn["observation_serial"] == previous_drawing["observation_serial"]: + need(drawn["carried_from_serial"] == drawn["observation_serial"], "carried observation called fresh") + equal({k: v for k, v in drawn.items() if k != "carried_from_serial"}, + {k: v for k, v in previous_drawing.items() if k != "carried_from_serial"}, "carried changed") + else: + need(drawn["carried_from_serial"] is None, "fresh drawing marked carried") + need(drawn["camera"] == report["camera"], "camera changed") + for field in ("render_cpu_raw_ms", "render_gpu_raw_ms", "time_process_seconds"): + need(number(frame[field]) and frame[field] >= 0, "raw time") + if frame["paused"]: + need(index == len(frames) - 1 and report["stop_reason"] != "complete", "continued after pause") + previous, previous_wall, previous_drawing = frame["game_end"], frame["wall"], drawn + need(report["actual_minutes"] == previous - report["start_minute"], "total minutes") + need(number(report["elapsed_wall_seconds"]) and report["elapsed_wall_seconds"] >= 0, "elapsed") + if report["overloads"]: + need(report["stop_reason"] == "simulation_overloaded", "ignored overload") + need(all(x["frame"] == len(frames) - 1 for x in report["overloads"]), "continued after overload") + if report["stop_reason"] == "complete": + need(report["actual_minutes"] >= warmup + target and report["measurement"]["actual_minutes"] >= target, + "incomplete interval called complete") + need("kernel_at_warmup_end" in report, "warmup kernel report") + for name in ("warmup", "measurement"): + interval(report, name) + if report.get("label") == "200客": + need(frames and all(f["registered"]["guests"] == 200 for f in frames), "200 guest claim") + + +def check_receipt(receipt, directory): + need(receipt["source_before"] == receipt["source_after"], "source map changed") + need(receipt["default_player_before"] == receipt["default_player_after"], "player changed") + need(not receipt["cleanup_errors"] and all(r["engine_ended"] is True for r in receipt["cleanup"]), "cleanup") + need(receipt["error"] is None and receipt["passed"] is True, "failed collection") + rows = receipt["phases"] + need([r["phase"] for r in rows] == ["import", *PHASES], "phase order") + for row in rows: + need(row["timeout_seconds"] == 1800 and row["passed"] is True, "phase timeout/pass") + for name, pin in row["raw"].items(): + data = (directory / row["phase"] / name).read_bytes() + need(hashlib.sha256(data).hexdigest() == pin["sha256"] and len(data) == pin["bytes"], "raw SHA") + if row["phase"] == "import": + continue + stdout = (directory / row["phase"] / "stdout.log").read_text() + printed = [json.loads(x[len(PREFIX):]) for x in stdout.splitlines() if x.startswith(PREFIX)] + need(printed == [row["report"]], "raw/report mismatch") + need(row["capture"]["actual_captured_pids"] == [row["report"]["pid"]], "PID mismatch") + check_report(row["report"]) + need(row["report"]["stop_reason"] == "complete", "collection includes stopped phase") + if row["phase"] in ("R0", "O0"): + estimate = row["extrapolation"] + equal(estimate["seconds_per_phase"], row["report"]["elapsed_wall_seconds"] * 150 / + row["report"]["actual_minutes"], "extrapolation") + + +def check_probe(text): + # The SceneTree bootstrap name is required; all other initialization is forbidden. + body = text.replace("func _initialize()", "func BOOTSTRAP()") + for token in FORBIDDEN: + need(token not in body, "forbidden probe token: " + token) + need('main.call("_set_speed", speed)' in body, "missing UI speed path") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("receipt", type=Path) + args = parser.parse_args() + check_receipt(json.loads(args.receipt.read_text()), args.receipt.parent) + print("P28-01 structural consistency only; execution/static-review gates remain separate.") + + +if __name__ == "__main__": + main() diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py new file mode 100644 index 0000000..2da0e9f --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python3 +"""Owned windowed P28-01 performance collection. Only Root executes --run.""" +import argparse +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import re +import subprocess +import sys +import uuid +from datetime import datetime, timezone + +sys.dont_write_bytecode = True +from check_p28_01_report import check_report, check_receipt +HERE = Path(__file__).resolve().parent +ROOT = HERE.parents[5] +FIXTURE_SHA = "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd" +ENGINE_SHA = "c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf" +VERSION = "4.7.2.stable.official.ed1daf0bf" +LIMIT = 1800 +PHASES = (("R0", "R", 1, 0, 10), ("P1", "R", 1, 30, 120), + ("P2", "R", 3, 30, 120), ("O0", "O", 1, 0, 10), + ("P3", "O", 1, 30, 120), ("P4", "O", 3, 30, 120)) +EXTERNAL_IMPORTS = ("tools/run_all_tests.py", "tools/regression/owned_outer.py") + + +def require(value, message): + if not value: + raise ValueError(message) + + +def sha(path): + return hashlib.sha256(Path(path).read_bytes()).hexdigest() + + +def digest(value): + return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":")).encode()).hexdigest() + + +def write(path, value): + with Path(path).open("x", encoding="utf-8") as stream: + json.dump(value, stream, ensure_ascii=False, indent=2, allow_nan=False) + stream.write("\n") + + +def now(): + return datetime.now(timezone.utc).isoformat() + + +def load_tool(relative): + require(relative in EXTERNAL_IMPORTS, "external import outside allowlist") + spec = importlib.util.spec_from_file_location("p28_" + Path(relative).stem, ROOT / relative) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def profile_files(directory): + directory = Path(directory) + result = {} + if not directory.exists(): + return result + require(not directory.is_symlink(), "profile/stage symlink") + for path in sorted(directory.rglob("*")): + require(not path.is_symlink(), "nested profile/stage symlink") + if path.is_file(): + result[path.relative_to(directory).as_posix()] = {"sha256": sha(path), "bytes": path.stat().st_size} + return result + + +def live_engines(): + rows = subprocess.check_output(["ps", "-axo", "pid=,comm="], text=True) + return [line for line in rows.splitlines() + if re.search(r"(?:^|/)(?:Godot|godot)(?:[ ._-]|$)", line.split(None, 1)[-1])] + + +def no_live_engine(): + require(not live_engines(), "another engine exists") + + +def diagnostics(text): + return [line for line in text.splitlines() if re.search( + r"ERROR:|SCRIPT ERROR:|Parse Error:|^\s*FAIL(?:ED)?[: ]|" + r"Unicode parsing error|ObjectDB instances leaked|instances were leaked|" + r"resources? still in use at exit|Leaked instance:", line)] + + +def source_map(standard, root): + return {name: {"bytes": row["bytes"], "sha256": row["sha256"]} + for name, row in standard.source_refs(root).items()} + + +def argv_for(engine, stage, wrapper, importing=False): + if importing: + return [str(engine), "--headless", "--editor", "--import", "--path", str(stage), "--quit"] + return [str(engine), "--path", str(stage), "--script", str(wrapper)] + + +def run_engine(outer, command, folder, environment): + no_live_engine() + native = folder / "engine-native.log" + command = command + ["--log-file", str(native), "--verbose"] + env = {key: value for key, value in os.environ.items() if not key.startswith("THE_TREE_")} + env.update(environment) + result, capture = outer.run_phase(command, folder, LIMIT, env, now_fn=now, + dump_fn=write, sha_fn=sha, live_engines_fn=live_engines) + raw = {} + text = "" + for name in ("stdout.log", "stderr.log", "engine-native.log"): + path = folder / name + raw[name] = {"sha256": sha(path), "bytes": path.stat().st_size} if path.is_file() else None + if path.is_file(): + text += path.read_text(errors="replace") + bad = diagnostics(text) + clean = bool(result) and all(( + result.get("exit_code") == 0, result.get("helper_exit_code") == 0, + result.get("timed_out") is False, result.get("error_lines") == [], + result.get("owned_popen_ended") is True, result.get("old_helper_engine_ended") is True, + result.get("start_receipt_written") is True, result.get("end_receipt_written") is True, + result.get("lifecycle_error") is None, result.get("external_process_or_group_signals") == 0, + capture.get("passed") is True, capture.get("all_captured_ended") is True, + len(capture.get("actual_captured_pids", [])) == 1, all(raw.values()), not bad)) + row = {"passed": clean, "process": result, "capture": capture, "raw": raw, + "diagnostics": bad, "timeout_seconds": LIMIT, "argv": command} + write(folder / "process.json", row) + return row + + +def inputs(readonly): + manifest_path = readonly / "ROOT_INPUT_MANIFEST.json" + require(sha(manifest_path) == "9dac0a51d52087e691c5723b4f78f171f7998c78b582b2b6c4c904798e77644d", + "Root input manifest changed") + manifest = json.loads(manifest_path.read_text()) + pins = {} + for row in manifest["files"]: + path = Path(row["destination"]) + require(path.resolve().is_relative_to(readonly.resolve()), "input escapes read-only directory") + require(sha(path) == row["sha256"] and path.stat().st_size == row["bytes"], "read-only input changed") + pins[str(path.relative_to(readonly))] = row["sha256"] + return {"manifest_sha256": sha(manifest_path), "files": pins} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--readonly", type=Path, required=True) + parser.add_argument("--fixture", type=Path, required=True) + parser.add_argument("--engine", type=Path, default=Path("/Applications/Godot.app/Contents/MacOS/Godot")) + parser.add_argument("--output", type=Path, required=True) + mode = parser.add_mutually_exclusive_group(required=True) + mode.add_argument("--check", action="store_true") + mode.add_argument("--run", action="store_true") + args = parser.parse_args() + standard = load_tool(EXTERNAL_IMPORTS[0]) + pins = inputs(args.readonly.resolve()) + fixture_actual = sha(args.fixture) if args.fixture.is_file() else None + if fixture_actual != FIXTURE_SHA: + shortfall = {"stop_reason": "input_shortfall", "fixture_expected_sha256": FIXTURE_SHA, + "fixture_actual_sha256": fixture_actual, "engines_started": 0, "stages_created": 0} + if args.run: + require(not args.output.exists(), "fresh output required") + args.output.mkdir(parents=True) + write(args.output / "input-shortfall.json", shortfall) + print(json.dumps(shortfall)) + return 1 + require(sha(args.engine) == ENGINE_SHA, "engine SHA") + before = source_map(standard, ROOT) + commit = subprocess.check_output(["git", "-C", str(ROOT), "rev-parse", "HEAD"], text=True).strip() + tracked = subprocess.check_output(["git", "-C", str(ROOT), "ls-tree", "-r", commit], text=True) + selected = {} + for line in tracked.splitlines(): + info, name = line.split("\t", 1) + if name.split("/", 1)[0] in ("autoload", "scripts", "scenes", "data", "tests", "assets", "tools") \ + or name in ("project.godot", "export_presets.cfg", "Makefile", "AGENTS.md"): + if Path(name).name != ".DS_Store" and "__pycache__" not in Path(name).parts: + selected[name] = info.split()[2] + require(set(selected) == set(before), "source map differs from committed file set") + for name, blob in selected.items(): + actual = subprocess.check_output(["git", "-C", str(ROOT), "hash-object", str(ROOT / name)], text=True).strip() + require(actual == blob, "dirty source: " + name) + stage = args.output.resolve() / "stage" + wrapper = args.output.resolve() / "guarded.gd" + planned = [argv_for(args.engine, stage, wrapper, True)] + [ + argv_for(args.engine, stage, wrapper) for _ in PHASES] + admission = {"commit": commit, "source_map": before, "source_map_sha256": digest(before), + "readonly": pins, "engine_sha256": ENGINE_SHA, "engine_version": VERSION, + "fixture_sha256": FIXTURE_SHA, "planned_argv": planned, "phases": PHASES, + "reused_r02": {"execution_sha256": pins["files"]["run-r02/execution.json"], + "report_sha256": pins["files"]["run-r02/after/evidence/report.json"], + "scope": "x1 headless Operations OFF; reused without remeasurement", + "tick_ms": {"mean": 826.6, "p95": 1702.7, "max": 2918.1}, + "path_cold_p95_ms": 0.61, "snapshot_mean_ms": 77.6, + "manual_save_ms": 297, "autosave_ms": 327, + "load_mean_ms": 3193, "load_max_ms": 3368, + "budget_comparisons_ms": { + "x1_tick_mean": {"value": 826.6, "limit": 4, "passed": False}, + "path_cold_p95": {"value": 0.61, "limit": 2, "passed": True}, + "manual_save": {"value": 297, "limit": 5000, "passed": True}, + "autosave": {"value": 327, "limit": 5000, "passed": True}, + "load_max": {"value": 3368, "limit": 8000, "passed": True}}}} + if args.check: + print(json.dumps(dict(admission, stages_created=0, engines_started=0), ensure_ascii=False, indent=2)) + return 0 + require(sys.platform == "darwin", "reviewed CoW execution requires macOS") + require(not args.output.exists(), "fresh output required") + no_live_engine() + player = standard.default_player(ROOT) + outer = load_tool(EXTERNAL_IMPORTS[1]) + out = args.output.resolve() + out.mkdir(parents=True) + write(out / "admission.json", admission) + rows = [] + error = None + profiles = [] + try: + stage.mkdir() + for name in before: + target = stage / name + target.parent.mkdir(parents=True, exist_ok=True) + standard.copy_source(ROOT / name, target) + require(source_map(standard, stage) == before, "CoW source differs") + probe = out / "measure_p28_01.gd" + probe.write_bytes((HERE / probe.name).read_bytes()) + guard = out / "profile_guard.gd" + guard.write_bytes((args.readonly / "run-r02/profile_guard.gd").read_bytes()) + wrapper.write_text("extends " + json.dumps(str(probe)) + "\nconst Guard = preload(" + + json.dumps(str(guard)) + ")\nfunc _initialize() -> void:\n" + "\tif not Guard.activate():\n\t\tquit(97)\n\t\treturn\n\tsuper._initialize()\n") + script_pins = {str(p): sha(p) for p in (probe, guard, wrapper)} + hardware = subprocess.check_output( + ["sysctl", "-n", "hw.model", "hw.logicalcpu", "hw.memsize"], text=True).splitlines() + meta = {"machine": {"hw.model": hardware[0], "cores": int(hardware[1]), "memory_bytes": int(hardware[2])}, + "build": {"engine_string": VERSION, "engine_hash": "ed1daf0bf", "engine_sha256": ENGINE_SHA, + "project_commit": commit, "source_map_sha256": digest(before)}} + public = (stage / "project.godot").read_bytes() + expected = before + for index, phase in enumerate((("import", "", 0, 0, 0),) + PHASES): + label, source, speed, warmup, minutes = phase + require(source_map(standard, ROOT) == before and standard.default_player(ROOT) == player, + "source/player changed before phase") + require(source_map(standard, stage) == expected, "stage changed before phase") + require(all(sha(Path(p)) == pin for p, pin in script_pins.items()), "execution scripts changed") + require(sha(args.fixture) == FIXTURE_SHA, "input_shortfall: changed input") + name = "TheTree-PCK-tests-" + uuid.uuid4().hex + "-p28-startup" + startup = standard.profile_path(name) + require(not startup.exists(), "startup profile not fresh") + runtime_name = "TheTree-PCK-tests-" + uuid.uuid4().hex + "-p28-runtime" + runtime = standard.profile_path(runtime_name) + require(not runtime.exists(), "runtime profile not fresh") + profiles.extend([startup, runtime]) + guard_path = out / (label + "-guard.json") + env = {"THE_TREE_PACK_PROFILE_NAME": runtime_name, "THE_TREE_PACK_PROFILE_EXPECTED": str(runtime), + "THE_TREE_PACK_PROFILE_REPORT": str(guard_path), "THE_TREE_P28_PHASE": label, + "THE_TREE_P28_INPUT": source, "THE_TREE_P28_SPEED": str(speed), + "THE_TREE_P28_WARMUP": str(warmup), "THE_TREE_P28_MINUTES": str(minutes), + "THE_TREE_P28_FIXTURE": str(args.fixture.resolve()), "THE_TREE_P28_FIXTURE_SHA": FIXTURE_SHA, + "THE_TREE_P28_META": json.dumps(meta)} + (stage / "project.godot").write_bytes(standard.private_project(public, name)) + try: + row = run_engine(outer, planned[index], out / label, env) + finally: + (stage / "project.godot").write_bytes(public) + row["phase"] = label + rows.append(row) + require(row["passed"], "native phase failed; raw retained: " + label) + after = source_map(standard, stage) + if label == "import": + require(all(after.get(k) == v for k, v in before.items()), "import changed source") + added = set(after) - set(before) + require(all((n.endswith(".gd.uid") and n[:-4] in before) or + (n.endswith(".import") and n[:-7] in before) for n in added), + "unexpected import-generated files") + expected = after + else: + require(after == expected, "stage changed during measurement") + observation = json.loads(guard_path.read_text()) + require(observation["verified"] is True and observation["conditions"] == [True] * 5 + and observation["actual_user_data_dir"] == str(runtime) + and observation["fresh_before_parent_initialize"] is True + and observation["profile_writes_before_guard"] == 0 + and observation["project_settings_persisted"] is False, "profile guard failure") + raw = (out / label / "stdout.log").read_text() + printed = [line.split("=", 1)[1] for line in raw.splitlines() if line.startswith("P28_01_PERF_REPORT=")] + require(len(printed) == 1, "exactly one raw report required") + report = json.loads(printed[0]) + require(report["pid"] == row["capture"]["actual_captured_pids"][0], "PID binding") + row.update(report=report, report_sha256=digest(report), guard=observation, + guard_sha256=sha(guard_path)) + write(out / label / "report.json", report) + check_report(report) + require(report["stop_reason"] == "complete", "phase stopped: " + report["stop_reason"]) + if label in ("R0", "O0"): + duration = report["elapsed_wall_seconds"] + row["extrapolation"] = {"observed_minutes": report["actual_minutes"], + "observed_seconds": duration, "target_minutes": 150, + "seconds_per_phase": duration * 150 / report["actual_minutes"], + "method": "stage0 wall seconds per game minute; x1/x3 same conservative estimate"} + require(standard.default_player(ROOT) == player, "default player changed") + except BaseException as caught: + error = repr(caught) + finally: + cleanup, cleanup_errors = outer.finish_captured(outer.OWNED) + after = source_map(standard, ROOT) + player_after = standard.default_player(ROOT) + receipt = {"phases": rows, "error": error, "source_before": before, "source_after": after, + "default_player_before": player, "default_player_after": player_after, + "cleanup": cleanup, "cleanup_errors": cleanup_errors, + "profiles": {str(p): profile_files(p) for p in profiles}, + "passed": error is None and not cleanup_errors and after == before and player_after == player, + "original_credit": 0, "windows_verified": False, "operations_200_guests_verified": False} + if receipt["passed"]: + try: + check_receipt(receipt, out) + except (ValueError, KeyError, TypeError) as caught: + receipt.update(passed=False, error="checker: " + repr(caught)) + write(out / "execution.json", receipt) + return 0 if receipt["passed"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd new file mode 100644 index 0000000..de5d661 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd @@ -0,0 +1,364 @@ +extends SceneTree +## External, passive observer of the default autoload world. +## Raw per-frame lines survive an owned timeout before the final report. + +class Observer: + extends Node + var owner_probe: Variant + func _process(_delta: float) -> void: + owner_probe.sample() + +var clock: Variant +var runtime: Variant +var main: Variant +var preview: Variant +var report := {} +var frames: Array = [] +var ready_to_sample := false +var start_us := 0 +var previous_us := 0 +var previous_minute := 0 +var start_minute := 0 +var warmup := 0 +var target := 0 +var speed := 0 +var previous_serial := -1 +var drawing := {} +var camera := {} +var overloads: Array = [] +var checkpoints: Array = [] +var warmup_ended := false +var measurement_start := 0 +var minute_ticks: Array = [] +var previous_tick_us := 0 +var previous_wall := 0.0 + +func _initialize() -> void: + call_deferred("begin") + +func hashed(value: Variant) -> String: + var hash_context := HashingContext.new() + hash_context.start(HashingContext.HASH_SHA256) + hash_context.update(var_to_bytes(value)) + return hash_context.finish().hex_encode() + +func vec(value: Vector2) -> Array: + return [value.x, value.y] + +func camera_state() -> Dictionary: + return {"layer": preview.layer_mode, "zoom": preview.zoom_level, + "pan": vec(preview.pan_offset), "viewport": vec(preview.size), + "window_size": vec(root.size), "screen_size": vec(DisplayServer.screen_get_size()), + "content_scale_size": vec(root.content_scale_size), + "content_scale_factor": root.content_scale_factor, + "vsync_mode": DisplayServer.window_get_vsync_mode()} + +func registered() -> Dictionary: + return {"guests": runtime.guest_service.get_guests().size(), + "staff": runtime.staff_service.get_staff_members().size(), + "facilities": runtime.facility_service.get_instances().size(), + "walkable_cells": runtime.world_service.get_walkable_cells().size()} + +func active_counts() -> Dictionary: + var guests := 0 + var staff := 0 + var facilities := 0 + for guest: Variant in runtime.guest_service.get_guests(): + if guest.state not in [GuestRuntime.State.SPAWNING, GuestRuntime.State.LEAVING, GuestRuntime.State.DESPAWNING]: + guests += 1 + for person: Variant in runtime.staff_service.get_staff_members(): + var shift: Variant = runtime.staff_service.get_shift(person.shift_id) + if shift != null and not shift.cancelled and shift.actual_started_at >= 0 \ + and shift.actual_ended_at < 0 and (shift.contains_minute(clock.absolute_minute) or person.is_overtime): + staff += 1 + for facility: Variant in runtime.facility_service.get_instances(): + if facility.operation_state == FacilityInstance.OperationState.OPERATING: + facilities += 1 + return {"guests": guests, "staff": staff, "facilities": facilities} + +func rng_state() -> Dictionary: + var result := {} + var streams: Dictionary = runtime.get("_rng_streams") + for key: Variant in streams: + result[String(key)] = streams[key].snapshot() + return result + +func draw_counts() -> Dictionary: + var observation: Dictionary = preview.get_life_observation_report() + var serial := int(observation.get("observation_serial", -1)) + if serial == previous_serial and not drawing.is_empty(): + var carried := drawing.duplicate(true) + carried["carried_from_serial"] = serial + return carried + previous_serial = serial + var area := Rect2(Vector2.ZERO, preview.size) + var actors: Array = observation.get("actor_drawings", []) + var visible := 0 + for actor: Dictionary in actors: + if area.has_point(actor["at"]): + visible += 1 + var facilities := 0 + for facility: Dictionary in preview.call("_completed_facilities_for_draw"): + if int(facility.get("layer", 0)) != int(preview.layer_mode): + continue + var cells: Array = preview.call("_cells_from_serialized", facility.get("occupied_cells", [])) + if not cells.is_empty() and preview.call("_cell_bounds_rect", cells).intersects(area): + facilities += 1 + var bounds: Dictionary = preview.call("_world_bounds") + var cells := 0 + if not bounds.is_empty(): + for y in range(int(bounds["min_y"]), int(bounds["max_y"]) + 1): + for x in range(int(bounds["min_x"]), int(bounds["max_x"]) + 1): + if preview.get_cell_rect(Vector3i(x, y, preview.layer_mode)).intersects(area): + cells += 1 + drawing = {"actors_in_layer": actors.size(), "actors_in_viewport": visible, + "facilities": facilities, "cells": cells, "observation_serial": serial, + "carried_from_serial": null, "camera": camera_state()} + return drawing.duplicate(true) + +func begin() -> void: + clock = root.get_node("GameClock") + runtime = root.get_node("SimulationRuntime") + clock.set_paused(true) + var input := OS.get_environment("THE_TREE_P28_INPUT") + speed = int(OS.get_environment("THE_TREE_P28_SPEED")) + warmup = int(OS.get_environment("THE_TREE_P28_WARMUP")) + target = int(OS.get_environment("THE_TREE_P28_MINUTES")) + report = {"schema": "p28-01-v1", "pid": OS.get_process_id(), + "phase": OS.get_environment("THE_TREE_P28_PHASE"), "input": input, + "metadata": JSON.parse_string(OS.get_environment("THE_TREE_P28_META")), + "input_sha256": null, "input_sha256_reason": "fresh_autoload_initialization", + "speed": speed, "speed_path": "main._set_speed->SimulationKernel._process", + "toggle_pause_calls": 0, "performance_clear_calls": 0, + "renderer": ProjectSettings.get_setting("rendering/renderer/rendering_method"), + "warmup_target": warmup, "measurement_target": target, + "window_note": "Product reports are raw rolling capacity-512 reports; warm-up may remain; no subtraction.", + "count_definitions": {"registered": "service collections; walkable cells", + "active": "guests exclude SPAWNING/LEAVING/DESPAWNING; staff current attended unended shift; facilities OPERATING", + "drawn": "world_preview submitted model serial and viewport geometry"}, + "overloads": overloads, "checkpoints": checkpoints, "frames": frames, "minute_ticks": minute_ticks} + if input == "R": + var path := OS.get_environment("THE_TREE_P28_FIXTURE") + report["input_sha256"] = FileAccess.get_sha256(path) + report["input_sha256_reason"] = null + if report["input_sha256"] != OS.get_environment("THE_TREE_P28_FIXTURE_SHA"): + finish("input_shortfall") + return + var file := FileAccess.open(path, FileAccess.READ) + if file == null: + finish("input_shortfall") + return + var snapshot: Variant = file.get_var(false) + file.close() + var restored: Variant = runtime.restore_snapshot(snapshot) + report["restore_result"] = restored + if not restored: + finish("input_shortfall") + return + if registered() != {"guests": 200, "staff": 50, "facilities": 150, "walkable_cells": 20000}: + report["initial_registered"] = registered() + finish("input_shortfall") + return + # Restore may carry a paused value. Pause before adding main, never resume here. + clock.set_paused(true) + var paused_before: bool = clock.paused + var opening_minute: int = clock.absolute_minute + main = load("res://scenes/main/main.tscn").instantiate() + root.add_child(main) + report["startup"] = {"pause_fresh_opening": {"return": null, "reason": "void_function", + "paused_before": paused_before, "paused_after": clock.paused}, + "save_policy": {"return": null, "reason": "return_discarded_by_main(main.gd:222)"}, + "save_policy_after": runtime.get_save_policy(), + "start_report": main.get("_operations_start_report").duplicate(true), + "operations_enabled_after": runtime.get_operations_report().enabled, + "opening_minute": opening_minute} + if report.startup.start_report.is_empty(): + report.startup["start_report_reason"] = "main_retained_empty_dictionary" + var start: Dictionary = report.startup.start_report + var expected: bool = bool(start.get("ok", false)) and report.startup.operations_enabled_after and opening_minute == 360 + if input == "R": + expected = not report.startup.operations_enabled_after and String(start.get("code", "")) == "INVALID_START_TIME" + if not expected: + finish("input_shortfall") + return + await process_frame + await process_frame + preview = main.get("world_preview") + camera = camera_state() + report["camera"] = camera + report["initial_registered"] = registered() + report["preparation_sha256"] = hashed(runtime.create_snapshot()) + report["seed"] = runtime.world_seed + report["rng_streams"] = rng_state() + RenderingServer.viewport_set_measure_render_time(root.get_viewport_rid(), true) + clock.simulation_overloaded.connect(on_overload) + clock.tick_completed.connect(on_tick) + runtime.checkpoint_presentation_requested.connect(on_checkpoint) + var observer := Observer.new() + observer.owner_probe = self + observer.process_priority = 100000 + root.add_child(observer) + main.call("_set_speed", speed) + report["speed_after"] = clock.speed + report["paused_after_speed"] = clock.paused + if clock.paused or clock.speed != speed: + finish("input_shortfall") + return + start_minute = clock.absolute_minute + measurement_start = start_minute if warmup == 0 else -1 + previous_minute = start_minute + start_us = Time.get_ticks_usec() + previous_us = start_us + previous_tick_us = start_us + report["start_minute"] = start_minute + report["start_wall"] = Time.get_unix_time_from_system() + previous_wall = report["start_wall"] + ready_to_sample = true + +func on_overload(backlog: int) -> void: + overloads.append({"backlog": backlog, "game_minute": clock.absolute_minute, "frame": frames.size()}) + +func on_checkpoint(_presentation: Dictionary) -> void: + checkpoints.append({"game_minute": clock.absolute_minute, "frame": frames.size(), + "wall": Time.get_unix_time_from_system(), "cause": "production_checkpoint_presentation_requested"}) + +func on_tick(minute: int) -> void: + if not ready_to_sample: + return + var tick_us := Time.get_ticks_usec() + var row := {"game_minute": minute, "wall": Time.get_unix_time_from_system(), + "wall_us_since_previous_tick": tick_us - previous_tick_us, + "frame": frames.size(), "note": "passive tick_completed; includes inter-frame waiting"} + minute_ticks.append(row) + previous_tick_us = tick_us + print("P28_01_MINUTE=" + JSON.stringify(row)) + +func sample() -> void: + if not ready_to_sample: + return + var current_us := Time.get_ticks_usec() + var minute: int = clock.absolute_minute + var elapsed := current_us - previous_us + var advanced := minute - previous_minute + var row := {"frame": frames.size(), "game_start": previous_minute, "game_end": minute, + "minutes_advanced": advanced, "frame_wall_us": elapsed, "wall_start": previous_wall, + "wall": Time.get_unix_time_from_system(), + "speed": clock.speed, "paused": clock.paused, + "segment": "warmup" if previous_minute - start_minute < warmup else "measurement", + "registered": registered(), "active": active_counts(), "drawn": draw_counts(), + "seed": runtime.world_seed, "rng_streams": rng_state(), + "metadata": report.metadata, "input_sha256": report.input_sha256, + "input_sha256_reason": report.input_sha256_reason, + "preparation_sha256": report.preparation_sha256, + "render_cpu_raw_ms": RenderingServer.viewport_get_measured_render_time_cpu(root.get_viewport_rid()), + "render_gpu_raw_ms": RenderingServer.viewport_get_measured_render_time_gpu(root.get_viewport_rid()), + "time_process_seconds": Performance.get_monitor(Performance.TIME_PROCESS)} + frames.append(row) + print("P28_01_FRAME=" + JSON.stringify(row)) + previous_us = current_us + previous_wall = row.wall + previous_minute = minute + if not warmup_ended and minute - start_minute >= warmup: + warmup_ended = true + report["kernel_at_warmup_end"] = clock.get_performance_reports() + if warmup > 0: + measurement_start = minute + var operations: Dictionary = runtime.get_operations_report() + if not overloads.is_empty(): + finish("simulation_overloaded") + elif bool(operations.get("runtime_failure_latched", false)): + finish("operations_runtime_failure") + elif runtime.operations_coordinator != null and runtime.operations_coordinator.is_terminal(): + finish("operations_terminal") + elif bool(operations.get("tick_blocked", false)): + finish("operations_pause") + elif clock.paused: + finish("clock_paused") + elif camera_state() != camera: + finish("camera_changed") + elif float(current_us - start_us) / 1000000.0 >= 1800.0: + finish("timeout") + elif measurement_start >= 0 and minute - measurement_start >= target: + finish("complete") + +func stats(values: Array, reason := "no_samples") -> Dictionary: + if values.is_empty(): + return {"count": 0, "mean": null, "p95": null, "max": null, "reason": reason} + var ordered := values.duplicate() + ordered.sort() + var total := 0.0 + for value: float in values: + total += value + return {"count": values.size(), "mean": total / values.size(), + "p95": ordered[int(ceil(values.size() * 0.95)) - 1], "max": ordered.back(), "reason": null} + +func summary(segment: String) -> Dictionary: + var wall: Array = [] + var per_minute: Array = [] + var cpu: Array = [] + var gpu: Array = [] + var process: Array = [] + var selected: Array = [] + var stalls: Array = [] + var excluded := 0 + var minutes := 0 + var cpu_missing := false + var gpu_missing := false + var ranges := {"registered": {}, "active": {}} + for frame: Dictionary in frames: + if frame.segment != segment: + continue + selected.append(frame) + wall.append(frame.frame_wall_us) + process.append(frame.time_process_seconds) + minutes += frame.minutes_advanced + if frame.frame_wall_us > 50000: + stalls.append(frame.frame_wall_us) + if frame.minutes_advanced > 0: + per_minute.append(float(frame.frame_wall_us) / frame.minutes_advanced) + else: + excluded += 1 + cpu_missing = cpu_missing or frame.render_cpu_raw_ms <= 0 + gpu_missing = gpu_missing or frame.render_gpu_raw_ms <= 0 + cpu.append(frame.render_cpu_raw_ms) + gpu.append(frame.render_gpu_raw_ms) + for kind: String in ["registered", "active"]: + for key: String in frame[kind]: + var value: int = frame[kind][key] + if not ranges[kind].has(key): + ranges[kind][key] = {"min": value, "max": value} + ranges[kind][key].min = mini(ranges[kind][key].min, value) + ranges[kind][key].max = maxi(ranges[kind][key].max, value) + var unavailable := "zero_from_api_on_gl_compatibility_unsupported_or_unmeasured" + var minute_stats := stats(per_minute) + var budget := 4000 if speed == 1 else 12000 + var metric: Variant = minute_stats.mean if speed == 1 else minute_stats.p95 + return {"frames": selected.size(), "actual_minutes": minutes, + "start_minute": selected.front().game_start if not selected.is_empty() else null, + "end_minute": selected.back().game_end if not selected.is_empty() else null, + "start_wall": selected.front().wall_start if not selected.is_empty() else null, + "end_wall": selected.back().wall if not selected.is_empty() else null, + "empty_reason": "no_frames" if selected.is_empty() else null, + "frame_wall_us": stats(wall), "sim_us_per_minute": minute_stats, + "zero_minute_frames_excluded": excluded, "stall_over_50000_us": stats(stalls), + "render_cpu_ms": stats([], unavailable) if cpu_missing else stats(cpu), + "render_gpu_ms": stats([], unavailable) if gpu_missing else stats(gpu), + "time_process_seconds": stats(process), "count_ranges": ranges, + "budget": {"limit_us": budget, "value": metric, "passed": metric <= budget if metric != null else null, + "reason": "no_samples" if metric == null else null}, + "stall_budget": {"limit_us": 50000, "value": wall.max() if not wall.is_empty() else null, + "passed": wall.max() <= 50000 if not wall.is_empty() else null, + "reason": "no_samples" if wall.is_empty() else null}} + +func finish(reason: String) -> void: + ready_to_sample = false + report["stop_reason"] = reason + report["actual_minutes"] = int(clock.absolute_minute) - start_minute if start_us > 0 else 0 + report["elapsed_wall_seconds"] = float(Time.get_ticks_usec() - start_us) / 1000000.0 if start_us > 0 else 0.0 + report["end_wall"] = Time.get_unix_time_from_system() + report["kernel_at_phase_end"] = clock.get_performance_reports() + report["operations_at_end"] = runtime.get_operations_report() + report["warmup"] = summary("warmup") + report["measurement"] = summary("measurement") + print("P28_01_PERF_REPORT=" + JSON.stringify(report)) + quit(0) diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py new file mode 100644 index 0000000..ede1048 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +"""Synthetic tests only. No native engine, fixture, profile, or Runner operations.""" +import ast +import copy +import hashlib +import json +from pathlib import Path +import sys +from types import SimpleNamespace +import unittest +from unittest.mock import patch + +sys.dont_write_bytecode = True +import check_p28_01_report as checker +import collect_p28_01 as collector + +HERE = Path(__file__).resolve().parent + + +def synthetic_report(): + meta = {"machine": {"hw.model": "synthetic", "cores": 10, "memory_bytes": 24000000000}, + "build": {"engine_string": collector.VERSION, "engine_hash": "ed1daf0bf", + "engine_sha256": collector.ENGINE_SHA, "project_commit": "8" * 40, + "source_map_sha256": "a" * 64}} + camera = {"layer": 0, "zoom": 1, "pan": [0, 0], "viewport": [600, 380], + "window_size": [1000, 700], "screen_size": [1920, 1080], "content_scale_size": [0, 0], + "content_scale_factor": 1, "vsync_mode": 1} + frames = [] + for i in range(10): + frames.append({"frame": i, "game_start": 360 + i, "game_end": 361 + i, + "minutes_advanced": 1, "frame_wall_us": 200000, "wall": 100 + (i + 1) * .2, + "wall_start": 100 + i * .2, + "speed": 1, "paused": False, "segment": "measurement", + "registered": {"guests": 3, "staff": 2, "facilities": 4, "walkable_cells": 50}, + "active": {"guests": 2, "staff": 1, "facilities": 3}, + "drawn": {"actors_in_layer": 4, "actors_in_viewport": 3, "facilities": 2, + "cells": 40, "observation_serial": i + 1, + "carried_from_serial": None, "camera": camera}, + "seed": 42, "rng_streams": {"weather": {"state": 93 + i}}, "metadata": meta, + "input_sha256": None, "input_sha256_reason": "fresh_autoload_initialization", + "preparation_sha256": "b" * 64, "render_cpu_raw_ms": 1, + "render_gpu_raw_ms": 0, "time_process_seconds": .001}) + report = {"schema": "p28-01-v1", "pid": 123, "phase": "O0", "input": "O", + "speed": 1, "speed_after": 1, "paused_after_speed": False, "metadata": meta, + "input_sha256": None, "input_sha256_reason": "fresh_autoload_initialization", + "preparation_sha256": "b" * 64, "seed": 42, "rng_streams": {"weather": {"state": 93}}, + "speed_path": "main._set_speed->SimulationKernel._process", + "toggle_pause_calls": 0, "performance_clear_calls": 0, "renderer": "gl_compatibility", + "warmup_target": 0, "measurement_target": 10, "start_minute": 360, "start_wall": 100, + "end_wall": 102, "actual_minutes": 10, "elapsed_wall_seconds": 2, + "window_note": "raw rolling capacity-512; warm-up may remain", + "overloads": [], "checkpoints": [], "frames": frames, "stop_reason": "complete", + "camera": camera, "kernel_at_warmup_end": [], "kernel_at_phase_end": [], + "operations_at_end": {"enabled": True}, "startup": { + "pause_fresh_opening": {"return": None, "reason": "void_function", + "paused_before": True, "paused_after": True}, + "save_policy": {"return": None, "reason": "return_discarded_by_main(main.gd:222)"}, + "save_policy_after": {"enabled": True, "autosave_path": "user://saves/autosave"}, + "start_report": {"ok": True}, "operations_enabled_after": True, "opening_minute": 360}} + # Explicit oracle: ten identical frames and an empty warm-up. + def constant(count, value, reason="no_samples"): + return {"count": count, "mean": value if count else None, "p95": value if count else None, + "max": value if count else None, "reason": None if count else reason} + report["measurement"] = { + "frames": 10, "actual_minutes": 10, "start_minute": 360, "end_minute": 370, + "start_wall": 100, "end_wall": 102, "zero_minute_frames_excluded": 0, "empty_reason": None, + "frame_wall_us": constant(10, 200000), "sim_us_per_minute": constant(10, 200000), + "stall_over_50000_us": constant(10, 200000), "render_cpu_ms": constant(10, 1), + "render_gpu_ms": constant(0, None, "zero_from_api_on_gl_compatibility_unsupported_or_unmeasured"), + "time_process_seconds": constant(10, .001), + "count_ranges": {kind: {k: {"min": v, "max": v} for k, v in frames[0][kind].items()} + for kind in ("registered", "active")}, + "budget": {"limit_us": 4000, "value": 200000, "passed": False, "reason": None}, + "stall_budget": {"limit_us": 50000, "value": 200000, "passed": False, "reason": None}} + report["warmup"] = { + "frames": 0, "actual_minutes": 0, "start_minute": None, "end_minute": None, + "start_wall": None, "end_wall": None, "zero_minute_frames_excluded": 0, "empty_reason": "no_frames", + **{k: constant(0, None) for k in ("frame_wall_us", "sim_us_per_minute", "stall_over_50000_us", + "render_cpu_ms", "render_gpu_ms", "time_process_seconds")}, + "count_ranges": {"registered": {}, "active": {}}, + "budget": {"limit_us": 4000, "value": None, "passed": None, "reason": "no_samples"}, + "stall_budget": {"limit_us": 50000, "value": None, "passed": None, "reason": "no_samples"}} + return report + + +class ReportTests(unittest.TestCase): + def reject(self, mutate): + report = synthetic_report() + mutate(report) + with self.assertRaises((ValueError, KeyError, TypeError)): + checker.check_report(report) + + def test_valid_synthetic(self): + checker.check_report(synthetic_report()) + + def test_rejections(self): + mutations = { + "direct advance x3": lambda r: r.update(speed_path="advance_minutes"), + "clock speed mismatch": lambda r: r["frames"][0].update(speed=3), + "frame minute mismatch": lambda r: r["frames"][0].update(minutes_advanced=8), + "frame cap exceeded": lambda r: r["frames"][0].update(minutes_advanced=9), + "registered copied as active": lambda r: r["frames"][0].update(active=r["frames"][0]["registered"]), + "registered copied as drawn": lambda r: r["frames"][0].update(drawn=r["frames"][0]["registered"]), + "rng copied as seed": lambda r: r["frames"][0].update(seed=r["frames"][0]["rng_streams"]), + "GPU zero accepted": lambda r: r["measurement"]["render_gpu_ms"].update(mean=0, p95=0, max=0), + "GPU reason omitted": lambda r: r["measurement"]["render_gpu_ms"].update(reason=None), + "sample machine omitted": lambda r: r["frames"][0].pop("metadata"), + "preparation hash omitted": lambda r: r.pop("preparation_sha256"), + "seed null without reason": lambda r: r["frames"][0].update(seed=None), + "fabricated save return": lambda r: r["startup"]["save_policy"].update(return_value=True, **{"return": True}), + "fabricated void return": lambda r: r["startup"]["pause_fresh_opening"].update(**{"return": True}), + "start report null": lambda r: r["startup"].update(start_report=None), + "toggle pause": lambda r: r.update(toggle_pause_calls=1), + "clear performance": lambda r: r.update(performance_clear_calls=1), + "continue after overload": lambda r: r.update(overloads=[{"frame": 0, "backlog": 121}], + stop_reason="simulation_overloaded"), + "carried as fresh": lambda r: r["frames"][1]["drawn"].update(observation_serial=1), + "short measurement called 120": lambda r: r["measurement"].update(actual_minutes=120), + "false 200 guests": lambda r: r.update(label="200客"), + "budget fabricated pass": lambda r: r["measurement"]["budget"].update(passed=True), + "statistics fabricated": lambda r: r["measurement"]["frame_wall_us"].update(p95=3), + "continue after pause": lambda r: r["frames"][0].update(paused=True), + "wrong O start minute": lambda r: r["startup"].update(opening_minute=602), + } + for label, mutate in mutations.items(): + with self.subTest(label=label): + self.reject(mutate) + + def test_carried_observation(self): + r = synthetic_report() + r["frames"][1]["drawn"].update(observation_serial=1, carried_from_serial=1) + checker.check_report(r) + + def test_probe_static_contract(self): + text = (HERE / "measure_p28_01.gd").read_text() + checker.check_probe(text) + for token in checker.FORBIDDEN: + with self.subTest(token=token), self.assertRaises(ValueError): + checker.check_probe(text + "\n" + token) + + def test_allowed_dependencies_only(self): + tree = ast.parse((HERE / "collect_p28_01.py").read_text()) + self.assertEqual(collector.EXTERNAL_IMPORTS, + ("tools/run_all_tests.py", "tools/regression/owned_outer.py")) + with self.assertRaises(ValueError): + collector.load_tool("adapter/windows_adapter.py") + paths = [n.value for n in ast.walk(tree) if isinstance(n, ast.Constant) and isinstance(n.value, str) + and n.value.endswith(".py")] + self.assertEqual(set(paths), set(collector.EXTERNAL_IMPORTS)) + + def test_windowed_and_phase_plan(self): + self.assertNotIn("--headless", collector.argv_for("engine", Path("stage"), Path("wrapper"))) + self.assertIn("--headless", collector.argv_for("engine", Path("stage"), Path("wrapper"), True)) + self.assertEqual(collector.LIMIT, 1800) + self.assertEqual([p[0] for p in collector.PHASES], ["R0", "P1", "P2", "O0", "P3", "P4"]) + + def test_natural_population_change_is_not_shortfall(self): + r = synthetic_report() + r["frames"][5]["registered"]["guests"] = 2 + r["frames"][5]["active"]["guests"] = 1 + r["measurement"]["count_ranges"]["registered"]["guests"]["min"] = 2 + r["measurement"]["count_ranges"]["active"]["guests"]["min"] = 1 + checker.check_report(r) + + def test_owned_timeout_keeps_raw_and_1800_limit(self): + calls = [] + def owned(*args, **kwargs): + calls.append((args, kwargs)) + return {"timed_out": True}, {"passed": False, "actual_captured_pids": [456]} + with patch.object(collector, "no_live_engine"), patch.object(collector, "write") as output, \ + patch.object(Path, "is_file", return_value=True), \ + patch.object(Path, "read_bytes", return_value=b"partial raw\n"), \ + patch.object(Path, "read_text", return_value="partial raw\n"), \ + patch.object(Path, "stat", return_value=SimpleNamespace(st_size=12)): + row = collector.run_engine(SimpleNamespace(run_phase=owned), ["engine"], Path("synthetic"), {}) + self.assertEqual(calls[0][0][2], 1800) + self.assertFalse(row["passed"]) + self.assertTrue(row["process"]["timed_out"]) + self.assertEqual(set(row["raw"]), {"stdout.log", "stderr.log", "engine-native.log"}) + output.assert_called_once() + + def test_receipt_rejects_source_player_and_raw_pid(self): + r = synthetic_report() + raw = (checker.PREFIX + json.dumps(r) + "\n").encode() + row = {"phase": "O0", "passed": True, "timeout_seconds": 1800, + "raw": {"stdout.log": {"sha256": hashlib.sha256(raw).hexdigest(), "bytes": len(raw)}}, + "report": r, "capture": {"actual_captured_pids": [123]}, + "extrapolation": {"seconds_per_phase": 30}} + receipt = {"source_before": {"x": "a"}, "source_after": {"x": "a"}, + "default_player_before": {}, "default_player_after": {}, "cleanup_errors": [], + "cleanup": [{"engine_ended": True}], "error": None, "passed": True, + "phases": [{"phase": "import", "passed": True, "timeout_seconds": 1800, "raw": {}}, row]} + with patch.object(checker, "PHASES", {"O0": ("O", 1, 0, 10)}), \ + patch.object(Path, "read_bytes", return_value=raw), \ + patch.object(Path, "read_text", return_value=raw.decode()): + checker.check_receipt(receipt, Path("synthetic")) + for label, mutate in { + "source": lambda x: x.update(source_after={"x": "b"}), + "player": lambda x: x.update(default_player_after={"save": "changed"}), + "raw mismatch": lambda x: x["phases"][1]["report"].update(pid=444), + "PID mismatch": lambda x: x["phases"][1]["capture"].update(actual_captured_pids=[444]), + "raw SHA": lambda x: x["phases"][1]["raw"]["stdout.log"].update(sha256="f" * 64), + }.items(): + broken = copy.deepcopy(receipt) + mutate(broken) + with self.subTest(label=label), self.assertRaises(ValueError): + checker.check_receipt(broken, Path("synthetic")) + + +if __name__ == "__main__": + unittest.main(verbosity=2)