skip: passage proof bounds include wall/floor relief (L75 soundness fix); FI -7.6%
L74's passage bound omitted the 0.21 m wall relief and 0.20 m floor relief, so its whole-tile
proof was unsound in principle (no tested view hit it). Passage support is now bounded per route
segment by four parameter-subinterval boxes using Catmull-Rom derivative extrema, local max
radius and relief padding. Stratum slicing only at LOD0-2 in FI/VS. Legacy sub-tile block
certificates are audit-only (runtime version was slower). TN regression (-80,-20,-3218) kept.
Exact full-grid vs skip hashes on every tile, 4 views, Clang/GCC, W1/W8/W12; control caught;
80-camera sweep 0 violations. Whole view vs c67b200: SW -1.3%, FI -7.6%, TN -2.3%, VS +0.2%.
Compiles in the Linux UE host (VFHostEditor, 27/27 actions).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
c67b2006aa
commit
9d5455ec68
16 files changed
+1335
-59
No files matched your search
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare full-grid and proof-skip mesh hashes for all four owner arrivals."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l75/logs"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINS = {
|
||||
"clang": ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang",
|
||||
"gcc": ROOT / "Saved/Linux/l75/bin-gcc/VoxelForgeCoreHeadless-l75-gcc",
|
||||
}
|
||||
CAMERAS = {
|
||||
0: (8.0, 16.0, -372.2626953125),
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
3: (-20.0625, -5.068359375, -900.353515625),
|
||||
}
|
||||
HASH_ROW = re.compile(
|
||||
r"\[PipelineTileHash\] lod=(\d+) x=(-?\d+) y=(-?\d+) z=(-?\d+) "
|
||||
r"density=(0x[0-9a-fA-F]+) mesh=(0x[0-9a-fA-F]+) "
|
||||
r"payload=(0x[0-9a-fA-F]+) topology=(0x[0-9a-fA-F]+)"
|
||||
)
|
||||
EMPTY_DENSITY_HASH = "0xcbf29ce484222325"
|
||||
|
||||
|
||||
def read_hashes(path: Path) -> tuple[dict, set[tuple[int, int, int, int]]]:
|
||||
hashes = {}
|
||||
generated_density = set()
|
||||
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
row = HASH_ROW.search(line)
|
||||
if row:
|
||||
lod, x, y, z = (int(row.group(i)) for i in range(1, 5))
|
||||
hashes[(lod, x, y, z)] = {
|
||||
"density": row.group(5).lower(), "mesh": row.group(6).lower(),
|
||||
"payload": row.group(7).lower(), "topology": row.group(8).lower(),
|
||||
}
|
||||
if row.group(5).lower() != EMPTY_DENSITY_HASH:
|
||||
generated_density.add((lod, x, y, z))
|
||||
return hashes, generated_density
|
||||
|
||||
|
||||
def run(compiler: str, view: int, workers: int, allow_skips: bool,
|
||||
negative_control: bool = False) -> Path:
|
||||
camera = ",".join(f"{value:.12f}" for value in CAMERAS[view]) + ",0,0"
|
||||
mode = "negative" if negative_control else "skip" if allow_skips else "reference"
|
||||
log = OUT / f"{mode}-{compiler}-view{view}-w{workers}.log"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1740s", str(BINS[compiler]), str(DATA),
|
||||
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||
"--force-generate", f"--workers={workers}", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||
"--stream-hash-tiles-allow-skips" if allow_skips or negative_control
|
||||
else "--stream-hash-tiles",
|
||||
]
|
||||
import os
|
||||
environment = os.environ.copy()
|
||||
if negative_control:
|
||||
environment["VOXELFORGE_L74_TIGHT_PROOF_CONTROL"] = "1"
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as output:
|
||||
result = subprocess.run(command, cwd=ROOT, env=environment, stdout=output,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
hashes, _ = read_hashes(log)
|
||||
print(json.dumps({"compiler": compiler, "view": view, "workers": workers,
|
||||
"mode": mode, "exit_code": result.returncode,
|
||||
"hash_rows": len(hashes), "wall_s": round(time.monotonic()-started, 3),
|
||||
"log": str(log.relative_to(ROOT))}, sort_keys=True), flush=True)
|
||||
if result.returncode != 0 or not hashes:
|
||||
raise SystemExit(f"hash generation failed; see {log}")
|
||||
return log
|
||||
|
||||
|
||||
def compare(reference: Path, candidate: Path) -> dict:
|
||||
reference_hashes, _ = read_hashes(reference)
|
||||
candidate_hashes, candidate_generated = read_hashes(candidate)
|
||||
missing = sorted(reference_hashes.keys() - candidate_hashes.keys())
|
||||
extra = sorted(candidate_hashes.keys() - reference_hashes.keys())
|
||||
mismatch_counts = {field: 0 for field in ("mesh", "payload", "topology", "density_processed")}
|
||||
mismatch_examples = []
|
||||
for key in sorted(reference_hashes.keys() & candidate_hashes.keys()):
|
||||
a, b = reference_hashes[key], candidate_hashes[key]
|
||||
fields = ["mesh", "payload", "topology"]
|
||||
if key in candidate_generated:
|
||||
fields.append("density")
|
||||
for field in fields:
|
||||
if a[field] != b[field]:
|
||||
label = "density_processed" if field == "density" else field
|
||||
mismatch_counts[label] += 1
|
||||
if len(mismatch_examples) < 8:
|
||||
mismatch_examples.append({"key": key, "field": field,
|
||||
"reference": a[field], "candidate": b[field]})
|
||||
return {"reference": str(reference.relative_to(ROOT)),
|
||||
"candidate": str(candidate.relative_to(ROOT)),
|
||||
"reference_tiles": len(reference_hashes), "candidate_tiles": len(candidate_hashes),
|
||||
"missing_keys": missing[:8], "missing_key_count": len(missing),
|
||||
"extra_keys": extra[:8], "extra_key_count": len(extra),
|
||||
"mismatch_counts": mismatch_counts, "mismatch_examples": mismatch_examples,
|
||||
"mesh_topology_equal": not missing and not extra
|
||||
and mismatch_counts["mesh"] == 0 and mismatch_counts["topology"] == 0}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--phase", choices=("matrix", "workers", "negative"), required=True)
|
||||
args = parser.parse_args()
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
missing = [str(path) for path in [DATA, *BINS.values()] if not path.is_file()]
|
||||
if missing:
|
||||
raise SystemExit("missing input: " + ", ".join(missing))
|
||||
comparisons = []
|
||||
if args.phase == "matrix":
|
||||
references = {}
|
||||
skip_eight = {}
|
||||
for compiler in ("clang", "gcc"):
|
||||
for view in range(4):
|
||||
reference = run(compiler, view, 8, False)
|
||||
skip = run(compiler, view, 8, True)
|
||||
references[(compiler, view)] = reference
|
||||
skip_eight[(compiler, view)] = skip
|
||||
comparisons.append(compare(reference, skip))
|
||||
for view in range(4):
|
||||
comparisons.append(compare(references[("clang", view)],
|
||||
references[("gcc", view)]))
|
||||
comparisons.append(compare(skip_eight[("clang", view)],
|
||||
skip_eight[("gcc", view)]))
|
||||
(OUT / "falsifier-matrix.json").write_text(json.dumps(comparisons, indent=2)+"\n",
|
||||
encoding="utf-8")
|
||||
elif args.phase == "workers":
|
||||
for compiler in ("clang", "gcc"):
|
||||
for view in range(4):
|
||||
reference = OUT / f"reference-{compiler}-view{view}-w8.log"
|
||||
if not reference.is_file():
|
||||
raise SystemExit(f"missing reference from matrix phase: {reference}")
|
||||
baseline = OUT / f"skip-{compiler}-view{view}-w8.log"
|
||||
for workers in (1, 12):
|
||||
candidate = run(compiler, view, workers, True)
|
||||
comparisons.append(compare(reference, candidate))
|
||||
comparisons.append(compare(baseline, candidate))
|
||||
(OUT / "falsifier-workers.json").write_text(json.dumps(comparisons, indent=2)+"\n",
|
||||
encoding="utf-8")
|
||||
else:
|
||||
for view in range(4):
|
||||
reference = OUT / f"reference-clang-view{view}-w8.log"
|
||||
candidate = run("clang", view, 8, True, negative_control=True)
|
||||
comparisons.append(compare(reference, candidate))
|
||||
(OUT / "falsifier-negative-control.json").write_text(
|
||||
json.dumps(comparisons, indent=2)+"\n", encoding="utf-8")
|
||||
caught = any(not row["mesh_topology_equal"] for row in comparisons)
|
||||
print(f"[L74NegativeControlResult] caught={int(caught)}", flush=True)
|
||||
if not caught:
|
||||
return 2
|
||||
return 0
|
||||
failures = [row for row in comparisons if not row["mesh_topology_equal"]
|
||||
or row["missing_key_count"] or row["extra_key_count"]
|
||||
or any(row["mismatch_counts"].values())]
|
||||
print(f"[L74Falsifier] phase={args.phase} comparisons={len(comparisons)} "
|
||||
f"mesh_topology_failures={len(failures)}", flush=True)
|
||||
return 0 if not failures else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,85 @@
|
||||
# L75 report
|
||||
|
||||
## Whole-tile veto census
|
||||
|
||||
Across 9,661 sampled tiles, 4,917 were uniform in the full-grid sample. The proof certified 3,093 (63% of those uniform tiles, 32% of all visits); 1,824 uniform tiles were vetoed.
|
||||
|
||||
| First veto | Missed uniform tiles |
|
||||
|---|---:|
|
||||
| Detail envelope too wide | 0 |
|
||||
| Composer candidate | 0 |
|
||||
| Passage bounds | 289 |
|
||||
| Primitive overlap | 236 |
|
||||
| Transition | 141 |
|
||||
| Stratum crossing | 924 |
|
||||
| Smooth-min slack | 0 |
|
||||
| Other / native interval inconclusive | 234 |
|
||||
|
||||
The 234 `other` cases are SW native interval queries that remain inconclusive. Per-stratum veto rows are in `Saved/Linux/l75/logs/veto-audit.json`.
|
||||
|
||||
## Changes and sub-tile proof
|
||||
|
||||
The dominant veto was stratum crossing. The classifier now slices its z interval at exact stratum boundaries and evaluates each slice; this reduced crossing vetoes from 1,354 to 924. Passage bounds now include the 0.21 m wall and 0.20 m floor relief, plus Catmull-Rom centerline extrema. The resulting bounds remain conservative; passage vetoes rose to 289 because the old bounds omitted real carve support.
|
||||
|
||||
Sub-tile checks use a block-local legacy interval and the same detail, composer, passage, primitive, transition, stratum, and smooth-min vetoes. A certified block is skipped; an inconclusive block falls back to full density sampling. Runtime legacy checks are enabled for TN, where the regression was found; the audit override exercises them in all four views.
|
||||
|
||||
The permanent TN regression is at `(-80,-20,-3218)`: density is `+0.0633840263` (air). The failed proofs bounded the nominal passage route but missed its wall/floor relief, which extends the carve outside that bound. All four containing blocks now classify mixed and sample the point. See `Saved/Linux/l75/logs/tn-block-regression.log`.
|
||||
|
||||
## Falsification and timing
|
||||
|
||||
Clang/GCC full-grid versus skip comparisons matched mesh, payload, and topology hashes on every tile in all four views. W1/W8/W12 comparisons also had zero mismatches. The deliberately too-tight negative control was caught in all four views. The 20-position-per-view sweep completed 80/80 positions with zero block-proof violations; hit rates were SW 83.68%, FI 69.16%, TN 73.80%, VS 73.64%. The fast tier passed in 318.6 s.
|
||||
|
||||
Three interleaved pairs per view compared the rebuilt `c67b200` baseline (source stamp `aacea94c`) with L75 (`ebc12def`). Whole-view medians:
|
||||
|
||||
| View | Before | L75 | Change |
|
||||
|---|---:|---:|---:|
|
||||
| SW | 2.405 s | 2.510 s | +4.37% |
|
||||
| FI | 4.839 s | 5.351 s | +10.58% |
|
||||
| TN | 4.477 s | 4.976 s | +11.15% |
|
||||
| VS | 2.464 s | 3.187 s | +29.34% |
|
||||
|
||||
The L75 proof work improved coverage but did not improve whole-view time. Full-detail unknown boxes rose from c67 to L75: SW 1,424→1,493, FI 9,391→15,636, TN 3,760→11,342, and VS 176→5,543. Further work should isolate the conservative primitive bounds and full-sampling fallbacks before claiming a speedup. Far LOD refinement was left unchanged.
|
||||
|
||||
## Builds, source changes, and failed runs
|
||||
|
||||
Clang 19 and GCC 14 builds passed at source stamp `ebc12def`. The fast tier passed. After timing, the requested `VFHostEditor` Linux Development build was launched with `-MaxParallelActions=4` and the required process limits. Bundled CoreCLR failed before UnrealBuildTool or C++ compilation with `GC heap initialization failed (0x8007000E)` / `Failed to create CoreCLR`; retrying with bounded .NET GC settings had the same failure. The UE compile is therefore unverified under the mandated 6 GiB address-space limit. Log: `Saved/Linux/l75/ue-build-driver.log`.
|
||||
|
||||
No top-level Unreal module `Source/` files changed. The standalone source changes are `Standalone/Source/StreamingWorld.h` and `Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp`. The cached L74 binary failed its owner-data preflight and was excluded; the timing baseline was rebuilt from `c67b200`. No final hash, sweep, negative-control, or fast-tier run failed.
|
||||
|
||||
## Net subset (follow-up)
|
||||
|
||||
Kept the corrected passage proof and TN regression. Passage support is now stored as four parameter-subinterval bounds per route segment. Each bound uses Catmull-Rom derivative extrema where smoothing can occur, otherwise linear endpoints, plus the local maximum radius and outward padding. The 0.21 m wall relief is included along the route; the 0.20 m floor relief is added only for upper-entry segments where that field can apply. The four proof boxes are a conservative union; runtime passage-feature bounds also use their union. The refreshed sampled-tile audit found 5,028 uniform tiles and 1,881 misses:
|
||||
|
||||
| First veto | Missed uniform tiles |
|
||||
|---|---:|
|
||||
| Detail envelope | 0 |
|
||||
| Composer candidate | 0 |
|
||||
| Passage bounds | 313 |
|
||||
| Primitive overlap | 199 |
|
||||
| Transition | 0 |
|
||||
| Stratum crossing | 1,135 |
|
||||
| Smooth-min slack | 0 |
|
||||
| Other / native interval inconclusive | 234 |
|
||||
|
||||
Sub-tile legacy block certificates are audit-only. The committed native block path remains enabled, with its full-grid fallback when any block candidate is uncertified. Stratum slicing is limited to LOD0–2 in FI and VS; SW and TN use the conservative crossing veto. The TN probe `(-80,-20,-3218)` remains air at `+0.0633840263`; all four covering blocks were Mixed/unknown, with zero violations across 62,261,518 audited samples.
|
||||
|
||||
Final evidence passed on source stamp `17470bc8`:
|
||||
|
||||
- Clang/GCC full-grid versus skip: 16 comparisons, exact mesh/payload/topology hashes on every tile; zero mismatches.
|
||||
- W1/W8/W12: 32 comparisons, zero mismatches.
|
||||
- Negative control: caught in all four views.
|
||||
- Sweep: 20 positions per view, 80/80 zero violations and exact tile hashes. Hit rates were SW 24.13%, FI 22.05%, TN 25.88%, VS 22.32%.
|
||||
- Fast tier: PASS, 318.9 s.
|
||||
|
||||
Three interleaved whole-view pairs against rebuilt `c67b200`:
|
||||
|
||||
| View | Before median | Net subset | Change |
|
||||
|---|---:|---:|---:|
|
||||
| SW | 2.441 s | 2.410 s | −1.27% |
|
||||
| FI | 4.975 s | 4.596 s | −7.62% |
|
||||
| TN | 4.569 s | 4.462 s | −2.34% |
|
||||
| VS | 2.479 s | 2.484 s | +0.20% |
|
||||
|
||||
The UE `VFHostEditor` Linux Development build succeeded after timing, with `-MaxParallelActions=4` and no address-space limit (27/27 actions, 68.4 s). It emitted three `CreateSectionGroup` deprecation warnings at `VoxelWorld.cpp`. No top-level Unreal `Source/` files changed; the standalone `Source/` changes are `Standalone/Source/StreamingWorld.h` and `Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp`.
|
||||
|
||||
Failed experiments: the original L75 runtime legacy-block configuration and the mixed-parent native-block gate regressed whole-view timing, so they were dropped. The earlier UBT attempt under the 6 GiB address-space cap failed before C++ compilation because CoreCLR could not initialize; the authorized uncapped UBT build passed.
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Audit certified legacy density blocks across 20 deterministic camera positions per stratum."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l75/logs"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BIN = ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang"
|
||||
CAMERAS = {
|
||||
0: (8.0, 16.0, -372.2626953125),
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
3: (-20.0625, -5.068359375, -900.353515625),
|
||||
}
|
||||
OFFSETS = [(x, y) for y in (0.1, 4.1, 8.1, 12.1)
|
||||
for x in (0.1, 4.1, 8.1, 12.1, 16.1)]
|
||||
AUDIT = re.compile(
|
||||
r"\[UniformBlockProofAudit\] checked_blocks=(\d+) checked_samples=(\d+) "
|
||||
r"fallback_blocks=(\d+) unknown_blocks=(\d+) violations=(\d+)"
|
||||
)
|
||||
TILE = re.compile(
|
||||
r"\[WorldAssemblerTilePipeline\] .*?solid_boxes=(\d+) air_boxes=(\d+) "
|
||||
r"mixed_boxes=(\d+) unknown_boxes=(\d+) whole_tile_skips=(\d+).*?"
|
||||
r"classification_worker_ms=([0-9.]+)"
|
||||
)
|
||||
VIOLATION = re.compile(r"\[UniformBlockProofViolation\]")
|
||||
|
||||
|
||||
def run(view: int, position: int, offset: tuple[int, int]) -> dict:
|
||||
base = CAMERAS[view]
|
||||
camera = ",".join(f"{value:.12f}" for value in
|
||||
(base[0] + offset[0], base[1] + offset[1], base[2])) + ",0,0"
|
||||
log = OUT / f"audit-s{view}-p{position:02d}.log"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
|
||||
"1740s", str(BIN), str(DATA), "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", "--workers=8", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||
"--audit-uniform-block-proofs", "--profile-lod-work",
|
||||
]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as output:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=output, stderr=subprocess.STDOUT,
|
||||
check=False, timeout=1740)
|
||||
content = log.read_text(encoding="utf-8", errors="replace")
|
||||
audit = AUDIT.search(content)
|
||||
tile = TILE.search(content)
|
||||
if result.returncode != 0 or audit is None or tile is None:
|
||||
raise SystemExit(f"audit run failed or summary is missing; see {log}")
|
||||
checked, samples, fallback, unknown, violations = map(int, audit.groups())
|
||||
solid, air, mixed, tile_unknown, tile_skips = map(int, tile.groups()[:5])
|
||||
classifier_ms = float(tile.group(6))
|
||||
violation_lines = len(VIOLATION.findall(content))
|
||||
row = {
|
||||
"strate": view, "position": position, "offset_m": list(offset),
|
||||
"exit_code": result.returncode, "wall_s": round(time.monotonic() - started, 3),
|
||||
"checked_blocks": checked, "checked_samples": samples,
|
||||
"fallback_blocks": fallback, "unknown_blocks": unknown,
|
||||
"violations": violations + violation_lines,
|
||||
"block_proof_hit_pct": round(100.0 * checked / max(checked + fallback, 1), 2),
|
||||
"tile_solid_boxes": solid, "tile_air_boxes": air, "tile_mixed_boxes": mixed,
|
||||
"tile_unknown_boxes": tile_unknown, "tile_whole_skips": tile_skips,
|
||||
"classification_worker_ms": classifier_ms,
|
||||
"log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
print("[L75AuditSweepRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
if row["violations"] != 0:
|
||||
raise SystemExit(f"uniform block proof violation; see {log}")
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
if not DATA.is_file() or not BIN.is_file():
|
||||
raise SystemExit("missing owner world or L75 Clang headless binary")
|
||||
rows = [run(view, index, offset) for view in range(4)
|
||||
for index, offset in enumerate(OFFSETS)]
|
||||
summary = []
|
||||
for view in range(4):
|
||||
group = [row for row in rows if row["strate"] == view]
|
||||
checked = sum(row["checked_blocks"] for row in group)
|
||||
fallback = sum(row["fallback_blocks"] for row in group)
|
||||
costs = [row["classification_worker_ms"] for row in group]
|
||||
summary.append({
|
||||
"strate": view, "positions": len(group), "violations": sum(
|
||||
row["violations"] for row in group), "checked_blocks": checked,
|
||||
"fallback_blocks": fallback, "proof_hit_pct": round(
|
||||
100.0 * checked / max(checked + fallback, 1), 2),
|
||||
"classifier_ms_p50": round(statistics.median(costs), 3),
|
||||
"classifier_ms_p95": round(sorted(costs)[int(0.95 * (len(costs) - 1))], 3),
|
||||
})
|
||||
if any(row["checked_blocks"] == 0 for row in summary):
|
||||
raise SystemExit("block interval audit did not check a proof block for every strate")
|
||||
result = {"runs": rows, "summary": summary}
|
||||
(OUT / "audit-sweep.json").write_text(json.dumps(result, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
for row in summary:
|
||||
print("[L75AuditSweepSummary] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exact full-grid versus whole-tile skip sweep at 20 camera positions per stratum."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l75/logs"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BIN = ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang"
|
||||
CAMERAS = {
|
||||
0: (8.0, 16.0, -372.2626953125),
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
3: (-20.0625, -5.068359375, -900.353515625),
|
||||
}
|
||||
OFFSETS = [(x, y) for y in (0.1, 4.1, 8.1, 12.1)
|
||||
for x in (0.1, 4.1, 8.1, 12.1, 16.1)]
|
||||
HASH = re.compile(
|
||||
r"\[PipelineTileHash\] lod=(\d+) x=(-?\d+) y=(-?\d+) z=(-?\d+) "
|
||||
r"density=(0x[0-9a-fA-F]+) mesh=(0x[0-9a-fA-F]+) "
|
||||
r"payload=(0x[0-9a-fA-F]+) topology=(0x[0-9a-fA-F]+)"
|
||||
)
|
||||
EMPTY_DENSITY_HASH = "0xcbf29ce484222325"
|
||||
PIPELINE = re.compile(r"\[StreamPipeline\] (.*)")
|
||||
TILE_PIPELINE = re.compile(r"\[WorldAssemblerTilePipeline\] (.*)")
|
||||
PAIR = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||
|
||||
|
||||
def read_hashes(path: Path) -> tuple[dict, set[tuple[int, int, int, int]]]:
|
||||
hashes = {}
|
||||
generated = set()
|
||||
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
match = HASH.search(line)
|
||||
if match:
|
||||
lod, x, y, z = (int(match.group(index)) for index in range(1, 5))
|
||||
hashes[(lod, x, y, z)] = {
|
||||
"density": match.group(5).lower(), "mesh": match.group(6).lower(),
|
||||
"payload": match.group(7).lower(), "topology": match.group(8).lower(),
|
||||
}
|
||||
if match.group(5).lower() != EMPTY_DENSITY_HASH:
|
||||
generated.add((lod, x, y, z))
|
||||
return hashes, generated
|
||||
|
||||
|
||||
def parse_summary(path: Path) -> dict:
|
||||
content = path.read_text(encoding="utf-8", errors="replace")
|
||||
pipeline = PIPELINE.search(content)
|
||||
tile_pipeline = TILE_PIPELINE.search(content)
|
||||
if pipeline is None or tile_pipeline is None:
|
||||
raise SystemExit(f"pipeline summaries missing; see {path}")
|
||||
values = dict(PAIR.findall(pipeline.group(1)))
|
||||
tile_values = dict(PAIR.findall(tile_pipeline.group(1)))
|
||||
tiles = values.get("whole_tiles", "0/0").split("/")
|
||||
return {
|
||||
"tiles": int(tiles[1]) if len(tiles) == 2 else 0,
|
||||
"whole_tile_skips": int(tile_values.get("whole_tile_skips", "0")),
|
||||
"classification_worker_ms": float(tile_values.get("classification_worker_ms", "0")),
|
||||
}
|
||||
|
||||
|
||||
def run(mode: str, view: int, position: int, offset: tuple[float, float]) -> dict:
|
||||
base = CAMERAS[view]
|
||||
xyz = (base[0] + offset[0], base[1] + offset[1], base[2])
|
||||
camera = ",".join(f"{value:.12f}" for value in xyz) + ",0,0"
|
||||
log = OUT / f"sweep-{mode}-s{view}-p{position:02d}.log"
|
||||
hash_flag = "--stream-hash-tiles" if mode == "reference" \
|
||||
else "--stream-hash-tiles-allow-skips"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
|
||||
"1740s", str(BIN), str(DATA), "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", "--force-generate", "--workers=8", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--camera={camera}", hash_flag,
|
||||
]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as output:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=output, stderr=subprocess.STDOUT,
|
||||
check=False, timeout=1740)
|
||||
hashes, _ = read_hashes(log)
|
||||
if result.returncode != 0 or not hashes:
|
||||
raise SystemExit(f"{mode} hash generation failed; see {log}")
|
||||
summary = parse_summary(log)
|
||||
row = {
|
||||
"mode": mode, "strate": view, "position": position, "offset_m": list(offset),
|
||||
"camera_m": list(xyz), "exit_code": result.returncode,
|
||||
"wall_s": round(time.monotonic() - started, 3), "hash_rows": len(hashes),
|
||||
**summary, "log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
print("[L75HashSweepRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def compare(reference: Path, candidate: Path) -> dict:
|
||||
expected, _ = read_hashes(reference)
|
||||
actual, candidate_generated = read_hashes(candidate)
|
||||
missing = expected.keys() - actual.keys()
|
||||
extra = actual.keys() - expected.keys()
|
||||
mismatches = {field: 0 for field in ("mesh", "payload", "topology", "density_processed")}
|
||||
for key in expected.keys() & actual.keys():
|
||||
fields = ["mesh", "payload", "topology"]
|
||||
if key in candidate_generated:
|
||||
fields.append("density")
|
||||
for field in fields:
|
||||
if expected[key][field] != actual[key][field]:
|
||||
mismatches["density_processed" if field == "density" else field] += 1
|
||||
return {
|
||||
"reference": str(reference.relative_to(ROOT)),
|
||||
"candidate": str(candidate.relative_to(ROOT)),
|
||||
"reference_tiles": len(expected), "candidate_tiles": len(actual),
|
||||
"missing_keys": len(missing), "extra_keys": len(extra),
|
||||
"mismatches": mismatches,
|
||||
"equal": not missing and not extra and not any(mismatches.values()),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not DATA.is_file() or not BIN.is_file():
|
||||
raise SystemExit("missing owner world or L75 Clang headless binary")
|
||||
references, candidates, comparisons = [], [], []
|
||||
for view in range(4):
|
||||
for position, offset in enumerate(OFFSETS):
|
||||
reference = run("reference", view, position, offset)
|
||||
candidate = run("skip", view, position, offset)
|
||||
comparison = compare(ROOT / reference["log"], ROOT / candidate["log"])
|
||||
comparison.update(strate=view, position=position)
|
||||
references.append(reference)
|
||||
candidates.append(candidate)
|
||||
comparisons.append(comparison)
|
||||
print("[L75HashSweepCompare] " + json.dumps(comparison, sort_keys=True), flush=True)
|
||||
if not comparison["equal"]:
|
||||
raise SystemExit(f"whole-tile proof falsified; see {comparison['candidate']}")
|
||||
summaries = []
|
||||
for view in range(4):
|
||||
rows = [row for row in candidates if row["strate"] == view]
|
||||
skips = sum(row["whole_tile_skips"] for row in rows)
|
||||
tiles = sum(row["tiles"] for row in rows)
|
||||
costs = [row["classification_worker_ms"] for row in rows]
|
||||
summaries.append({
|
||||
"strate": view, "camera_positions": len(rows), "hash_comparisons": len(rows),
|
||||
"violations": sum(not row["equal"] for row in comparisons
|
||||
if row["strate"] == view),
|
||||
"whole_tile_proof_hits": skips, "tiles_visited": tiles,
|
||||
"proof_hit_pct": round(100.0 * skips / max(tiles, 1), 2),
|
||||
"classifier_ms_p50": round(statistics.median(costs), 3),
|
||||
"classifier_ms_p95": round(sorted(costs)[int(0.95 * (len(costs) - 1))], 3),
|
||||
})
|
||||
result = {"runs": {"reference": references, "skip": candidates},
|
||||
"comparisons": comparisons, "summary": summaries}
|
||||
(OUT / "hash-sweep.json").write_text(json.dumps(result, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
for row in summaries:
|
||||
print("[L75HashSweepSummary] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run three interleaved full-view pairs on committed L74 and current L75 builds."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l75/logs/timing"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"before": ROOT / "Saved/Linux/l75/bin-c67-before/VoxelForgeCoreHeadless-l74-clang19",
|
||||
"after": ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang",
|
||||
}
|
||||
CAMERAS = {
|
||||
0: (8.0, 16.0, -372.2626953125),
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
3: (-20.0625, -5.068359375, -900.353515625),
|
||||
}
|
||||
ORDERS = (("before", "after"), ("after", "before"), ("before", "after"))
|
||||
PIPELINE = re.compile(r"\[StreamPipeline\] (.*)")
|
||||
PAIR = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||
|
||||
|
||||
def run(strate: int, pair: int, mode: str) -> dict:
|
||||
label = f"strate{strate}-pair{pair}-{mode}"
|
||||
log = OUT / f"{label}.log"
|
||||
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
|
||||
"1740s", str(BINARIES[mode]), str(DATA), "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--workers=8",
|
||||
"--clip-radius=3", "--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||
]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as output:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=output, stderr=subprocess.STDOUT,
|
||||
check=False, timeout=1740)
|
||||
content = log.read_text(encoding="utf-8", errors="replace")
|
||||
match = PIPELINE.search(content)
|
||||
if result.returncode != 0 or match is None:
|
||||
raise SystemExit(f"timing run failed or missing StreamPipeline summary; see {log}")
|
||||
pipeline = dict(PAIR.findall(match.group(1)))
|
||||
row = {
|
||||
"strate": strate, "pair": pair, "mode": mode, "exit_code": result.returncode,
|
||||
"process_wall_s": round(time.monotonic() - started, 3),
|
||||
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
|
||||
"stream_wall_s": float(pipeline["stream_wall"].removesuffix("s")),
|
||||
"whole_tiles": int(pipeline["whole_tiles"].split("/")[0]),
|
||||
"source_stamp": re.search(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$",
|
||||
content, re.M),
|
||||
"log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
row["source_stamp"] = row["source_stamp"].group(1) if row["source_stamp"] else None
|
||||
print("[L75TimingPairRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
missing = [str(path) for path in [DATA, *BINARIES.values()] if not path.is_file()]
|
||||
if missing:
|
||||
raise SystemExit("missing input: " + ", ".join(missing))
|
||||
rows = []
|
||||
for strate in range(4):
|
||||
for pair, order in enumerate(ORDERS, 1):
|
||||
for mode in order:
|
||||
rows.append(run(strate, pair, mode))
|
||||
summary = []
|
||||
for strate in range(4):
|
||||
group = [row for row in rows if row["strate"] == strate]
|
||||
before = [row["whole_view_ready_s"] for row in group if row["mode"] == "before"]
|
||||
after = [row["whole_view_ready_s"] for row in group if row["mode"] == "after"]
|
||||
summary.append({"strate": strate, "pairs": len(before),
|
||||
"before_s": [round(value, 3) for value in before],
|
||||
"after_s": [round(value, 3) for value in after],
|
||||
"before_median_s": round(statistics.median(before), 3),
|
||||
"after_median_s": round(statistics.median(after), 3),
|
||||
"change_pct": round(100.0 * (statistics.median(after)
|
||||
/ statistics.median(before) - 1.0), 2)})
|
||||
result = {"runs": rows, "summary": summary}
|
||||
(OUT / "summary.json").write_text(json.dumps(result, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
for row in summary:
|
||||
print("[L75TimingPairSummary] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep the L74 false-solid TN lattice sample under a permanent block audit."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l75/logs"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BIN = ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang"
|
||||
PROBE = (-80, -20, -3218)
|
||||
EXPECTED_DENSITY = 0.063384
|
||||
CAMERA_METERS = (19.266015625, -14.1001953125, -753.3740234375)
|
||||
REGRESSION = re.compile(r"\[L75TNBlockRegression\] (.*)")
|
||||
AUDIT = re.compile(r"\[UniformBlockProofAudit\] checked_blocks=(\d+) checked_samples=(\d+) "
|
||||
r"fallback_blocks=(\d+) unknown_blocks=(\d+) violations=(\d+)")
|
||||
PAIR = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not DATA.is_file() or not BIN.is_file():
|
||||
raise SystemExit("missing owner world or L75 Clang headless binary")
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
camera = ",".join(f"{value:.12f}" for value in CAMERA_METERS) + ",0,0"
|
||||
log = OUT / "tn-block-regression.log"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
|
||||
"1740s", str(BIN), str(DATA), "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--stream-hash-tiles",
|
||||
"--audit-uniform-block-proofs", "--workers=8", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||
]
|
||||
environment = os.environ.copy()
|
||||
environment["VOXELFORGE_L75_TN_BLOCK_REGRESSION"] = "1"
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as output:
|
||||
result = subprocess.run(command, cwd=ROOT, env=environment, stdout=output,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
content = log.read_text(encoding="utf-8", errors="replace")
|
||||
probes = []
|
||||
for line in content.splitlines():
|
||||
match = REGRESSION.search(line)
|
||||
if match:
|
||||
row = dict(PAIR.findall(match.group(1)))
|
||||
row.update(density=float(row["density"]), proof_safe=int(row["proof_safe"]))
|
||||
row["tile_origin"] = tuple(int(value) for value in
|
||||
row["tile_origin"].strip("()").split(","))
|
||||
probes.append(row)
|
||||
audit_match = AUDIT.search(content)
|
||||
if not probes or audit_match is None:
|
||||
raise SystemExit(f"TN regression did not cover the target lattice sample; see {log}")
|
||||
checked, samples, fallback, unknown, violations = map(int, audit_match.groups())
|
||||
density_matches = all(abs(row["density"] - EXPECTED_DENSITY) <= 1.0e-6
|
||||
for row in probes)
|
||||
false_solid_proofs = sum(row["proof_safe"] and row["verdict"] == "solid"
|
||||
for row in probes)
|
||||
row = {
|
||||
"probe": PROBE, "expected_density": EXPECTED_DENSITY,
|
||||
"observed_densities": [probe["density"] for probe in probes],
|
||||
"block_intervals": [{key: probe[key] for key in
|
||||
("tile_origin", "step", "block", "verdict", "proof_safe", "unknown")}
|
||||
for probe in probes],
|
||||
"checked_blocks": checked, "checked_samples": samples,
|
||||
"fallback_blocks": fallback, "unknown_blocks": unknown,
|
||||
"violations": violations, "false_solid_proofs": false_solid_proofs,
|
||||
"exit_code": result.returncode,
|
||||
"wall_s": round(time.monotonic() - started, 3),
|
||||
"log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
(OUT / "tn-block-regression.json").write_text(json.dumps(row, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
print("[L75TNRegression] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
if result.returncode != 0 or not density_matches or false_solid_proofs or violations:
|
||||
raise SystemExit(f"TN block regression failed; see {log}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Count first whole-tile proof vetoes among full-grid uniform observations."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l75/logs"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BIN = ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang"
|
||||
CAMERAS = {
|
||||
0: (8.0, 16.0, -372.2626953125),
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
3: (-20.0625, -5.068359375, -900.353515625),
|
||||
}
|
||||
ROW = re.compile(r"\[UniformGridCeiling\] (.*)")
|
||||
PAIR = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||
REASONS = {
|
||||
0: "none_or_certified", 1: "detail_envelope", 2: "composer_candidate",
|
||||
3: "passage_bounds", 4: "primitive_overlap", 5: "transition",
|
||||
6: "stratum_crossing", 7: "smooth_min_slack", 8: "other",
|
||||
}
|
||||
|
||||
|
||||
def run(view: int) -> list[dict]:
|
||||
camera = ",".join(f"{value:.12f}" for value in CAMERAS[view]) + ",0,0"
|
||||
log = OUT / f"veto-view{view}.log"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
|
||||
"1740s", str(BIN), str(DATA), "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--stream-hash-tiles",
|
||||
"--force-generate", "--workers=8", "--clip-radius=3", "--max-clip-level=8",
|
||||
"--max-level=8", f"--camera={camera}",
|
||||
]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as output:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=output,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
rows = []
|
||||
for line in log.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
match = ROW.search(line)
|
||||
if not match:
|
||||
continue
|
||||
row = dict(PAIR.findall(match.group(1)))
|
||||
key_value = row["key"].strip("()")
|
||||
row["key"] = tuple(int(value) for value in key_value.split(","))
|
||||
row = {key: (value if key == "key" else int(value))
|
||||
for key, value in row.items()}
|
||||
row.update(view=view, log=str(log.relative_to(ROOT)))
|
||||
rows.append(row)
|
||||
print(json.dumps({"view": view, "exit_code": result.returncode,
|
||||
"wall_s": round(time.monotonic() - started, 3),
|
||||
"tiles": len(rows), "log": str(log.relative_to(ROOT))},
|
||||
sort_keys=True), flush=True)
|
||||
if result.returncode != 0 or not rows:
|
||||
raise SystemExit(f"veto audit view {view} failed or emitted no rows; see {log}")
|
||||
return rows
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not DATA.is_file() or not BIN.is_file():
|
||||
raise SystemExit("missing owner world or L75 Clang headless binary")
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
all_rows = [row for view in range(4) for row in run(view)]
|
||||
missed = [row for row in all_rows if row["samples"] > 0 and row["uniform"]
|
||||
and not row["proof_safe"]]
|
||||
counts = Counter(row["proof_veto"] for row in missed)
|
||||
by_strate = []
|
||||
for view in range(4):
|
||||
group = [row for row in missed if row["view"] == view]
|
||||
by_strate.append({
|
||||
"strate": view,
|
||||
"missed_uniform_tiles": len(group),
|
||||
"vetoes": {REASONS.get(reason, f"unknown_{reason}"): count
|
||||
for reason, count in sorted(Counter(
|
||||
row["proof_veto"] for row in group).items())},
|
||||
})
|
||||
summary = {
|
||||
"observations": len(all_rows),
|
||||
"sampled_tiles": sum(row["samples"] > 0 for row in all_rows),
|
||||
"true_uniform_tiles": sum(row["samples"] > 0 and row["uniform"]
|
||||
for row in all_rows),
|
||||
"missed_uniform_tiles": len(missed),
|
||||
"vetoes": {REASONS.get(reason, f"unknown_{reason}"): count
|
||||
for reason, count in sorted(counts.items())},
|
||||
"by_strate": by_strate,
|
||||
"missed_tiles": missed,
|
||||
}
|
||||
(OUT / "veto-audit.json").write_text(json.dumps(summary, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
print("[L75VetoSummary] " + json.dumps({key: value for key, value in summary.items()
|
||||
if key != "missed_tiles"}, sort_keys=True), flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in new issue
Block a user