P28-01-COLLECTOR: 現HEADの性能基準:windowed x1/x3・Operations ON collector
This commit is contained in:
+277
@@ -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()
|
||||
+322
@@ -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())
|
||||
+364
@@ -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)
|
||||
+211
@@ -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)
|
||||
Reference in New Issue
Block a user