From 686b6f10e6b81a687137e18d3ee822ebbb918b3d Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:52:52 +0930 Subject: [PATCH 01/82] Add BH #2D hardware scaling sweep harness --- scripts/bench-bh2d-hardware.py | 381 +++++++++++++++++++++++++++++++++ 1 file changed, 381 insertions(+) create mode 100644 scripts/bench-bh2d-hardware.py diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py new file mode 100644 index 0000000..bafbaf7 --- /dev/null +++ b/scripts/bench-bh2d-hardware.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Fail-closed BH #2D real-hardware scaling sweep. + +This runner does not create a performance claim by itself. It executes the +existing galaxy-bh-gpu-tree-parallel verifier with --require-hardware at each +requested resident-body count, validates the emitted receipts, and writes a +manifest tying the sweep to one clean Git revision and one adapter identity. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import platform +import shlex +import subprocess +import sys +from pathlib import Path +from typing import Any + +RECEIPT_SCHEMA = "galaxy.barnes-hut-parallel-gpu-tree-receipt.v1" +MANIFEST_SCHEMA = "galaxy.bh2d-hardware-scaling-manifest.v1" +MAX_PARTICLES = 65_536 +BH2C_CAP = 4_096 +DEFAULT_PARTICLES = "512,1024,2048,4096,8192,16384,32768,65536" + + +class SweepError(RuntimeError): + pass + + +def parse_particles(raw: str) -> list[int]: + try: + values = [int(item.strip()) for item in raw.split(",") if item.strip()] + except ValueError as exc: + raise SweepError("--particles must be a comma-separated list of integers") from exc + if not values: + raise SweepError("--particles must contain at least one value") + if any(value < 2 or value > MAX_PARTICLES for value in values): + raise SweepError(f"every particle count must be in 2..={MAX_PARTICLES}") + if values != sorted(values) or len(set(values)) != len(values): + raise SweepError("--particles must be strictly increasing with no duplicates") + return values + + +def run_checked(command: list[str], cwd: Path) -> str: + completed = subprocess.run( + command, + cwd=cwd, + check=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + if completed.returncode != 0: + detail = completed.stderr.strip() or completed.stdout.strip() + raise SweepError(f"command failed ({completed.returncode}): {' '.join(command)}\n{detail}") + return completed.stdout.strip() + + +def git_revision(repo_root: Path) -> str: + return run_checked(["git", "rev-parse", "HEAD"], repo_root) + + +def require_clean_tracked_tree(repo_root: Path) -> None: + for command in ( + ["git", "diff", "--quiet", "--"], + ["git", "diff", "--cached", "--quiet", "--"], + ): + completed = subprocess.run(command, cwd=repo_root, check=False) + if completed.returncode != 0: + raise SweepError( + "tracked source tree is dirty; commit or revert changes before hardware evidence capture" + ) + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def require(condition: bool, message: str) -> None: + if not condition: + raise SweepError(message) + + +def adapter_identity(gpu: Any) -> str: + require(isinstance(gpu, dict), "receipt.gpu must be an object") + stable = { + key: gpu.get(key) + for key in ("name", "backend", "device_type", "vendor", "device", "driver", "driver_info") + if key in gpu + } + return json.dumps(stable, sort_keys=True, separators=(",", ":")) + + +def validate_receipt( + receipt: dict[str, Any], + *, + particles: int, + steps: int, + oracle_limit: int, + benchmark_repeats: int, +) -> dict[str, Any]: + require(receipt.get("schema") == RECEIPT_SCHEMA, "unexpected BH #2D receipt schema") + require(receipt.get("status") == "complete", "BH #2D receipt is not complete") + require(receipt.get("phase") == "BH-2D", "receipt phase is not BH-2D") + require(receipt.get("builder") == "gpu-parallel-sparse-radix-v1", "unexpected BH #2D builder") + require(receipt.get("particles") == particles, "receipt particle count does not match sweep point") + require(receipt.get("steps") == steps, "receipt step count does not match sweep configuration") + require(receipt.get("measurement_class") == "hardware", "software-validation receipt rejected") + require( + receipt.get("hardware_performance_claim_allowed") is True, + "receipt does not authorize hardware performance evidence", + ) + require(receipt.get("host_tree_rebuilds") == 0, "host tree rebuild occurred") + require( + receipt.get("host_particle_readbacks_during_steps") == 0, + "host particle readback occurred inside the evolution loop", + ) + require(receipt.get("force_solves") == steps + 1, "unexpected force-solve count") + require(receipt.get("evolution_tree_builds") == steps + 1, "unexpected tree-build count") + + tree = receipt.get("tree") + require(isinstance(tree, dict), "receipt.tree must be an object") + require(tree.get("repeat_rebuild_matches") is True, "same-state repeat tree checksum changed") + require(int(tree.get("active_cell_count", 0)) > 0, "receipt reports no active cells") + + force = receipt.get("final_force") + require(isinstance(force, dict), "receipt.final_force must be an object") + require(float(force.get("direct_probe_rms_relative", 1.0)) < 0.04, "direct-force RMS gate failed") + require(float(force.get("direct_probe_max_relative", 1.0)) < 0.30, "direct-force max gate failed") + + oracles = receipt.get("gpu_oracles") + require(isinstance(oracles, dict), "receipt.gpu_oracles must be an object") + expected_oracle_status = "executed" if particles <= oracle_limit else "skipped-particle-limit" + require( + oracles.get("status") == expected_oracle_status, + f"GPU oracle status mismatch: expected {expected_oracle_status}", + ) + + trajectory = receipt.get("trajectory_vs_bh2a_flat_f64") + require(isinstance(trajectory, dict), "trajectory evidence must be an object") + expected_trajectory_status = "executed" if particles <= oracle_limit else "skipped-particle-limit" + require( + trajectory.get("status") == expected_trajectory_status, + f"CPU trajectory status mismatch: expected {expected_trajectory_status}", + ) + + benchmark = receipt.get("tree_build_benchmark") + require(isinstance(benchmark, dict), "tree_build_benchmark must be an object") + require( + benchmark.get("stage_timings_are_diagnostics") is True, + "benchmark must mark stage timings as diagnostics", + ) + samples = benchmark.get("parallel_samples_seconds") + require( + isinstance(samples, list) and len(samples) == benchmark_repeats, + "parallel benchmark sample count mismatch", + ) + median = float(benchmark.get("parallel_median_seconds", -1.0)) + require(median > 0.0, "parallel rebuild median must be positive") + + serial = benchmark.get("bh2c_serial") + require(isinstance(serial, dict), "BH #2C benchmark evidence must be an object") + if particles <= BH2C_CAP: + require(serial.get("status") == "executed", "BH #2C comparison should execute at this size") + speedup = float(serial.get("parallel_vs_serial_speedup", 0.0)) + require(speedup > 0.0, "BH #2C comparison speedup must be positive") + else: + require(serial.get("status") == "skipped-bh2c-cap", "BH #2C comparison must skip above 4096") + speedup = None + + gpu = receipt.get("gpu") + identity = adapter_identity(gpu) + + return { + "particles": particles, + "parallel_tree_buffer_bytes": int(receipt.get("parallel_tree_buffer_bytes", 0)), + "active_cell_count": int(tree.get("active_cell_count", 0)), + "leaf_count": int(tree.get("leaf_count", 0)), + "max_depth": int(tree.get("max_depth", 0)), + "parallel_median_seconds": median, + "bh2c_parallel_vs_serial_speedup": speedup, + "direct_probe_rms_relative": float(force["direct_probe_rms_relative"]), + "direct_probe_max_relative": float(force["direct_probe_max_relative"]), + "adapter_identity": identity, + } + + +def command_for(args: argparse.Namespace, particles: int, receipt: Path) -> list[str]: + command = [ + args.cargo, + "run", + "--release", + "--manifest-path", + "runtime/Cargo.toml", + "--locked", + "--bin", + "galaxy-bh-gpu-tree-parallel", + "--", + "--preset", + args.preset, + "--particles", + str(particles), + "--steps", + str(args.steps), + "--dt-myr", + str(args.dt_myr), + "--seed", + str(args.seed), + "--theta", + str(args.theta), + "--softening-kpc", + str(args.softening_kpc), + "--direct-probes", + str(args.direct_probes), + "--oracle-limit", + str(args.oracle_limit), + "--benchmark-warmup", + str(args.benchmark_warmup), + "--benchmark-repeats", + str(args.benchmark_repeats), + "--require-hardware", + "--receipt", + str(receipt), + ] + if args.adapter: + command.extend(["--adapter", args.adapter]) + return command + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Run a fail-closed real-hardware BH #2D scaling sweep" + ) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--particles", default=DEFAULT_PARTICLES) + parser.add_argument("--preset", choices=("disc", "collision"), default="disc") + parser.add_argument("--steps", type=int, default=3) + parser.add_argument("--dt-myr", type=float, default=0.01) + parser.add_argument("--seed", type=int, default=303) + parser.add_argument("--theta", type=float, default=0.5) + parser.add_argument("--softening-kpc", type=float, default=0.02) + parser.add_argument("--direct-probes", type=int, default=12) + parser.add_argument("--oracle-limit", type=int, default=4096) + parser.add_argument("--benchmark-warmup", type=int, default=2) + parser.add_argument("--benchmark-repeats", type=int, default=7) + parser.add_argument("--adapter") + parser.add_argument("--cargo", default="cargo") + parser.add_argument( + "--dry-run", + action="store_true", + help="print the exact hardware commands without creating files or running cargo", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + repo_root = Path(__file__).resolve().parents[1] + try: + particles_list = parse_particles(args.particles) + require(1 <= args.steps <= 256, "--steps must be in 1..=256") + require(2 <= args.oracle_limit <= BH2C_CAP, "--oracle-limit must be in 2..=4096") + require(1 <= args.benchmark_repeats <= 31, "--benchmark-repeats must be in 1..=31") + require(0 <= args.benchmark_warmup <= 10, "--benchmark-warmup must be in 0..=10") + + if args.dry_run: + for particles in particles_list: + receipt = args.output / f"n{particles:06d}" / "receipt.json" + print(shlex.join(command_for(args, particles, receipt))) + return 0 + + output = args.output.expanduser().resolve() + if output.exists(): + raise SweepError(f"output directory already exists: {output}") + require_clean_tracked_tree(repo_root) + revision = git_revision(repo_root) + + output.mkdir(parents=True) + manifest_path = output / "manifest.json" + manifest: dict[str, Any] = { + "schema": MANIFEST_SCHEMA, + "status": "running", + "claim_boundary": ( + "hardware receipts and scaling observations for the recorded source/workload/adapter only; " + "this manifest does not by itself promote BH #2D to production" + ), + "source_revision": revision, + "host": { + "platform": platform.platform(), + "python": sys.version.split()[0], + "machine": platform.machine(), + }, + "configuration": { + "particles": particles_list, + "preset": args.preset, + "steps": args.steps, + "dt_myr": args.dt_myr, + "seed": args.seed, + "theta": args.theta, + "softening_kpc": args.softening_kpc, + "direct_probes": args.direct_probes, + "oracle_limit": args.oracle_limit, + "benchmark_warmup": args.benchmark_warmup, + "benchmark_repeats": args.benchmark_repeats, + "adapter_selector": args.adapter, + }, + "runs": [], + } + manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + + adapter: str | None = None + for particles in particles_list: + run_dir = output / f"n{particles:06d}" + run_dir.mkdir() + receipt_path = run_dir / "receipt.json" + log_path = run_dir / "run.log" + command = command_for(args, particles, receipt_path) + + completed = subprocess.run( + command, + cwd=repo_root, + check=False, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + log_path.write_text(completed.stdout) + if completed.returncode != 0: + raise SweepError( + f"BH #2D hardware run failed at {particles} particles; see {log_path}" + ) + if not receipt_path.is_file(): + raise SweepError(f"missing receipt after {particles}-particle run") + + receipt = json.loads(receipt_path.read_text()) + summary = validate_receipt( + receipt, + particles=particles, + steps=args.steps, + oracle_limit=args.oracle_limit, + benchmark_repeats=args.benchmark_repeats, + ) + if adapter is None: + adapter = summary["adapter_identity"] + elif summary["adapter_identity"] != adapter: + raise SweepError("adapter identity changed during the scaling sweep") + + summary.update( + { + "receipt": str(receipt_path.relative_to(output)), + "receipt_sha256": sha256_file(receipt_path), + "log": str(log_path.relative_to(output)), + "log_sha256": sha256_file(log_path), + } + ) + summary.pop("adapter_identity") + manifest["runs"].append(summary) + manifest["adapter_identity"] = adapter + manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + + manifest["status"] = "complete" + manifest["completed_run_count"] = len(manifest["runs"]) + manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + print(manifest_path) + return 0 + except (OSError, json.JSONDecodeError, SweepError) as exc: + print(f"BH #2D hardware sweep: {exc}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) From ff1ab082b8cd082e9878bd5f2a50316e605794c0 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:53:28 +0930 Subject: [PATCH 02/82] Test BH #2D hardware sweep receipt gates --- tests/test_bh2d_hardware_sweep.py | 141 ++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 tests/test_bh2d_hardware_sweep.py diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py new file mode 100644 index 0000000..45aeae3 --- /dev/null +++ b/tests/test_bh2d_hardware_sweep.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +import importlib.util +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +MODULE_PATH = ROOT / "scripts" / "bench-bh2d-hardware.py" +SPEC = importlib.util.spec_from_file_location("bench_bh2d_hardware", MODULE_PATH) +sweep = importlib.util.module_from_spec(SPEC) +assert SPEC.loader is not None +SPEC.loader.exec_module(sweep) + + +def receipt_for(particles: int, *, steps: int = 3, oracle_limit: int = 4096, repeats: int = 7): + oracle_status = "executed" if particles <= oracle_limit else "skipped-particle-limit" + serial = ( + { + "status": "executed", + "samples_seconds": [0.01] * repeats, + "median_seconds": 0.01, + "parallel_vs_serial_speedup": 2.0, + } + if particles <= 4096 + else {"status": "skipped-bh2c-cap", "limit": 4096} + ) + return { + "schema": sweep.RECEIPT_SCHEMA, + "status": "complete", + "phase": "BH-2D", + "builder": "gpu-parallel-sparse-radix-v1", + "measurement_class": "hardware", + "hardware_performance_claim_allowed": True, + "particles": particles, + "steps": steps, + "gpu": { + "name": "Synthetic GPU", + "backend": "Vulkan", + "device_type": "DiscreteGpu", + "software": False, + }, + "host_tree_rebuilds": 0, + "host_particle_readbacks_during_steps": 0, + "force_solves": steps + 1, + "evolution_tree_builds": steps + 1, + "parallel_tree_buffer_bytes": particles * 1024, + "tree": { + "repeat_rebuild_matches": True, + "active_cell_count": 17, + "leaf_count": 8, + "max_depth": 6, + }, + "final_force": { + "direct_probe_rms_relative": 0.001, + "direct_probe_max_relative": 0.01, + }, + "trajectory_vs_bh2a_flat_f64": {"status": oracle_status}, + "gpu_oracles": {"status": oracle_status}, + "tree_build_benchmark": { + "stage_timings_are_diagnostics": True, + "parallel_samples_seconds": [0.005] * repeats, + "parallel_median_seconds": 0.005, + "bh2c_serial": serial, + }, + } + + +class Bh2dHardwareSweepTests(unittest.TestCase): + def test_particle_list_must_be_strictly_increasing(self): + self.assertEqual(sweep.parse_particles("512,4096,8192"), [512, 4096, 8192]) + for invalid in ("", "4096,512", "512,512", "1", "65537", "512,nope"): + with self.subTest(invalid=invalid): + with self.assertRaises(sweep.SweepError): + sweep.parse_particles(invalid) + + def test_hardware_receipt_at_oracle_size_is_accepted(self): + summary = sweep.validate_receipt( + receipt_for(4096), + particles=4096, + steps=3, + oracle_limit=4096, + benchmark_repeats=7, + ) + self.assertEqual(summary["particles"], 4096) + self.assertEqual(summary["bh2c_parallel_vs_serial_speedup"], 2.0) + self.assertGreater(summary["parallel_median_seconds"], 0.0) + + def test_large_hardware_receipt_requires_explicit_oracle_skips(self): + summary = sweep.validate_receipt( + receipt_for(8192), + particles=8192, + steps=3, + oracle_limit=4096, + benchmark_repeats=7, + ) + self.assertEqual(summary["particles"], 8192) + self.assertIsNone(summary["bh2c_parallel_vs_serial_speedup"]) + + def test_software_receipt_is_rejected(self): + receipt = receipt_for(512) + receipt["measurement_class"] = "software-validation" + receipt["hardware_performance_claim_allowed"] = False + with self.assertRaisesRegex(sweep.SweepError, "software-validation"): + sweep.validate_receipt( + receipt, + particles=512, + steps=3, + oracle_limit=4096, + benchmark_repeats=7, + ) + + def test_repeat_tree_mismatch_is_rejected(self): + receipt = receipt_for(512) + receipt["tree"]["repeat_rebuild_matches"] = False + with self.assertRaisesRegex(sweep.SweepError, "repeat tree checksum"): + sweep.validate_receipt( + receipt, + particles=512, + steps=3, + oracle_limit=4096, + benchmark_repeats=7, + ) + + def test_bh2c_benchmark_must_skip_above_its_cap(self): + receipt = receipt_for(8192) + receipt["tree_build_benchmark"]["bh2c_serial"] = { + "status": "executed", + "parallel_vs_serial_speedup": 1.0, + } + with self.assertRaisesRegex(sweep.SweepError, "must skip above 4096"): + sweep.validate_receipt( + receipt, + particles=8192, + steps=3, + oracle_limit=4096, + benchmark_repeats=7, + ) + + +if __name__ == "__main__": + unittest.main() From 73b9c8c42ace5299288b541cebac5ddb8bf11d1b Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:54:09 +0930 Subject: [PATCH 03/82] Document BH #2D hardware sweep evidence --- docs/BARNES-HUT.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/BARNES-HUT.md b/docs/BARNES-HUT.md index 8491904..a1a63ab 100644 --- a/docs/BARNES-HUT.md +++ b/docs/BARNES-HUT.md @@ -289,6 +289,28 @@ Kernel families are batched into command buffers to avoid measuring one CPU/GPU Receipts classify the selected adapter as either `software-validation` or `hardware`. `--require-hardware` rejects software adapters. Mesa/llvmpipe timing is therefore correctness and diagnostic evidence only; a hardware performance claim requires a receipt from a real GPU. +### Real-hardware sweep + +`scripts/bench-bh2d-hardware.py` is the evidence runner for the remaining BH #2D hardware item. It executes a strictly increasing resident-body sweep through `galaxy-bh-gpu-tree-parallel --require-hardware`, refuses a dirty tracked source tree or an existing evidence directory, validates every receipt, requires one adapter identity for the complete sweep, and hashes each receipt/log into `manifest.json`. + +The default sweep is: + +```text +512 -> 1024 -> 2048 -> 4096 -> 8192 -> 16384 -> 32768 -> 65536 +``` + +Counts through 4,096 retain repeat-matched BH #2C timing. Larger points explicitly require the BH #2C and bounded full-oracle skips already encoded by the verifier; they do not silently drop those gates. + +Example on a real GPU: + +```bash +python3 scripts/bench-bh2d-hardware.py \ + --output runs/bh2d-hardware-sweep \ + --adapter 0 +``` + +A completed scaling manifest is hardware evidence for the exact recorded source, workload and adapter. It does **not** by itself promote BH #2D to production or establish a host-independent speed claim. + ## Next rung After real-hardware BH #2D receipts exist, BH #2E can evaluate scaling, memory/capacity limits and production promotion while retaining BH #2A/B2B2/B2C as frozen oracles. From 131f62d8ceb09be386f3e3cedd029eda5aae57b9 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:54:13 +0930 Subject: [PATCH 04/82] Add BH #2D hardware evidence protocol to roadmap --- ROADMAP.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index 28ba21a..ce3ef91 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -199,6 +199,8 @@ Stable radix ordering starts from resident-body order and preserves that order f The initial CI fixture proves correctness through Mesa Vulkan but does not establish a hardware speedup. Real GPU performance evidence remains an explicit BH #2D completion item. +The repository now includes `scripts/bench-bh2d-hardware.py` to capture that evidence without weakening the boundary. The runner requires a clean tracked source tree and `--require-hardware`, validates the BH #2D correctness/determinism gates at every point, preserves repeat-matched BH #2C comparison through 4,096 bodies, and emits a source-pinned scaling manifest with receipt/log hashes. The harness is implementation support; BH #2D remains evidence-pending until a real-GPU manifest exists. + ## BH #2E — Hardware Promotion and Scaling After BH #2D obtains real-hardware receipts, the next promotion rung is: From 761dd6c32a1e9c6d03b2e52e56ca7eceec3b6ea6 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:54:16 +0930 Subject: [PATCH 05/82] Document BH #2D hardware sweep runner --- docs/GPU-RUNTIME.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/GPU-RUNTIME.md b/docs/GPU-RUNTIME.md index 362025c..4eb199d 100644 --- a/docs/GPU-RUNTIME.md +++ b/docs/GPU-RUNTIME.md @@ -275,6 +275,16 @@ It also records `hardware_performance_claim_allowed`. `--require-hardware` fails Tree-build stage families are batched into command buffers before synchronization. Reported stage times cover bounds, Morton generation, radix ordering, target-position assignment, topology and aggregates rather than one host synchronization per small kernel. +For repeatable real-hardware scaling evidence, use the fail-closed sweep runner: + +```bash +python3 scripts/bench-bh2d-hardware.py \ + --output runs/bh2d-hardware-sweep \ + --adapter 0 +``` + +The runner defaults to 512 through 65,536 resident bodies, refuses software receipts and dirty tracked source, keeps the selected adapter fixed across the sweep, validates the BH #2D correctness/oracle status encoded in every receipt, and records SHA-256 hashes for the receipts and logs in a source-revision-pinned `manifest.json`. Use `--dry-run` to inspect the exact commands without executing GPU work. + ## Physics and numerical behavior The source remains UFF commit From 14b999a5e27f3bacc8a20e87504841bce6431507 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:54:45 +0930 Subject: [PATCH 06/82] Validate BH #2D hardware sweep host contract in CI --- .github/workflows/native-gpu.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/native-gpu.yml b/.github/workflows/native-gpu.yml index c35a9f4..e2e47a3 100644 --- a/.github/workflows/native-gpu.yml +++ b/.github/workflows/native-gpu.yml @@ -3,7 +3,7 @@ name: Native GPU runtime on: push: branches: [main] - paths: ['runtime/**', 'rust/**', 'nbody/**', 'data/uff/**', 'tests/uff-reference.json', 'scripts/*gpu*', 'scripts/vast-runner.py', 'rust-toolchain.toml', '.github/workflows/native-gpu.yml', '.dockerignore', 'LICENSE', 'NOTICE.md'] + paths: ['runtime/**', 'rust/**', 'nbody/**', 'data/uff/**', 'tests/uff-reference.json', 'tests/test_bh2d_hardware_sweep.py', 'scripts/*gpu*', 'scripts/bench-bh2d-hardware.py', 'scripts/vast-runner.py', 'rust-toolchain.toml', '.github/workflows/native-gpu.yml', '.dockerignore', 'LICENSE', 'NOTICE.md'] pull_request: paths: ['runtime/**', 'rust/**', 'nbody/**', 'data/uff/**', 'tests/uff-reference.json', 'scripts/*gpu*', 'scripts/vast-runner.py', 'rust-toolchain.toml', '.github/workflows/native-gpu.yml', '.dockerignore', 'LICENSE', 'NOTICE.md'] workflow_dispatch: @@ -60,6 +60,8 @@ jobs: bash -n scripts/run-gpu.sh scripts/bootstrap-gpu.sh python runtime/tests/test_runner.py python runtime/cuda/check_barnes_hut_layout.py + python tests/test_bh2d_hardware_sweep.py + python scripts/bench-bh2d-hardware.py --dry-run --output "$RUNNER_TEMP/bh2d-plan" --particles 512,4096,8192 - name: Install software Vulkan for compute validation run: | sudo apt-get update -qq From ff6b03935ca8a26fe646af84b3ba145671b24a81 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:55:36 +0930 Subject: [PATCH 07/82] Preserve failed BH #2D sweep manifests --- scripts/bench-bh2d-hardware.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index bafbaf7..14c2990 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -247,7 +247,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--dt-myr", type=float, default=0.01) parser.add_argument("--seed", type=int, default=303) parser.add_argument("--theta", type=float, default=0.5) - parser.add_argument("--softening-kpc", type=float, default=0.02) + parser.add_argument("--softening-kpc", type=float, default=0.05) parser.add_argument("--direct-probes", type=int, default=12) parser.add_argument("--oracle-limit", type=int, default=4096) parser.add_argument("--benchmark-warmup", type=int, default=2) @@ -265,6 +265,8 @@ def parse_args() -> argparse.Namespace: def main() -> int: args = parse_args() repo_root = Path(__file__).resolve().parents[1] + manifest_path: Path | None = None + manifest: dict[str, Any] | None = None try: particles_list = parse_particles(args.particles) require(1 <= args.steps <= 256, "--steps must be in 1..=256") @@ -286,7 +288,7 @@ def main() -> int: output.mkdir(parents=True) manifest_path = output / "manifest.json" - manifest: dict[str, Any] = { + manifest = { "schema": MANIFEST_SCHEMA, "status": "running", "claim_boundary": ( @@ -373,6 +375,13 @@ def main() -> int: print(manifest_path) return 0 except (OSError, json.JSONDecodeError, SweepError) as exc: + if manifest_path is not None and manifest is not None and manifest_path.parent.exists(): + manifest["status"] = "failed" + manifest["error"] = str(exc) + try: + manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + except OSError: + pass print(f"BH #2D hardware sweep: {exc}", file=sys.stderr) return 1 From 20af0f0d035c8bc8c7341f21633ced6feed9da16 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:55:53 +0930 Subject: [PATCH 08/82] Trigger native GPU CI for BH #2D sweep changes --- .github/workflows/native-gpu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/native-gpu.yml b/.github/workflows/native-gpu.yml index e2e47a3..95a0be4 100644 --- a/.github/workflows/native-gpu.yml +++ b/.github/workflows/native-gpu.yml @@ -5,7 +5,7 @@ on: branches: [main] paths: ['runtime/**', 'rust/**', 'nbody/**', 'data/uff/**', 'tests/uff-reference.json', 'tests/test_bh2d_hardware_sweep.py', 'scripts/*gpu*', 'scripts/bench-bh2d-hardware.py', 'scripts/vast-runner.py', 'rust-toolchain.toml', '.github/workflows/native-gpu.yml', '.dockerignore', 'LICENSE', 'NOTICE.md'] pull_request: - paths: ['runtime/**', 'rust/**', 'nbody/**', 'data/uff/**', 'tests/uff-reference.json', 'scripts/*gpu*', 'scripts/vast-runner.py', 'rust-toolchain.toml', '.github/workflows/native-gpu.yml', '.dockerignore', 'LICENSE', 'NOTICE.md'] + paths: ['runtime/**', 'rust/**', 'nbody/**', 'data/uff/**', 'tests/uff-reference.json', 'tests/test_bh2d_hardware_sweep.py', 'scripts/*gpu*', 'scripts/bench-bh2d-hardware.py', 'scripts/vast-runner.py', 'rust-toolchain.toml', '.github/workflows/native-gpu.yml', '.dockerignore', 'LICENSE', 'NOTICE.md'] workflow_dispatch: permissions: From d142ca835ae6e27883621535d9b5d151494e57a3 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:55:57 +0930 Subject: [PATCH 09/82] Document BH #2D hardware sweep in README --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index bb3cc67..7fb5da8 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,16 @@ BH #2D is the active optimization path. It retains BH #2C and BH #2B2 as executa BH #2D correctness is exercised through software Vulkan in CI, but **software-Vulkan timings are not hardware performance evidence**. A hardware speed claim requires a non-software adapter receipt. +The remaining BH #2D evidence item has a fail-closed real-GPU sweep runner: + +```bash +python3 scripts/bench-bh2d-hardware.py \ + --output runs/bh2d-hardware-sweep \ + --adapter 0 +``` + +It runs a source-pinned 512→65,536 resident-body sweep by default, requires hardware receipts, validates the frozen correctness/oracle gates, and hashes each receipt/log into a scaling manifest. The manifest is evidence for that exact source/workload/adapter, not an automatic production-promotion claim. + `--allow-software` enables verification through software Vulkan and must not be interpreted as hardware GPU performance evidence. The CUDA checker freezes ABI/source parity without claiming CUDA execution where no NVIDIA CUDA run occurred. @@ -467,6 +477,9 @@ sh scripts/test-cpu-runtime.sh # Retro CPU portability sh scripts/test-retro-cpu.sh +# GPU evidence-runner host-side tests +python3 tests/test_bh2d_hardware_sweep.py + # CUDA host-side tests python3 tests/test_cuda_u64.py ``` From 3d35cfd7761aeb7e58e2fed28ff4139a52698678 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:08:14 +0930 Subject: [PATCH 10/82] Harden BH #2D hardware evidence validation --- scripts/bench-bh2d-hardware.py | 456 ++++++++++++++++++++++++++++----- 1 file changed, 396 insertions(+), 60 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 14c2990..1515010 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -13,7 +13,7 @@ import argparse import hashlib import json -import os +import math import platform import shlex import subprocess @@ -58,14 +58,25 @@ def run_checked(command: list[str], cwd: Path) -> str: if completed.returncode != 0: detail = completed.stderr.strip() or completed.stdout.strip() raise SweepError(f"command failed ({completed.returncode}): {' '.join(command)}\n{detail}") - return completed.stdout.strip() + return completed.stdout def git_revision(repo_root: Path) -> str: - return run_checked(["git", "rev-parse", "HEAD"], repo_root) + return run_checked(["git", "rev-parse", "HEAD"], repo_root).strip() -def require_clean_tracked_tree(repo_root: Path) -> None: +def is_within(path: Path, root: Path) -> bool: + try: + path.relative_to(root) + return True + except ValueError: + return False + + +def require_clean_source_tree( + repo_root: Path, + allowed_untracked_root: Path | None = None, +) -> None: for command in ( ["git", "diff", "--quiet", "--"], ["git", "diff", "--cached", "--quiet", "--"], @@ -76,6 +87,38 @@ def require_clean_tracked_tree(repo_root: Path) -> None: "tracked source tree is dirty; commit or revert changes before hardware evidence capture" ) + raw_untracked = run_checked( + ["git", "ls-files", "--others", "--exclude-standard", "-z"], + repo_root, + ) + allowed = allowed_untracked_root.resolve() if allowed_untracked_root is not None else None + unexpected: list[str] = [] + for relative in (item for item in raw_untracked.split("\0") if item): + candidate = (repo_root / relative).resolve() + if allowed is not None and is_within(candidate, allowed): + continue + unexpected.append(relative) + if unexpected: + preview = ", ".join(repr(path) for path in unexpected[:8]) + suffix = "" if len(unexpected) <= 8 else f", ... (+{len(unexpected) - 8} more)" + raise SweepError( + "untracked files are present outside the evidence output; " + f"commit, remove, or ignore them only after proving they cannot affect the build: {preview}{suffix}" + ) + + +def require_source_provenance( + repo_root: Path, + revision: str, + allowed_untracked_root: Path | None = None, +) -> None: + current = git_revision(repo_root) + if current != revision: + raise SweepError( + f"source revision changed during hardware evidence capture: expected {revision}, got {current}" + ) + require_clean_source_tree(repo_root, allowed_untracked_root) + def sha256_file(path: Path) -> str: digest = hashlib.sha256() @@ -90,106 +133,386 @@ def require(condition: bool, message: str) -> None: raise SweepError(message) +def require_object(value: Any, name: str) -> dict[str, Any]: + require(isinstance(value, dict), f"{name} must be an object") + return value + + +def require_list(value: Any, name: str) -> list[Any]: + require(isinstance(value, list), f"{name} must be an array") + return value + + +def require_int(value: Any, name: str, *, minimum: int | None = None) -> int: + require(isinstance(value, int) and not isinstance(value, bool), f"{name} must be an integer") + if minimum is not None: + require(value >= minimum, f"{name} must be >= {minimum}") + return value + + +def require_number( + value: Any, + name: str, + *, + positive: bool = False, + nonnegative: bool = False, +) -> float: + require( + isinstance(value, (int, float)) and not isinstance(value, bool), + f"{name} must be numeric", + ) + numeric = float(value) + require(math.isfinite(numeric), f"{name} must be finite") + if positive: + require(numeric > 0.0, f"{name} must be positive") + if nonnegative: + require(numeric >= 0.0, f"{name} must be nonnegative") + return numeric + + +def require_string(value: Any, name: str, *, nonempty: bool = False) -> str: + require(isinstance(value, str), f"{name} must be a string") + if nonempty: + require(bool(value.strip()), f"{name} must not be empty") + return value + + +def require_same_number(actual: Any, expected: float, name: str) -> float: + numeric = require_number(actual, name) + require(numeric == float(expected), f"{name} does not match requested workload") + return numeric + + +def upper_median(values: list[float]) -> float: + ordered = sorted(values) + return ordered[len(ordered) // 2] + + +def validated_samples(value: Any, name: str, expected_count: int) -> list[float]: + raw = require_list(value, name) + require(len(raw) == expected_count, f"{name} sample count mismatch") + return [ + require_number(sample, f"{name}[{index}]", positive=True) + for index, sample in enumerate(raw) + ] + + +def validate_state_error(value: Any, name: str) -> dict[str, float]: + state = require_object(value, name) + result = { + "position_rms_relative_l2": require_number( + state.get("position_rms_relative_l2"), + f"{name}.position_rms_relative_l2", + nonnegative=True, + ), + "position_max_relative": require_number( + state.get("position_max_relative"), + f"{name}.position_max_relative", + nonnegative=True, + ), + "velocity_rms_relative_l2": require_number( + state.get("velocity_rms_relative_l2"), + f"{name}.velocity_rms_relative_l2", + nonnegative=True, + ), + "velocity_max_relative": require_number( + state.get("velocity_max_relative"), + f"{name}.velocity_max_relative", + nonnegative=True, + ), + } + require(result["position_rms_relative_l2"] < 0.03, f"{name} position RMS gate failed") + require(result["position_max_relative"] < 0.30, f"{name} position max gate failed") + require(result["velocity_rms_relative_l2"] < 0.03, f"{name} velocity RMS gate failed") + require(result["velocity_max_relative"] < 0.30, f"{name} velocity max gate failed") + return result + + +def validate_force_errors(value: Any, name: str) -> tuple[float, float]: + evidence = require_object(value, name) + rms = require_number( + evidence.get("force_rms_relative"), + f"{name}.force_rms_relative", + nonnegative=True, + ) + maximum = require_number( + evidence.get("force_max_relative"), + f"{name}.force_max_relative", + nonnegative=True, + ) + require(rms < 0.04, f"{name} force RMS gate failed") + require(maximum < 0.30, f"{name} force max gate failed") + return rms, maximum + + def adapter_identity(gpu: Any) -> str: - require(isinstance(gpu, dict), "receipt.gpu must be an object") - stable = { - key: gpu.get(key) - for key in ("name", "backend", "device_type", "vendor", "device", "driver", "driver_info") - if key in gpu + info = require_object(gpu, "receipt.gpu") + required = { + "name": require_string(info.get("name"), "receipt.gpu.name", nonempty=True), + "backend": require_string(info.get("backend"), "receipt.gpu.backend", nonempty=True), + "device_type": require_string( + info.get("device_type"), "receipt.gpu.device_type", nonempty=True + ), + "driver": require_string(info.get("driver"), "receipt.gpu.driver"), + "driver_info": require_string(info.get("driver_info"), "receipt.gpu.driver_info"), } - return json.dumps(stable, sort_keys=True, separators=(",", ":")) + require( + bool(required["driver"].strip() or required["driver_info"].strip()), + "receipt.gpu must include nonempty driver or driver_info provenance", + ) + require(info.get("software") is False, "receipt.gpu.software must be false for hardware evidence") + return json.dumps(required, sort_keys=True, separators=(",", ":")) def validate_receipt( - receipt: dict[str, Any], + receipt: Any, *, particles: int, + preset: str, steps: int, + dt_myr: float, + seed: int, + theta: float, + softening_kpc: float, + direct_probes: int, oracle_limit: int, + benchmark_warmup: int, benchmark_repeats: int, ) -> dict[str, Any]: - require(receipt.get("schema") == RECEIPT_SCHEMA, "unexpected BH #2D receipt schema") - require(receipt.get("status") == "complete", "BH #2D receipt is not complete") - require(receipt.get("phase") == "BH-2D", "receipt phase is not BH-2D") - require(receipt.get("builder") == "gpu-parallel-sparse-radix-v1", "unexpected BH #2D builder") - require(receipt.get("particles") == particles, "receipt particle count does not match sweep point") - require(receipt.get("steps") == steps, "receipt step count does not match sweep configuration") - require(receipt.get("measurement_class") == "hardware", "software-validation receipt rejected") + root = require_object(receipt, "receipt") + require(root.get("schema") == RECEIPT_SCHEMA, "unexpected BH #2D receipt schema") + require(root.get("status") == "complete", "BH #2D receipt is not complete") + require(root.get("phase") == "BH-2D", "receipt phase is not BH-2D") + require(root.get("builder") == "gpu-parallel-sparse-radix-v1", "unexpected BH #2D builder") + + require(root.get("preset") == preset, "receipt preset does not match requested workload") + require_int(root.get("particles"), "receipt.particles") + require(root.get("particles") == particles, "receipt particle count does not match sweep point") + require_int(root.get("steps"), "receipt.steps") + require(root.get("steps") == steps, "receipt step count does not match sweep configuration") + require_same_number(root.get("dt_myr"), dt_myr, "receipt.dt_myr") + require_int(root.get("seed"), "receipt.seed") + require(root.get("seed") == seed, "receipt seed does not match requested workload") + require_same_number(root.get("theta"), theta, "receipt.theta") + require_same_number( + root.get("softening_kpc"), + softening_kpc, + "receipt.softening_kpc", + ) + require_same_number( + root.get("simulated_time_myr"), + dt_myr * steps, + "receipt.simulated_time_myr", + ) + + require(root.get("measurement_class") == "hardware", "software-validation receipt rejected") require( - receipt.get("hardware_performance_claim_allowed") is True, + root.get("hardware_performance_claim_allowed") is True, "receipt does not authorize hardware performance evidence", ) - require(receipt.get("host_tree_rebuilds") == 0, "host tree rebuild occurred") + require(root.get("host_tree_rebuilds") == 0, "host tree rebuild occurred") require( - receipt.get("host_particle_readbacks_during_steps") == 0, + root.get("host_particle_readbacks_during_steps") == 0, "host particle readback occurred inside the evolution loop", ) - require(receipt.get("force_solves") == steps + 1, "unexpected force-solve count") - require(receipt.get("evolution_tree_builds") == steps + 1, "unexpected tree-build count") + require(root.get("force_solves") == steps + 1, "unexpected force-solve count") + require(root.get("evolution_tree_builds") == steps + 1, "unexpected tree-build count") + + identity = adapter_identity(root.get("gpu")) - tree = receipt.get("tree") - require(isinstance(tree, dict), "receipt.tree must be an object") + tree = require_object(root.get("tree"), "receipt.tree") require(tree.get("repeat_rebuild_matches") is True, "same-state repeat tree checksum changed") - require(int(tree.get("active_cell_count", 0)) > 0, "receipt reports no active cells") + active_cell_count = require_int( + tree.get("active_cell_count"), + "receipt.tree.active_cell_count", + minimum=1, + ) + leaf_count = require_int(tree.get("leaf_count"), "receipt.tree.leaf_count", minimum=1) + max_depth = require_int(tree.get("max_depth"), "receipt.tree.max_depth", minimum=0) - force = receipt.get("final_force") - require(isinstance(force, dict), "receipt.final_force must be an object") - require(float(force.get("direct_probe_rms_relative", 1.0)) < 0.04, "direct-force RMS gate failed") - require(float(force.get("direct_probe_max_relative", 1.0)) < 0.30, "direct-force max gate failed") + force = require_object(root.get("final_force"), "receipt.final_force") + expected_probe_count = min(direct_probes, particles) + require_int(force.get("direct_probe_count"), "receipt.final_force.direct_probe_count") + require( + force.get("direct_probe_count") == expected_probe_count, + "receipt direct-probe count does not match requested workload", + ) + direct_rms = require_number( + force.get("direct_probe_rms_relative"), + "receipt.final_force.direct_probe_rms_relative", + nonnegative=True, + ) + direct_max = require_number( + force.get("direct_probe_max_relative"), + "receipt.final_force.direct_probe_max_relative", + nonnegative=True, + ) + require(direct_rms < 0.04, "direct-force RMS gate failed") + require(direct_max < 0.30, "direct-force max gate failed") - oracles = receipt.get("gpu_oracles") - require(isinstance(oracles, dict), "receipt.gpu_oracles must be an object") expected_oracle_status = "executed" if particles <= oracle_limit else "skipped-particle-limit" + require( + force.get("bh2a_flat_status") == expected_oracle_status, + f"BH #2A final-force status mismatch: expected {expected_oracle_status}", + ) + if expected_oracle_status == "executed": + flat_rms = require_number( + force.get("gpu_vs_bh2a_flat_rms_relative"), + "receipt.final_force.gpu_vs_bh2a_flat_rms_relative", + nonnegative=True, + ) + flat_max = require_number( + force.get("gpu_vs_bh2a_flat_max_relative"), + "receipt.final_force.gpu_vs_bh2a_flat_max_relative", + nonnegative=True, + ) + require(flat_rms < 0.04, "BH #2A final-force RMS gate failed") + require(flat_max < 0.30, "BH #2A final-force max gate failed") + + oracles = require_object(root.get("gpu_oracles"), "receipt.gpu_oracles") require( oracles.get("status") == expected_oracle_status, f"GPU oracle status mismatch: expected {expected_oracle_status}", ) - - trajectory = receipt.get("trajectory_vs_bh2a_flat_f64") - require(isinstance(trajectory, dict), "trajectory evidence must be an object") - expected_trajectory_status = "executed" if particles <= oracle_limit else "skipped-particle-limit" + if expected_oracle_status == "executed": + for key, label in ( + ("bh2c_serial_gpu", "receipt.gpu_oracles.bh2c_serial_gpu"), + ("bh2b2_host_tree_gpu", "receipt.gpu_oracles.bh2b2_host_tree_gpu"), + ): + oracle = require_object(oracles.get(key), label) + validate_state_error(oracle.get("state_error"), f"{label}.state_error") + validate_force_errors(oracle, label) + else: + require( + require_int(oracles.get("limit"), "receipt.gpu_oracles.limit") == oracle_limit, + "GPU oracle skip limit does not match requested oracle limit", + ) + + trajectory = require_object( + root.get("trajectory_vs_bh2a_flat_f64"), + "receipt.trajectory_vs_bh2a_flat_f64", + ) require( - trajectory.get("status") == expected_trajectory_status, - f"CPU trajectory status mismatch: expected {expected_trajectory_status}", + trajectory.get("status") == expected_oracle_status, + f"CPU trajectory status mismatch: expected {expected_oracle_status}", ) + if expected_oracle_status == "executed": + validate_state_error( + trajectory.get("state_error"), + "receipt.trajectory_vs_bh2a_flat_f64.state_error", + ) + else: + require( + require_int( + trajectory.get("limit"), + "receipt.trajectory_vs_bh2a_flat_f64.limit", + ) + == oracle_limit, + "CPU trajectory skip limit does not match requested oracle limit", + ) - benchmark = receipt.get("tree_build_benchmark") - require(isinstance(benchmark, dict), "tree_build_benchmark must be an object") + benchmark = require_object(root.get("tree_build_benchmark"), "receipt.tree_build_benchmark") + require( + require_string( + benchmark.get("sample_scope"), + "receipt.tree_build_benchmark.sample_scope", + nonempty=True, + ).startswith("complete rebuild call"), + "benchmark sample scope does not cover the complete rebuild call", + ) require( benchmark.get("stage_timings_are_diagnostics") is True, "benchmark must mark stage timings as diagnostics", ) - samples = benchmark.get("parallel_samples_seconds") require( - isinstance(samples, list) and len(samples) == benchmark_repeats, - "parallel benchmark sample count mismatch", + require_int(benchmark.get("warmup"), "receipt.tree_build_benchmark.warmup") + == benchmark_warmup, + "benchmark warmup count does not match requested workload", + ) + require( + require_int(benchmark.get("repeats"), "receipt.tree_build_benchmark.repeats") + == benchmark_repeats, + "benchmark repeat count does not match requested workload", ) - median = float(benchmark.get("parallel_median_seconds", -1.0)) - require(median > 0.0, "parallel rebuild median must be positive") - serial = benchmark.get("bh2c_serial") - require(isinstance(serial, dict), "BH #2C benchmark evidence must be an object") + parallel_samples = validated_samples( + benchmark.get("parallel_samples_seconds"), + "receipt.tree_build_benchmark.parallel_samples_seconds", + benchmark_repeats, + ) + parallel_median = upper_median(parallel_samples) + producer_parallel_median = require_number( + benchmark.get("parallel_median_seconds"), + "receipt.tree_build_benchmark.parallel_median_seconds", + positive=True, + ) + require( + producer_parallel_median == parallel_median, + "parallel benchmark median does not match its samples", + ) + + serial = require_object( + benchmark.get("bh2c_serial"), + "receipt.tree_build_benchmark.bh2c_serial", + ) if particles <= BH2C_CAP: require(serial.get("status") == "executed", "BH #2C comparison should execute at this size") - speedup = float(serial.get("parallel_vs_serial_speedup", 0.0)) - require(speedup > 0.0, "BH #2C comparison speedup must be positive") + serial_samples = validated_samples( + serial.get("samples_seconds"), + "receipt.tree_build_benchmark.bh2c_serial.samples_seconds", + benchmark_repeats, + ) + serial_median = upper_median(serial_samples) + producer_serial_median = require_number( + serial.get("median_seconds"), + "receipt.tree_build_benchmark.bh2c_serial.median_seconds", + positive=True, + ) + require( + producer_serial_median == serial_median, + "BH #2C benchmark median does not match its samples", + ) + speedup = serial_median / parallel_median + producer_speedup = require_number( + serial.get("parallel_vs_serial_speedup"), + "receipt.tree_build_benchmark.bh2c_serial.parallel_vs_serial_speedup", + positive=True, + ) + require( + producer_speedup == speedup, + "BH #2C speedup does not match the validated benchmark medians", + ) else: - require(serial.get("status") == "skipped-bh2c-cap", "BH #2C comparison must skip above 4096") + require( + serial.get("status") == "skipped-bh2c-cap", + "BH #2C comparison must skip above 4096", + ) + require( + require_int( + serial.get("limit"), + "receipt.tree_build_benchmark.bh2c_serial.limit", + ) + == BH2C_CAP, + "BH #2C benchmark skip limit is invalid", + ) speedup = None - gpu = receipt.get("gpu") - identity = adapter_identity(gpu) + parallel_tree_buffer_bytes = require_int( + root.get("parallel_tree_buffer_bytes"), + "receipt.parallel_tree_buffer_bytes", + minimum=1, + ) return { "particles": particles, - "parallel_tree_buffer_bytes": int(receipt.get("parallel_tree_buffer_bytes", 0)), - "active_cell_count": int(tree.get("active_cell_count", 0)), - "leaf_count": int(tree.get("leaf_count", 0)), - "max_depth": int(tree.get("max_depth", 0)), - "parallel_median_seconds": median, + "parallel_tree_buffer_bytes": parallel_tree_buffer_bytes, + "active_cell_count": active_cell_count, + "leaf_count": leaf_count, + "max_depth": max_depth, + "parallel_median_seconds": parallel_median, "bh2c_parallel_vs_serial_speedup": speedup, - "direct_probe_rms_relative": float(force["direct_probe_rms_relative"]), - "direct_probe_max_relative": float(force["direct_probe_max_relative"]), + "direct_probe_rms_relative": direct_rms, + "direct_probe_max_relative": direct_max, "adapter_identity": identity, } @@ -283,8 +606,9 @@ def main() -> int: output = args.output.expanduser().resolve() if output.exists(): raise SweepError(f"output directory already exists: {output}") - require_clean_tracked_tree(repo_root) + revision = git_revision(repo_root) + require_source_provenance(repo_root, revision) output.mkdir(parents=True) manifest_path = output / "manifest.json" @@ -321,6 +645,8 @@ def main() -> int: adapter: str | None = None for particles in particles_list: + require_source_provenance(repo_root, revision, output) + run_dir = output / f"n{particles:06d}" run_dir.mkdir() receipt_path = run_dir / "receipt.json" @@ -336,6 +662,8 @@ def main() -> int: text=True, ) log_path.write_text(completed.stdout) + + require_source_provenance(repo_root, revision, output) if completed.returncode != 0: raise SweepError( f"BH #2D hardware run failed at {particles} particles; see {log_path}" @@ -347,8 +675,15 @@ def main() -> int: summary = validate_receipt( receipt, particles=particles, + preset=args.preset, steps=args.steps, + dt_myr=args.dt_myr, + seed=args.seed, + theta=args.theta, + softening_kpc=args.softening_kpc, + direct_probes=args.direct_probes, oracle_limit=args.oracle_limit, + benchmark_warmup=args.benchmark_warmup, benchmark_repeats=args.benchmark_repeats, ) if adapter is None: @@ -369,6 +704,7 @@ def main() -> int: manifest["adapter_identity"] = adapter manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + require_source_provenance(repo_root, revision, output) manifest["status"] = "complete" manifest["completed_run_count"] = len(manifest["runs"]) manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") From 2570e34baa509dd74420a439bf1a59ee9660d22b Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:09:44 +0930 Subject: [PATCH 11/82] Cover BH #2D evidence integrity regressions --- tests/test_bh2d_hardware_sweep.py | 301 ++++++++++++++++++++++++++---- 1 file changed, 260 insertions(+), 41 deletions(-) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 45aeae3..a6aaf9a 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 import importlib.util +import subprocess +import tempfile import unittest from pathlib import Path @@ -12,8 +14,61 @@ SPEC.loader.exec_module(sweep) -def receipt_for(particles: int, *, steps: int = 3, oracle_limit: int = 4096, repeats: int = 7): +STATE_ERROR = { + "position_rms_relative_l2": 0.001, + "position_max_relative": 0.01, + "velocity_rms_relative_l2": 0.001, + "velocity_max_relative": 0.01, +} + + +def receipt_for( + particles: int, + *, + preset: str = "disc", + steps: int = 3, + dt_myr: float = 0.01, + seed: int = 303, + theta: float = 0.5, + softening_kpc: float = 0.05, + direct_probes: int = 12, + oracle_limit: int = 4096, + warmup: int = 2, + repeats: int = 7, +): oracle_status = "executed" if particles <= oracle_limit else "skipped-particle-limit" + if oracle_status == "executed": + oracles = { + "status": "executed", + "bh2c_serial_gpu": { + "state_error": dict(STATE_ERROR), + "force_rms_relative": 0.001, + "force_max_relative": 0.01, + }, + "bh2b2_host_tree_gpu": { + "state_error": dict(STATE_ERROR), + "force_rms_relative": 0.001, + "force_max_relative": 0.01, + }, + } + trajectory = {"status": "executed", "state_error": dict(STATE_ERROR)} + flat_rms = 0.001 + flat_max = 0.01 + else: + oracles = { + "status": "skipped-particle-limit", + "limit": oracle_limit, + "reason": "bounded oracle", + } + trajectory = { + "status": "skipped-particle-limit", + "limit": oracle_limit, + "reason": "bounded trajectory", + } + flat_rms = None + flat_max = None + + parallel_samples = [0.005] * repeats serial = ( { "status": "executed", @@ -31,12 +86,20 @@ def receipt_for(particles: int, *, steps: int = 3, oracle_limit: int = 4096, rep "builder": "gpu-parallel-sparse-radix-v1", "measurement_class": "hardware", "hardware_performance_claim_allowed": True, + "preset": preset, "particles": particles, "steps": steps, + "dt_myr": dt_myr, + "simulated_time_myr": dt_myr * steps, + "seed": seed, + "theta": theta, + "softening_kpc": softening_kpc, "gpu": { "name": "Synthetic GPU", "backend": "Vulkan", "device_type": "DiscreteGpu", + "driver": "synthetic-driver", + "driver_info": "1.0", "software": False, }, "host_tree_rebuilds": 0, @@ -51,20 +114,48 @@ def receipt_for(particles: int, *, steps: int = 3, oracle_limit: int = 4096, rep "max_depth": 6, }, "final_force": { + "bh2a_flat_status": oracle_status, + "gpu_vs_bh2a_flat_rms_relative": flat_rms, + "gpu_vs_bh2a_flat_max_relative": flat_max, + "direct_probe_count": min(direct_probes, particles), "direct_probe_rms_relative": 0.001, "direct_probe_max_relative": 0.01, }, - "trajectory_vs_bh2a_flat_f64": {"status": oracle_status}, - "gpu_oracles": {"status": oracle_status}, + "trajectory_vs_bh2a_flat_f64": trajectory, + "gpu_oracles": oracles, "tree_build_benchmark": { + "sample_scope": ( + "complete rebuild call including host-side uniform/bind-group setup, " + "command encoding, submit and synchronization" + ), "stage_timings_are_diagnostics": True, - "parallel_samples_seconds": [0.005] * repeats, + "warmup": warmup, + "repeats": repeats, + "parallel_samples_seconds": parallel_samples, "parallel_median_seconds": 0.005, "bh2c_serial": serial, }, } +def validation_kwargs(particles: int, **overrides): + values = { + "particles": particles, + "preset": "disc", + "steps": 3, + "dt_myr": 0.01, + "seed": 303, + "theta": 0.5, + "softening_kpc": 0.05, + "direct_probes": 12, + "oracle_limit": 4096, + "benchmark_warmup": 2, + "benchmark_repeats": 7, + } + values.update(overrides) + return values + + class Bh2dHardwareSweepTests(unittest.TestCase): def test_particle_list_must_be_strictly_increasing(self): self.assertEqual(sweep.parse_particles("512,4096,8192"), [512, 4096, 8192]) @@ -73,68 +164,196 @@ def test_particle_list_must_be_strictly_increasing(self): with self.assertRaises(sweep.SweepError): sweep.parse_particles(invalid) + def test_cleanliness_rejects_untracked_cargo_config(self): + with tempfile.TemporaryDirectory() as tmp: + repo = Path(tmp) + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + subprocess.run(["git", "config", "user.name", "GALAXY Test"], cwd=repo, check=True) + subprocess.run( + ["git", "config", "user.email", "galaxy-test@example.invalid"], + cwd=repo, + check=True, + ) + (repo / "tracked.txt").write_text("tracked\n") + subprocess.run(["git", "add", "tracked.txt"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "fixture"], cwd=repo, check=True) + + cargo = repo / ".cargo" + cargo.mkdir() + (cargo / "config.toml").write_text('[build]\nrustflags = ["-C", "target-cpu=native"]\n') + + with self.assertRaisesRegex(sweep.SweepError, "untracked files"): + sweep.require_clean_source_tree(repo) + + def test_cleanliness_allows_only_the_evidence_output(self): + with tempfile.TemporaryDirectory() as tmp: + repo = Path(tmp) + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + subprocess.run(["git", "config", "user.name", "GALAXY Test"], cwd=repo, check=True) + subprocess.run( + ["git", "config", "user.email", "galaxy-test@example.invalid"], + cwd=repo, + check=True, + ) + (repo / "tracked.txt").write_text("tracked\n") + subprocess.run(["git", "add", "tracked.txt"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "fixture"], cwd=repo, check=True) + + output = repo / "runs" / "evidence" + output.mkdir(parents=True) + (output / "manifest.json").write_text("{}\n") + sweep.require_clean_source_tree(repo, output) + + (repo / "unexpected.txt").write_text("changes build provenance\n") + with self.assertRaisesRegex(sweep.SweepError, "unexpected.txt"): + sweep.require_clean_source_tree(repo, output) + + def test_source_provenance_rejects_head_change(self): + with tempfile.TemporaryDirectory() as tmp: + repo = Path(tmp) + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + subprocess.run(["git", "config", "user.name", "GALAXY Test"], cwd=repo, check=True) + subprocess.run( + ["git", "config", "user.email", "galaxy-test@example.invalid"], + cwd=repo, + check=True, + ) + path = repo / "tracked.txt" + path.write_text("one\n") + subprocess.run(["git", "add", "tracked.txt"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "one"], cwd=repo, check=True) + revision = sweep.git_revision(repo) + + path.write_text("two\n") + subprocess.run(["git", "add", "tracked.txt"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "two"], cwd=repo, check=True) + + with self.assertRaisesRegex(sweep.SweepError, "source revision changed"): + sweep.require_source_provenance(repo, revision) + def test_hardware_receipt_at_oracle_size_is_accepted(self): - summary = sweep.validate_receipt( - receipt_for(4096), - particles=4096, - steps=3, - oracle_limit=4096, - benchmark_repeats=7, - ) + summary = sweep.validate_receipt(receipt_for(4096), **validation_kwargs(4096)) self.assertEqual(summary["particles"], 4096) self.assertEqual(summary["bh2c_parallel_vs_serial_speedup"], 2.0) - self.assertGreater(summary["parallel_median_seconds"], 0.0) + self.assertEqual(summary["parallel_median_seconds"], 0.005) def test_large_hardware_receipt_requires_explicit_oracle_skips(self): - summary = sweep.validate_receipt( - receipt_for(8192), - particles=8192, - steps=3, - oracle_limit=4096, - benchmark_repeats=7, - ) + summary = sweep.validate_receipt(receipt_for(8192), **validation_kwargs(8192)) self.assertEqual(summary["particles"], 8192) self.assertIsNone(summary["bh2c_parallel_vs_serial_speedup"]) + def test_receipt_must_bind_the_full_requested_workload(self): + mutations = { + "preset": "collision", + "dt_myr": 1.0, + "seed": 999, + "theta": 2.0, + "softening_kpc": 100.0, + } + for field, value in mutations.items(): + with self.subTest(field=field): + receipt = receipt_for(512) + receipt[field] = value + with self.assertRaisesRegex(sweep.SweepError, "requested workload"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + receipt = receipt_for(512) + receipt["final_force"]["direct_probe_count"] = 1 + with self.assertRaisesRegex(sweep.SweepError, "direct-probe count"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + receipt = receipt_for(512) + receipt["tree_build_benchmark"]["warmup"] = 9 + with self.assertRaisesRegex(sweep.SweepError, "warmup count"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + receipt = receipt_for(512) + receipt["tree_build_benchmark"]["repeats"] = 6 + with self.assertRaisesRegex(sweep.SweepError, "repeat count"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + def test_adapter_identity_is_mandatory_and_hardware_bound(self): + for gpu in ( + {}, + { + "name": "Synthetic GPU", + "backend": "Vulkan", + "device_type": "DiscreteGpu", + "driver": "", + "driver_info": "", + "software": False, + }, + ): + with self.subTest(gpu=gpu): + receipt = receipt_for(512) + receipt["gpu"] = gpu + with self.assertRaises(sweep.SweepError): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_software_receipt_is_rejected(self): receipt = receipt_for(512) receipt["measurement_class"] = "software-validation" receipt["hardware_performance_claim_allowed"] = False + receipt["gpu"]["software"] = True with self.assertRaisesRegex(sweep.SweepError, "software-validation"): - sweep.validate_receipt( - receipt, - particles=512, - steps=3, - oracle_limit=4096, - benchmark_repeats=7, - ) + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + def test_malformed_typed_receipt_is_rejected_as_sweep_error(self): + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = "corrupt" + with self.assertRaisesRegex(sweep.SweepError, "active_cell_count must be an integer"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) def test_repeat_tree_mismatch_is_rejected(self): receipt = receipt_for(512) receipt["tree"]["repeat_rebuild_matches"] = False with self.assertRaisesRegex(sweep.SweepError, "repeat tree checksum"): - sweep.validate_receipt( - receipt, - particles=512, - steps=3, - oracle_limit=4096, - benchmark_repeats=7, - ) + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + def test_executed_oracle_payloads_are_required(self): + receipt = receipt_for(512) + receipt["gpu_oracles"] = {"status": "executed"} + with self.assertRaisesRegex(sweep.SweepError, "bh2c_serial_gpu"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + receipt = receipt_for(512) + receipt["trajectory_vs_bh2a_flat_f64"] = {"status": "executed"} + with self.assertRaisesRegex(sweep.SweepError, "state_error"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + def test_parallel_median_is_recomputed_from_samples(self): + receipt = receipt_for(512) + receipt["tree_build_benchmark"]["parallel_samples_seconds"] = [100.0] * 7 + receipt["tree_build_benchmark"]["parallel_median_seconds"] = 0.000001 + with self.assertRaisesRegex(sweep.SweepError, "median does not match"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + def test_serial_benchmark_summary_is_recomputed_from_samples(self): + receipt = receipt_for(512) + serial = receipt["tree_build_benchmark"]["bh2c_serial"] + serial["samples_seconds"] = [50.0] * 7 + serial["median_seconds"] = 0.01 + with self.assertRaisesRegex(sweep.SweepError, "BH #2C benchmark median"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + def test_nonfinite_or_nonpositive_benchmark_samples_are_rejected(self): + for bad in (0.0, -1.0, float("inf"), float("nan")): + with self.subTest(bad=bad): + receipt = receipt_for(512) + receipt["tree_build_benchmark"]["parallel_samples_seconds"][0] = bad + with self.assertRaises(sweep.SweepError): + sweep.validate_receipt(receipt, **validation_kwargs(512)) def test_bh2c_benchmark_must_skip_above_its_cap(self): receipt = receipt_for(8192) receipt["tree_build_benchmark"]["bh2c_serial"] = { "status": "executed", - "parallel_vs_serial_speedup": 1.0, + "samples_seconds": [0.01] * 7, + "median_seconds": 0.01, + "parallel_vs_serial_speedup": 2.0, } with self.assertRaisesRegex(sweep.SweepError, "must skip above 4096"): - sweep.validate_receipt( - receipt, - particles=8192, - steps=3, - oracle_limit=4096, - benchmark_repeats=7, - ) + sweep.validate_receipt(receipt, **validation_kwargs(8192)) if __name__ == "__main__": From 88f8b9c0ce472f143bfe2eacf086e954ee1c4273 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:21:19 +0930 Subject: [PATCH 12/82] Tighten BH #2D evidence provenance and receipt binding --- scripts/bench-bh2d-hardware.py | 171 +++++++++++++++++++++++++++++++-- 1 file changed, 164 insertions(+), 7 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 1515010..4c730c7 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -14,7 +14,9 @@ import hashlib import json import math +import os import platform +import re import shlex import subprocess import sys @@ -26,6 +28,18 @@ MAX_PARTICLES = 65_536 BH2C_CAP = 4_096 DEFAULT_PARTICLES = "512,1024,2048,4096,8192,16384,32768,65536" +BENCHMARK_SAMPLE_SCOPE = ( + "complete rebuild call including host-side uniform/bind-group setup, " + "command encoding, submit and synchronization" +) +WORKGROUP_SIZE = 128 +TREE_LEVELS = 17 +GPU_BODY_BYTES = 32 +GPU_ENTRY_BYTES = 16 +GPU_CELL_BYTES = 64 +TREE_META_BYTES = 32 +BOUNDS_RECORD_BYTES = 16 +RADIX_DIGITS = 16 class SweepError(RuntimeError): @@ -73,6 +87,68 @@ def is_within(path: Path, root: Path) -> bool: return False +def path_label(path: Path, repo_root: Path) -> str: + resolved = path.resolve() + try: + return str(resolved.relative_to(repo_root.resolve())) + except ValueError: + home = Path.home().resolve() + try: + return str(Path("$HOME") / resolved.relative_to(home)) + except ValueError: + return str(resolved) + + +def effective_cargo_config_paths(repo_root: Path) -> list[Path]: + candidates: list[Path] = [] + current = repo_root.resolve() + while True: + cargo_dir = current / ".cargo" + candidates.extend((cargo_dir / "config.toml", cargo_dir / "config")) + if current.parent == current: + break + current = current.parent + + cargo_home = Path(os.environ.get("CARGO_HOME", Path.home() / ".cargo")).expanduser().resolve() + candidates.extend((cargo_home / "config.toml", cargo_home / "config")) + + unique: list[Path] = [] + seen: set[Path] = set() + for candidate in candidates: + resolved = candidate.resolve() + if resolved not in seen and resolved.is_file(): + seen.add(resolved) + unique.append(resolved) + return unique + + +def cargo_config_context(repo_root: Path) -> list[dict[str, str]]: + context: list[dict[str, str]] = [] + root = repo_root.resolve() + for path in effective_cargo_config_paths(repo_root): + if is_within(path, root): + relative = path.relative_to(root) + tracked = subprocess.run( + ["git", "ls-files", "--error-unmatch", "--", str(relative)], + cwd=repo_root, + check=False, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + if tracked.returncode != 0: + raise SweepError( + "untracked or ignored Cargo configuration affects the evidence build: " + f"{relative}" + ) + context.append( + { + "path": path_label(path, repo_root), + "sha256": sha256_file(path), + } + ) + return context + + def require_clean_source_tree( repo_root: Path, allowed_untracked_root: Path | None = None, @@ -111,6 +187,7 @@ def require_source_provenance( repo_root: Path, revision: str, allowed_untracked_root: Path | None = None, + expected_cargo_context: list[dict[str, str]] | None = None, ) -> None: current = git_revision(repo_root) if current != revision: @@ -118,6 +195,9 @@ def require_source_provenance( f"source revision changed during hardware evidence capture: expected {revision}, got {current}" ) require_clean_source_tree(repo_root, allowed_untracked_root) + current_cargo_context = cargo_config_context(repo_root) + if expected_cargo_context is not None and current_cargo_context != expected_cargo_context: + raise SweepError("effective Cargo configuration changed during hardware evidence capture") def sha256_file(path: Path) -> str: @@ -161,7 +241,10 @@ def require_number( isinstance(value, (int, float)) and not isinstance(value, bool), f"{name} must be numeric", ) - numeric = float(value) + try: + numeric = float(value) + except (OverflowError, TypeError, ValueError) as exc: + raise SweepError(f"{name} cannot be represented as a finite number") from exc require(math.isfinite(numeric), f"{name} must be finite") if positive: require(numeric > 0.0, f"{name} must be positive") @@ -177,6 +260,34 @@ def require_string(value: Any, name: str, *, nonempty: bool = False) -> str: return value +def require_checksum(value: Any, name: str) -> str: + checksum = require_string(value, name, nonempty=True) + require( + re.fullmatch(r"[0-9a-f]{16}", checksum) is not None, + f"{name} must be a 16-digit lowercase hexadecimal checksum", + ) + return checksum + + +def expected_parallel_tree_buffer_bytes(particles: int) -> int: + blocks = (particles + WORKGROUP_SIZE - 1) // WORKGROUP_SIZE + body_bytes = particles * GPU_BODY_BYTES + entry_bytes = particles * GPU_ENTRY_BYTES + cell_bytes = particles * TREE_LEVELS * GPU_CELL_BYTES + bounds_bytes = blocks * BOUNDS_RECORD_BYTES + histogram_bytes = blocks * RADIX_DIGITS * 4 + offsets_bytes = histogram_bytes + return ( + body_bytes + + entry_bytes * 2 + + cell_bytes + + TREE_META_BYTES + + bounds_bytes * 2 + + histogram_bytes + + offsets_bytes + ) + + def require_same_number(actual: Any, expected: float, name: str) -> float: numeric = require_number(actual, name) require(numeric == float(expected), f"{name} does not match requested workload") @@ -322,6 +433,22 @@ def validate_receipt( tree = require_object(root.get("tree"), "receipt.tree") require(tree.get("repeat_rebuild_matches") is True, "same-state repeat tree checksum changed") + require_checksum( + tree.get("initial_checksum_fnv_mix64"), + "receipt.tree.initial_checksum_fnv_mix64", + ) + final_checksum = require_checksum( + tree.get("final_checksum_fnv_mix64"), + "receipt.tree.final_checksum_fnv_mix64", + ) + repeat_checksum = require_checksum( + tree.get("repeat_checksum_fnv_mix64"), + "receipt.tree.repeat_checksum_fnv_mix64", + ) + require( + final_checksum == repeat_checksum, + "receipt repeat tree checksum does not match the final tree checksum", + ) active_cell_count = require_int( tree.get("active_cell_count"), "receipt.tree.active_cell_count", @@ -417,8 +544,9 @@ def validate_receipt( benchmark.get("sample_scope"), "receipt.tree_build_benchmark.sample_scope", nonempty=True, - ).startswith("complete rebuild call"), - "benchmark sample scope does not cover the complete rebuild call", + ) + == BENCHMARK_SAMPLE_SCOPE, + "benchmark sample scope does not match the frozen complete-rebuild declaration", ) require( benchmark.get("stage_timings_are_diagnostics") is True, @@ -502,6 +630,11 @@ def validate_receipt( "receipt.parallel_tree_buffer_bytes", minimum=1, ) + expected_buffer_bytes = expected_parallel_tree_buffer_bytes(particles) + require( + parallel_tree_buffer_bytes == expected_buffer_bytes, + "receipt.parallel_tree_buffer_bytes does not match the frozen BH #2D allocation formula", + ) return { "particles": particles, @@ -608,7 +741,13 @@ def main() -> int: raise SweepError(f"output directory already exists: {output}") revision = git_revision(repo_root) - require_source_provenance(repo_root, revision) + require_clean_source_tree(repo_root) + build_cargo_context = cargo_config_context(repo_root) + require_source_provenance( + repo_root, + revision, + expected_cargo_context=build_cargo_context, + ) output.mkdir(parents=True) manifest_path = output / "manifest.json" @@ -620,6 +759,9 @@ def main() -> int: "this manifest does not by itself promote BH #2D to production" ), "source_revision": revision, + "build_context": { + "cargo_configuration": build_cargo_context, + }, "host": { "platform": platform.platform(), "python": sys.version.split()[0], @@ -645,7 +787,12 @@ def main() -> int: adapter: str | None = None for particles in particles_list: - require_source_provenance(repo_root, revision, output) + require_source_provenance( + repo_root, + revision, + output, + expected_cargo_context=build_cargo_context, + ) run_dir = output / f"n{particles:06d}" run_dir.mkdir() @@ -663,7 +810,12 @@ def main() -> int: ) log_path.write_text(completed.stdout) - require_source_provenance(repo_root, revision, output) + require_source_provenance( + repo_root, + revision, + output, + expected_cargo_context=build_cargo_context, + ) if completed.returncode != 0: raise SweepError( f"BH #2D hardware run failed at {particles} particles; see {log_path}" @@ -704,7 +856,12 @@ def main() -> int: manifest["adapter_identity"] = adapter manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") - require_source_provenance(repo_root, revision, output) + require_source_provenance( + repo_root, + revision, + output, + expected_cargo_context=build_cargo_context, + ) manifest["status"] = "complete" manifest["completed_run_count"] = len(manifest["runs"]) manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") From a8d444cf321104562c88b10ba4c3707d8ff1da77 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:21:49 +0930 Subject: [PATCH 13/82] Cover latest BH #2D evidence integrity findings --- tests/test_bh2d_hardware_sweep.py | 105 ++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 5 deletions(-) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index a6aaf9a..bd3a338 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -106,8 +106,11 @@ def receipt_for( "host_particle_readbacks_during_steps": 0, "force_solves": steps + 1, "evolution_tree_builds": steps + 1, - "parallel_tree_buffer_bytes": particles * 1024, + "parallel_tree_buffer_bytes": sweep.expected_parallel_tree_buffer_bytes(particles), "tree": { + "initial_checksum_fnv_mix64": "1111111111111111", + "final_checksum_fnv_mix64": "2222222222222222", + "repeat_checksum_fnv_mix64": "2222222222222222", "repeat_rebuild_matches": True, "active_cell_count": 17, "leaf_count": 8, @@ -124,10 +127,7 @@ def receipt_for( "trajectory_vs_bh2a_flat_f64": trajectory, "gpu_oracles": oracles, "tree_build_benchmark": { - "sample_scope": ( - "complete rebuild call including host-side uniform/bind-group setup, " - "command encoding, submit and synchronization" - ), + "sample_scope": sweep.BENCHMARK_SAMPLE_SCOPE, "stage_timings_are_diagnostics": True, "warmup": warmup, "repeats": repeats, @@ -185,6 +185,30 @@ def test_cleanliness_rejects_untracked_cargo_config(self): with self.assertRaisesRegex(sweep.SweepError, "untracked files"): sweep.require_clean_source_tree(repo) + def test_ignored_cargo_config_is_rejected_even_when_git_hides_it(self): + with tempfile.TemporaryDirectory() as tmp: + repo = Path(tmp) + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + subprocess.run(["git", "config", "user.name", "GALAXY Test"], cwd=repo, check=True) + subprocess.run( + ["git", "config", "user.email", "galaxy-test@example.invalid"], + cwd=repo, + check=True, + ) + (repo / "tracked.txt").write_text("tracked\n") + subprocess.run(["git", "add", "tracked.txt"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "fixture"], cwd=repo, check=True) + + info_exclude = repo / ".git" / "info" / "exclude" + info_exclude.write_text(".cargo/\n") + cargo = repo / ".cargo" + cargo.mkdir() + (cargo / "config.toml").write_text('[build]\nrustflags = ["-C", "target-cpu=native"]\n') + + sweep.require_clean_source_tree(repo) + with self.assertRaisesRegex(sweep.SweepError, "Cargo configuration"): + sweep.cargo_config_context(repo) + def test_cleanliness_allows_only_the_evidence_output(self): with tempfile.TemporaryDirectory() as tmp: repo = Path(tmp) @@ -231,6 +255,33 @@ def test_source_provenance_rejects_head_change(self): with self.assertRaisesRegex(sweep.SweepError, "source revision changed"): sweep.require_source_provenance(repo, revision) + def test_cargo_config_context_detects_mid_sweep_change(self): + with tempfile.TemporaryDirectory() as tmp: + repo = Path(tmp) + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + subprocess.run(["git", "config", "user.name", "GALAXY Test"], cwd=repo, check=True) + subprocess.run( + ["git", "config", "user.email", "galaxy-test@example.invalid"], + cwd=repo, + check=True, + ) + cargo = repo / ".cargo" + cargo.mkdir() + config = cargo / "config.toml" + config.write_text("[build]\nincremental = false\n") + subprocess.run(["git", "add", ".cargo/config.toml"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "fixture"], cwd=repo, check=True) + revision = sweep.git_revision(repo) + context = sweep.cargo_config_context(repo) + + config.write_text("[build]\nincremental = true\n") + with self.assertRaises(sweep.SweepError): + sweep.require_source_provenance( + repo, + revision, + expected_cargo_context=context, + ) + def test_hardware_receipt_at_oracle_size_is_accepted(self): summary = sweep.validate_receipt(receipt_for(4096), **validation_kwargs(4096)) self.assertEqual(summary["particles"], 4096) @@ -310,6 +361,22 @@ def test_repeat_tree_mismatch_is_rejected(self): with self.assertRaisesRegex(sweep.SweepError, "repeat tree checksum"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_repeat_tree_checksum_fields_are_required_and_compared(self): + receipt = receipt_for(512) + del receipt["tree"]["final_checksum_fnv_mix64"] + with self.assertRaisesRegex(sweep.SweepError, "final_checksum_fnv_mix64"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + receipt = receipt_for(512) + receipt["tree"]["repeat_checksum_fnv_mix64"] = "3333333333333333" + with self.assertRaisesRegex(sweep.SweepError, "does not match"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + receipt = receipt_for(512) + receipt["tree"]["repeat_checksum_fnv_mix64"] = "NOT-A-CHECKSUM" + with self.assertRaisesRegex(sweep.SweepError, "16-digit lowercase hexadecimal"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_executed_oracle_payloads_are_required(self): receipt = receipt_for(512) receipt["gpu_oracles"] = {"status": "executed"} @@ -321,6 +388,34 @@ def test_executed_oracle_payloads_are_required(self): with self.assertRaisesRegex(sweep.SweepError, "state_error"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_numeric_overflow_is_normalized_to_sweep_error(self): + receipt = receipt_for(512) + receipt["dt_myr"] = 10 ** 400 + with self.assertRaisesRegex(sweep.SweepError, "finite number"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + def test_tree_buffer_size_is_bound_to_particle_count(self): + for particles in (2, 127, 128, 129, 4096, 65536): + with self.subTest(particles=particles): + expected = 1152 * particles + 160 * ((particles + 127) // 128) + 32 + self.assertEqual( + sweep.expected_parallel_tree_buffer_bytes(particles), + expected, + ) + + receipt = receipt_for(512) + receipt["parallel_tree_buffer_bytes"] = 1 + with self.assertRaisesRegex(sweep.SweepError, "allocation formula"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + def test_benchmark_scope_must_match_frozen_declaration(self): + receipt = receipt_for(512) + receipt["tree_build_benchmark"]["sample_scope"] = ( + "complete rebuild call excluding queue submit and GPU synchronization" + ) + with self.assertRaisesRegex(sweep.SweepError, "frozen complete-rebuild declaration"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_parallel_median_is_recomputed_from_samples(self): receipt = receipt_for(512) receipt["tree_build_benchmark"]["parallel_samples_seconds"] = [100.0] * 7 From 89a898d51917f9251d45d2a9d20ec03b30e70cfc Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:34:12 +0930 Subject: [PATCH 14/82] Harden BH #2D toolchain and topology provenance --- scripts/bench-bh2d-hardware.py | 139 ++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 4c730c7..321646f 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -18,6 +18,7 @@ import platform import re import shlex +import shutil import subprocess import sys from pathlib import Path @@ -40,6 +41,32 @@ TREE_META_BYTES = 32 BOUNDS_RECORD_BYTES = 16 RADIX_DIGITS = 16 +BUILD_ENV_EXACT = { + "RUSTFLAGS", + "CARGO_ENCODED_RUSTFLAGS", + "CARGO_BUILD_RUSTFLAGS", + "RUSTC", + "RUSTC_WRAPPER", + "RUSTC_WORKSPACE_WRAPPER", + "RUSTC_BOOTSTRAP", + "RUSTUP_TOOLCHAIN", + "CARGO_BUILD_TARGET", + "CARGO_INCREMENTAL", + "CC", + "CXX", + "AR", + "RANLIB", + "CFLAGS", + "CXXFLAGS", + "CPPFLAGS", + "LDFLAGS", +} +BUILD_ENV_PATTERNS = ( + re.compile(r"^CARGO_TARGET_.+_(?:RUSTFLAGS|LINKER)$"), + re.compile( + r"^CARGO_PROFILE_.+_(?:CODEGEN_UNITS|DEBUG|INCREMENTAL|LTO|OPT_LEVEL|PANIC|RPATH|STRIP)$" + ), +) class SweepError(RuntimeError): @@ -122,6 +149,57 @@ def effective_cargo_config_paths(repo_root: Path) -> list[Path]: return unique +def build_environment_overrides() -> list[str]: + names: list[str] = [] + for name, value in os.environ.items(): + if not value: + continue + if name in BUILD_ENV_EXACT or any(pattern.fullmatch(name) for pattern in BUILD_ENV_PATTERNS): + names.append(name) + return sorted(names) + + +def require_no_build_environment_overrides() -> None: + overrides = build_environment_overrides() + if overrides: + raise SweepError( + "build-affecting environment overrides are not allowed for hardware evidence capture: " + + ", ".join(overrides) + ) + + +def resolve_executable(command: str, name: str) -> Path: + resolved = shutil.which(command) + if resolved is None: + raise SweepError(f"could not resolve {name} executable: {command}") + path = Path(resolved).resolve() + if not path.is_file(): + raise SweepError(f"resolved {name} executable is not a file: {path}") + return path + + +def toolchain_context(cargo_command: str, repo_root: Path) -> dict[str, Any]: + require_no_build_environment_overrides() + cargo_path = resolve_executable(cargo_command, "Cargo") + rustc_path = resolve_executable("rustc", "rustc") + return { + "cargo": { + "requested": cargo_command, + "path": path_label(cargo_path, repo_root), + "sha256": sha256_file(cargo_path), + "version_verbose": run_checked( + [str(cargo_path), "--version", "--verbose"], + repo_root, + ).strip(), + }, + "rustc": { + "path": path_label(rustc_path, repo_root), + "sha256": sha256_file(rustc_path), + "version_verbose": run_checked([str(rustc_path), "-vV"], repo_root).strip(), + }, + } + + def cargo_config_context(repo_root: Path) -> list[dict[str, str]]: context: list[dict[str, str]] = [] root = repo_root.resolve() @@ -153,6 +231,23 @@ def require_clean_source_tree( repo_root: Path, allowed_untracked_root: Path | None = None, ) -> None: + flagged_records = run_checked(["git", "ls-files", "-v", "-z"], repo_root) + flagged: list[str] = [] + for record in (item for item in flagged_records.split("\0") if item): + if len(record) < 3 or record[1] != " ": + raise SweepError("could not parse git index flag evidence") + tag = record[0] + path = record[2:] + if tag != "H": + flagged.append(f"{tag} {path}") + if flagged: + preview = ", ".join(repr(item) for item in flagged[:8]) + suffix = "" if len(flagged) <= 8 else f", ... (+{len(flagged) - 8} more)" + raise SweepError( + "tracked files use index flags or states that can hide worktree changes; " + f"clear assume-unchanged/skip-worktree and restore a normal index first: {preview}{suffix}" + ) + for command in ( ["git", "diff", "--quiet", "--"], ["git", "diff", "--cached", "--quiet", "--"], @@ -188,6 +283,8 @@ def require_source_provenance( revision: str, allowed_untracked_root: Path | None = None, expected_cargo_context: list[dict[str, str]] | None = None, + expected_toolchain_context: dict[str, Any] | None = None, + cargo_command: str = "cargo", ) -> None: current = git_revision(repo_root) if current != revision: @@ -198,6 +295,24 @@ def require_source_provenance( current_cargo_context = cargo_config_context(repo_root) if expected_cargo_context is not None and current_cargo_context != expected_cargo_context: raise SweepError("effective Cargo configuration changed during hardware evidence capture") + current_toolchain_context = toolchain_context(cargo_command, repo_root) + if ( + expected_toolchain_context is not None + and current_toolchain_context != expected_toolchain_context + ): + raise SweepError("Cargo/Rust toolchain changed during hardware evidence capture") + + +def load_receipt(path: Path) -> Any: + payload = path.read_bytes() + try: + text = payload.decode("utf-8") + except UnicodeDecodeError as exc: + raise SweepError(f"receipt is not valid UTF-8: {path}") from exc + try: + return json.loads(text) + except json.JSONDecodeError as exc: + raise SweepError(f"receipt is not valid JSON: {path}") from exc def sha256_file(path: Path) -> str: @@ -456,6 +571,19 @@ def validate_receipt( ) leaf_count = require_int(tree.get("leaf_count"), "receipt.tree.leaf_count", minimum=1) max_depth = require_int(tree.get("max_depth"), "receipt.tree.max_depth", minimum=0) + cell_capacity = TREE_LEVELS * particles + require( + active_cell_count <= cell_capacity, + "receipt.tree.active_cell_count exceeds the frozen sparse-cell capacity", + ) + require( + leaf_count <= active_cell_count, + "receipt.tree.leaf_count cannot exceed active_cell_count", + ) + require( + max_depth <= TREE_LEVELS - 1, + "receipt.tree.max_depth exceeds the frozen Morton depth", + ) force = require_object(root.get("final_force"), "receipt.final_force") expected_probe_count = min(direct_probes, particles) @@ -743,10 +871,13 @@ def main() -> int: revision = git_revision(repo_root) require_clean_source_tree(repo_root) build_cargo_context = cargo_config_context(repo_root) + build_toolchain_context = toolchain_context(args.cargo, repo_root) require_source_provenance( repo_root, revision, expected_cargo_context=build_cargo_context, + expected_toolchain_context=build_toolchain_context, + cargo_command=args.cargo, ) output.mkdir(parents=True) @@ -761,6 +892,8 @@ def main() -> int: "source_revision": revision, "build_context": { "cargo_configuration": build_cargo_context, + "toolchain": build_toolchain_context, + "environment_overrides": [], }, "host": { "platform": platform.platform(), @@ -792,6 +925,8 @@ def main() -> int: revision, output, expected_cargo_context=build_cargo_context, + expected_toolchain_context=build_toolchain_context, + cargo_command=args.cargo, ) run_dir = output / f"n{particles:06d}" @@ -815,6 +950,8 @@ def main() -> int: revision, output, expected_cargo_context=build_cargo_context, + expected_toolchain_context=build_toolchain_context, + cargo_command=args.cargo, ) if completed.returncode != 0: raise SweepError( @@ -823,7 +960,7 @@ def main() -> int: if not receipt_path.is_file(): raise SweepError(f"missing receipt after {particles}-particle run") - receipt = json.loads(receipt_path.read_text()) + receipt = load_receipt(receipt_path) summary = validate_receipt( receipt, particles=particles, From 1243fc5ab3ec6dd619409c9eb3c34c764dd381ff Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:34:54 +0930 Subject: [PATCH 15/82] Recheck toolchain before completing BH #2D sweep --- scripts/bench-bh2d-hardware.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 321646f..8422499 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -998,6 +998,8 @@ def main() -> int: revision, output, expected_cargo_context=build_cargo_context, + expected_toolchain_context=build_toolchain_context, + cargo_command=args.cargo, ) manifest["status"] = "complete" manifest["completed_run_count"] = len(manifest["runs"]) From d00d223e154c64bb2f2dba7cf404b804c890dd18 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:35:43 +0930 Subject: [PATCH 16/82] Cover toolchain, index and topology evidence regressions --- tests/test_bh2d_hardware_sweep.py | 98 +++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index bd3a338..77a0f4a 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 import importlib.util +import os import subprocess import tempfile import unittest from pathlib import Path +from unittest import mock ROOT = Path(__file__).resolve().parents[1] MODULE_PATH = ROOT / "scripts" / "bench-bh2d-hardware.py" @@ -209,6 +211,35 @@ def test_ignored_cargo_config_is_rejected_even_when_git_hides_it(self): with self.assertRaisesRegex(sweep.SweepError, "Cargo configuration"): sweep.cargo_config_context(repo) + def test_cleanliness_rejects_assume_unchanged_and_skip_worktree(self): + for flag in ("--assume-unchanged", "--skip-worktree"): + with self.subTest(flag=flag), tempfile.TemporaryDirectory() as tmp: + repo = Path(tmp) + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + subprocess.run(["git", "config", "user.name", "GALAXY Test"], cwd=repo, check=True) + subprocess.run( + ["git", "config", "user.email", "galaxy-test@example.invalid"], + cwd=repo, + check=True, + ) + path = repo / "tracked.txt" + path.write_text("one\n") + subprocess.run(["git", "add", "tracked.txt"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "fixture"], cwd=repo, check=True) + subprocess.run(["git", "update-index", flag, "tracked.txt"], cwd=repo, check=True) + path.write_text("two\n") + + self.assertEqual( + subprocess.run( + ["git", "diff", "--quiet", "--"], + cwd=repo, + check=False, + ).returncode, + 0, + ) + with self.assertRaisesRegex(sweep.SweepError, "index flags"): + sweep.require_clean_source_tree(repo) + def test_cleanliness_allows_only_the_evidence_output(self): with tempfile.TemporaryDirectory() as tmp: repo = Path(tmp) @@ -282,6 +313,56 @@ def test_cargo_config_context_detects_mid_sweep_change(self): expected_cargo_context=context, ) + def test_build_environment_overrides_are_rejected(self): + for name in ( + "RUSTFLAGS", + "CARGO_BUILD_RUSTFLAGS", + "RUSTC", + "RUSTC_WRAPPER", + "RUSTUP_TOOLCHAIN", + "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS", + "CARGO_PROFILE_RELEASE_LTO", + ): + with self.subTest(name=name), mock.patch.dict( + os.environ, + {name: "evidence-changing-value"}, + clear=False, + ): + with self.assertRaisesRegex(sweep.SweepError, name): + sweep.require_no_build_environment_overrides() + + def test_toolchain_context_records_resolved_binaries_and_versions(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + bin_dir = root / "bin" + bin_dir.mkdir() + cargo = bin_dir / "cargo-custom" + rustc = bin_dir / "rustc" + cargo.write_text("#!/bin/sh\necho 'cargo 9.9.9 (fixture)'\n") + rustc.write_text("#!/bin/sh\necho 'rustc 9.9.9 (fixture)'\n") + cargo.chmod(0o755) + rustc.chmod(0o755) + + with mock.patch.dict( + os.environ, + {"PATH": str(bin_dir)}, + clear=True, + ): + context = sweep.toolchain_context("cargo-custom", root) + + self.assertEqual(context["cargo"]["requested"], "cargo-custom") + self.assertEqual(context["cargo"]["version_verbose"], "cargo 9.9.9 (fixture)") + self.assertEqual(context["rustc"]["version_verbose"], "rustc 9.9.9 (fixture)") + self.assertEqual(len(context["cargo"]["sha256"]), 64) + self.assertEqual(len(context["rustc"]["sha256"]), 64) + + def test_invalid_utf8_receipt_is_normalized_to_sweep_error(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "receipt.json" + path.write_bytes(b"{\xff}") + with self.assertRaisesRegex(sweep.SweepError, "not valid UTF-8"): + sweep.load_receipt(path) + def test_hardware_receipt_at_oracle_size_is_accepted(self): summary = sweep.validate_receipt(receipt_for(4096), **validation_kwargs(4096)) self.assertEqual(summary["particles"], 4096) @@ -377,6 +458,23 @@ def test_repeat_tree_checksum_fields_are_required_and_compared(self): with self.assertRaisesRegex(sweep.SweepError, "16-digit lowercase hexadecimal"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = sweep.TREE_LEVELS * 512 + 1 + with self.assertRaisesRegex(sweep.SweepError, "sparse-cell capacity"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = 17 + receipt["tree"]["leaf_count"] = 18 + with self.assertRaisesRegex(sweep.SweepError, "cannot exceed active_cell_count"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + receipt = receipt_for(512) + receipt["tree"]["max_depth"] = 17 + with self.assertRaisesRegex(sweep.SweepError, "frozen Morton depth"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_executed_oracle_payloads_are_required(self): receipt = receipt_for(512) receipt["gpu_oracles"] = {"status": "executed"} From b317f18bc2428964d0c0b3d2c42ae2ed3491f0a7 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:52:20 +0930 Subject: [PATCH 17/82] Isolate BH #2D builds and reject Cargo execution redirects --- scripts/bench-bh2d-hardware.py | 94 +++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 8422499..cad6edd 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -21,6 +21,7 @@ import shutil import subprocess import sys +import tomllib from pathlib import Path from typing import Any @@ -62,7 +63,7 @@ "LDFLAGS", } BUILD_ENV_PATTERNS = ( - re.compile(r"^CARGO_TARGET_.+_(?:RUSTFLAGS|LINKER)$"), + re.compile(r"^CARGO_TARGET_.+_(?:RUSTFLAGS|LINKER|RUNNER)$"), re.compile( r"^CARGO_PROFILE_.+_(?:CODEGEN_UNITS|DEBUG|INCREMENTAL|LTO|OPT_LEVEL|PANIC|RPATH|STRIP)$" ), @@ -200,6 +201,60 @@ def toolchain_context(cargo_command: str, repo_root: Path) -> dict[str, Any]: } +def reject_cargo_config_redirects(document: Any, path: Path) -> None: + root = require_object(document, f"Cargo config {path}") + + forbidden_top_level = { + "paths": "dependency path overrides", + "source": "source replacement", + "registries": "registry replacement", + "patch": "dependency patching", + } + for key, description in forbidden_top_level.items(): + if key in root: + raise SweepError( + f"Cargo config {path} contains unsupported {description} via [{key}]" + ) + + env = root.get("env") + if env is not None: + require_object(env, f"Cargo config {path}.env") + raise SweepError( + f"Cargo config {path} contains [env] overrides; build environment injection is not allowed" + ) + + build = root.get("build") + if build is not None: + build = require_object(build, f"Cargo config {path}.build") + forbidden_build = ( + "rustc", + "rustc-wrapper", + "rustc-workspace-wrapper", + "rustflags", + "rustdocflags", + ) + for key in forbidden_build: + if key in build: + raise SweepError( + f"Cargo config {path} contains unsupported build.{key} redirect/override" + ) + + target = root.get("target") + if target is not None: + target = require_object(target, f"Cargo config {path}.target") + for target_name, target_config in target.items(): + target_config = require_object( + target_config, + f"Cargo config {path}.target.{target_name}", + ) + for key in ("runner", "linker", "rustflags", "rustdocflags"): + if key in target_config: + raise SweepError( + f"Cargo config {path} contains unsupported target.{target_name}.{key} " + "redirect/override" + ) + + def cargo_config_context(repo_root: Path) -> list[dict[str, str]]: context: list[dict[str, str]] = [] root = repo_root.resolve() @@ -218,10 +273,18 @@ def cargo_config_context(repo_root: Path) -> list[dict[str, str]]: "untracked or ignored Cargo configuration affects the evidence build: " f"{relative}" ) + payload = path.read_bytes() + try: + document = tomllib.loads(payload.decode("utf-8")) + except UnicodeDecodeError as exc: + raise SweepError(f"Cargo config is not valid UTF-8: {path}") from exc + except tomllib.TOMLDecodeError as exc: + raise SweepError(f"Cargo config is not valid TOML: {path}: {exc}") from exc + reject_cargo_config_redirects(document, path) context.append( { "path": path_label(path, repo_root), - "sha256": sha256_file(path), + "sha256": hashlib.sha256(payload).hexdigest(), } ) return context @@ -580,6 +643,10 @@ def validate_receipt( leaf_count <= active_cell_count, "receipt.tree.leaf_count cannot exceed active_cell_count", ) + require( + leaf_count <= particles, + "receipt.tree.leaf_count cannot exceed the resident particle count", + ) require( max_depth <= TREE_LEVELS - 1, "receipt.tree.max_depth exceeds the frozen Morton depth", @@ -778,13 +845,20 @@ def validate_receipt( } -def command_for(args: argparse.Namespace, particles: int, receipt: Path) -> list[str]: +def command_for( + args: argparse.Namespace, + particles: int, + receipt: Path, + target_dir: Path, +) -> list[str]: command = [ args.cargo, "run", "--release", "--manifest-path", "runtime/Cargo.toml", + "--target-dir", + str(target_dir), "--locked", "--bin", "galaxy-bh-gpu-tree-parallel", @@ -860,8 +934,10 @@ def main() -> int: if args.dry_run: for particles in particles_list: - receipt = args.output / f"n{particles:06d}" / "receipt.json" - print(shlex.join(command_for(args, particles, receipt))) + run_dir = args.output / f"n{particles:06d}" + receipt = run_dir / "receipt.json" + target_dir = run_dir / "cargo-target" + print(shlex.join(command_for(args, particles, receipt, target_dir))) return 0 output = args.output.expanduser().resolve() @@ -933,7 +1009,12 @@ def main() -> int: run_dir.mkdir() receipt_path = run_dir / "receipt.json" log_path = run_dir / "run.log" - command = command_for(args, particles, receipt_path) + target_dir = run_dir / "cargo-target" + if target_dir.exists(): + raise SweepError( + f"fresh Cargo target directory unexpectedly exists before build: {target_dir}" + ) + command = command_for(args, particles, receipt_path, target_dir) completed = subprocess.run( command, @@ -986,6 +1067,7 @@ def main() -> int: "receipt_sha256": sha256_file(receipt_path), "log": str(log_path.relative_to(output)), "log_sha256": sha256_file(log_path), + "cargo_target_dir": str(target_dir.relative_to(output)), } ) summary.pop("adapter_identity") From e9f014597773b796458fe98c2d8241bdc8cf15d9 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:52:58 +0930 Subject: [PATCH 18/82] Cover fresh build, Cargo redirect and leaf-bound regressions --- tests/test_bh2d_hardware_sweep.py | 74 +++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 77a0f4a..93d4316 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -321,6 +321,7 @@ def test_build_environment_overrides_are_rejected(self): "RUSTC_WRAPPER", "RUSTUP_TOOLCHAIN", "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS", + "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER", "CARGO_PROFILE_RELEASE_LTO", ): with self.subTest(name=name), mock.patch.dict( @@ -331,6 +332,50 @@ def test_build_environment_overrides_are_rejected(self): with self.assertRaisesRegex(sweep.SweepError, name): sweep.require_no_build_environment_overrides() + def test_cargo_config_execution_and_source_redirects_are_rejected(self): + cases = { + "rustc-wrapper": '[build]\nrustc-wrapper = "/tmp/wrapper"\n', + "target-linker": ( + '[target.x86_64-unknown-linux-gnu]\n' + 'linker = "/tmp/linker"\n' + ), + "target-runner": ( + '[target.x86_64-unknown-linux-gnu]\n' + 'runner = "/tmp/runner"\n' + ), + "target-rustflags": ( + '[target.x86_64-unknown-linux-gnu]\n' + 'rustflags = ["-C", "linker=/tmp/linker"]\n' + ), + "source": ( + '[source.crates-io]\n' + 'replace-with = "vendored"\n' + '[source.vendored]\n' + 'directory = "/tmp/vendor"\n' + ), + "paths": 'paths = ["/tmp/override"]\n', + "env": '[env]\nRUSTFLAGS = "-C target-cpu=native"\n', + } + for name, payload in cases.items(): + with self.subTest(name=name), tempfile.TemporaryDirectory() as tmp: + repo = Path(tmp) + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + subprocess.run(["git", "config", "user.name", "GALAXY Test"], cwd=repo, check=True) + subprocess.run( + ["git", "config", "user.email", "galaxy-test@example.invalid"], + cwd=repo, + check=True, + ) + cargo = repo / ".cargo" + cargo.mkdir() + config = cargo / "config.toml" + config.write_text(payload) + subprocess.run(["git", "add", ".cargo/config.toml"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "fixture"], cwd=repo, check=True) + + with self.assertRaisesRegex(sweep.SweepError, "unsupported|not allowed"): + sweep.cargo_config_context(repo) + def test_toolchain_context_records_resolved_binaries_and_versions(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) @@ -363,6 +408,29 @@ def test_invalid_utf8_receipt_is_normalized_to_sweep_error(self): with self.assertRaisesRegex(sweep.SweepError, "not valid UTF-8"): sweep.load_receipt(path) + def test_verifier_command_uses_fresh_explicit_target_directory(self): + args = __import__("argparse").Namespace( + cargo="cargo", + preset="disc", + steps=3, + dt_myr=0.01, + seed=303, + theta=0.5, + softening_kpc=0.05, + direct_probes=12, + oracle_limit=4096, + benchmark_warmup=2, + benchmark_repeats=7, + adapter=None, + ) + receipt = Path("/evidence/n000512/receipt.json") + target_dir = Path("/evidence/n000512/cargo-target") + command = sweep.command_for(args, 512, receipt, target_dir) + self.assertIn("--target-dir", command) + index = command.index("--target-dir") + self.assertEqual(command[index + 1], str(target_dir)) + self.assertNotIn("runtime/target", " ".join(command)) + def test_hardware_receipt_at_oracle_size_is_accepted(self): summary = sweep.validate_receipt(receipt_for(4096), **validation_kwargs(4096)) self.assertEqual(summary["particles"], 4096) @@ -470,6 +538,12 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): with self.assertRaisesRegex(sweep.SweepError, "cannot exceed active_cell_count"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = 600 + receipt["tree"]["leaf_count"] = 600 + with self.assertRaisesRegex(sweep.SweepError, "resident particle count"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) receipt["tree"]["max_depth"] = 17 with self.assertRaisesRegex(sweep.SweepError, "frozen Morton depth"): From a4c547268c562369dee419a6b5e9ab1ea2769623 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:24:09 +0930 Subject: [PATCH 19/82] Harden hardware sweep launch, toolchain provenance and failure records --- .github/workflows/native-gpu.yml | 2 +- docs/GPU-RUNTIME.md | 2 +- scripts/bench-bh2d-hardware.py | 151 +++++++++++++++++++++--------- tests/test_bh2d_hardware_sweep.py | 115 +++++++++++++++++++++++ 4 files changed, 224 insertions(+), 46 deletions(-) diff --git a/.github/workflows/native-gpu.yml b/.github/workflows/native-gpu.yml index 95a0be4..cc37d90 100644 --- a/.github/workflows/native-gpu.yml +++ b/.github/workflows/native-gpu.yml @@ -61,7 +61,7 @@ jobs: python runtime/tests/test_runner.py python runtime/cuda/check_barnes_hut_layout.py python tests/test_bh2d_hardware_sweep.py - python scripts/bench-bh2d-hardware.py --dry-run --output "$RUNNER_TEMP/bh2d-plan" --particles 512,4096,8192 + python -I scripts/bench-bh2d-hardware.py --dry-run --output "$RUNNER_TEMP/bh2d-plan" --particles 512,4096,8192 - name: Install software Vulkan for compute validation run: | sudo apt-get update -qq diff --git a/docs/GPU-RUNTIME.md b/docs/GPU-RUNTIME.md index 4eb199d..14e2bf0 100644 --- a/docs/GPU-RUNTIME.md +++ b/docs/GPU-RUNTIME.md @@ -278,7 +278,7 @@ Tree-build stage families are batched into command buffers before synchronizatio For repeatable real-hardware scaling evidence, use the fail-closed sweep runner: ```bash -python3 scripts/bench-bh2d-hardware.py \ +python3 -I scripts/bench-bh2d-hardware.py \ --output runs/bh2d-hardware-sweep \ --adapter 0 ``` diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index cad6edd..95a3ab8 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env -S python3 -I # SPDX-License-Identifier: Apache-2.0 """Fail-closed BH #2D real-hardware scaling sweep. @@ -10,6 +10,11 @@ from __future__ import annotations +# sys is built in: fail before importing anything from a caller-controlled path. +import sys +if __name__ == "__main__" and not sys.flags.isolated: + raise SystemExit("Hardware capture requires isolated Python: python3 -I scripts/bench-bh2d-hardware.py ...") + import argparse import hashlib import json @@ -20,7 +25,6 @@ import shlex import shutil import subprocess -import sys import tomllib from pathlib import Path from typing import Any @@ -42,7 +46,10 @@ TREE_META_BYTES = 32 BOUNDS_RECORD_BYTES = 16 RADIX_DIGITS = 16 +SYSTEM_PATH = "/usr/bin:/bin" BUILD_ENV_EXACT = { + "LD_PRELOAD", "LD_LIBRARY_PATH", "LIBRARY_PATH", "COMPILER_PATH", + "GCC_EXEC_PREFIX", "CPATH", "C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH", "RUSTFLAGS", "CARGO_ENCODED_RUSTFLAGS", "CARGO_BUILD_RUSTFLAGS", @@ -88,19 +95,35 @@ def parse_particles(raw: str) -> list[int]: return values +def git_invocation(command: list[str], cwd: Path) -> tuple[list[str], dict[str, str]]: + env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")} + env.update(PATH=SYSTEM_PATH, GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL=os.devnull) + git = shutil.which("git", path=SYSTEM_PATH) + if git is None: + raise SweepError("system Git is required") + # rev-parse also handles a linked worktree's .git file. No ambient selectors. + result = subprocess.run([git, "-C", str(cwd), "rev-parse", "--absolute-git-dir"], + env=env, capture_output=True, check=False) + if result.returncode: + raise SweepError("could not locate the checkout Git directory") + git_dir = result.stdout.decode("utf-8").strip() + return [git, "--git-dir", git_dir, "--work-tree", str(cwd.resolve()), + "-c", "core.fsmonitor=false", *command[1:]], env + + def run_checked(command: list[str], cwd: Path) -> str: - completed = subprocess.run( - command, - cwd=cwd, - check=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) + env = None + if command[0] == "git": + command, env = git_invocation(command, cwd) + completed = subprocess.run(command, cwd=cwd, env=env, check=False, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) if completed.returncode != 0: - detail = completed.stderr.strip() or completed.stdout.strip() + detail = (completed.stderr or completed.stdout).decode("utf-8", errors="replace").strip() raise SweepError(f"command failed ({completed.returncode}): {' '.join(command)}\n{detail}") - return completed.stdout + try: + return completed.stdout.decode("utf-8") + except UnicodeDecodeError as exc: + raise SweepError("command output is not valid UTF-8") from exc def git_revision(repo_root: Path) -> str: @@ -137,7 +160,10 @@ def effective_cargo_config_paths(repo_root: Path) -> list[Path]: break current = current.parent - cargo_home = Path(os.environ.get("CARGO_HOME", Path.home() / ".cargo")).expanduser().resolve() + cargo_home = Path(os.environ.get("CARGO_HOME", Path.home() / ".cargo")).expanduser() + if not cargo_home.is_absolute(): + cargo_home = repo_root / cargo_home + cargo_home = cargo_home.resolve() candidates.extend((cargo_home / "config.toml", cargo_home / "config")) unique: list[Path] = [] @@ -173,34 +199,57 @@ def resolve_executable(command: str, name: str) -> Path: resolved = shutil.which(command) if resolved is None: raise SweepError(f"could not resolve {name} executable: {command}") - path = Path(resolved).resolve() + path = Path(os.path.abspath(resolved)) if not path.is_file(): raise SweepError(f"resolved {name} executable is not a file: {path}") return path -def toolchain_context(cargo_command: str, repo_root: Path) -> dict[str, Any]: - require_no_build_environment_overrides() - cargo_path = resolve_executable(cargo_command, "Cargo") - rustc_path = resolve_executable("rustc", "rustc") +def tool_record(command: str, name: str, repo_root: Path) -> dict[str, Any]: + invocation = resolve_executable(command, name) + selected = invocation + # Keep argv[0] semantics of proxies, but identify the actual selected tool. + rustup = shutil.which("rustup") + is_proxy = invocation.resolve().stem == "rustup" or ( + rustup is not None and os.path.samefile(invocation, rustup) + ) + if is_proxy: + rustup_command = rustup if rustup and os.path.samefile(invocation, rustup) else str(invocation.resolve()) + selected = Path(run_checked([rustup_command, "which", name], repo_root).strip()) + require(selected.is_absolute() and selected.is_file(), f"rustup did not resolve {name}") + require(not os.path.samefile(selected, invocation), f"rustup resolved {name} to its proxy") + flags = ["--version", "--verbose"] if name == "cargo" else ["-vV"] return { - "cargo": { - "requested": cargo_command, - "path": path_label(cargo_path, repo_root), - "sha256": sha256_file(cargo_path), - "version_verbose": run_checked( - [str(cargo_path), "--version", "--verbose"], - repo_root, - ).strip(), - }, - "rustc": { - "path": path_label(rustc_path, repo_root), - "sha256": sha256_file(rustc_path), - "version_verbose": run_checked([str(rustc_path), "-vV"], repo_root).strip(), - }, + "path": path_label(selected, repo_root), + "executable": str(selected.absolute()), + "sha256": sha256_file(selected), + "invocation": str(invocation), + "proxy_sha256": sha256_file(invocation) if is_proxy else None, + "version_verbose": run_checked([str(selected), *flags], repo_root).strip(), } +def toolchain_context(cargo_command: str, repo_root: Path) -> dict[str, Any]: + require_no_build_environment_overrides() + cargo = tool_record(cargo_command, "cargo", repo_root) + cargo["requested"] = cargo_command + rustc = tool_record("rustc", "rustc", repo_root) + system_tools = {} + for name in ("cc", "c++", "gcc", "g++", "ld", "as", "ar", "ranlib", "git"): + path = shutil.which(name, path=SYSTEM_PATH) + if path: + system_tools[name] = {"path": str(Path(path).resolve()), "sha256": sha256_file(Path(path))} + require("cc" in system_tools, "system C linker (cc) is required") + return {"cargo": cargo, "rustc": rustc, "build_path": SYSTEM_PATH, "system_tools": system_tools} + + +def build_environment(context: dict[str, Any]) -> dict[str, str]: + env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")} + env["PATH"] = SYSTEM_PATH + env["RUSTC"] = context["rustc"]["executable"] + return env + + def reject_cargo_config_redirects(document: Any, path: Path) -> None: root = require_object(document, f"Cargo config {path}") @@ -261,8 +310,10 @@ def cargo_config_context(repo_root: Path) -> list[dict[str, str]]: for path in effective_cargo_config_paths(repo_root): if is_within(path, root): relative = path.relative_to(root) + git_command, git_env = git_invocation( + ["git", "ls-files", "--error-unmatch", "--", str(relative)], repo_root) tracked = subprocess.run( - ["git", "ls-files", "--error-unmatch", "--", str(relative)], + git_command, env=git_env, cwd=repo_root, check=False, stdout=subprocess.DEVNULL, @@ -312,10 +363,11 @@ def require_clean_source_tree( ) for command in ( - ["git", "diff", "--quiet", "--"], - ["git", "diff", "--cached", "--quiet", "--"], + ["git", "diff", "--no-ext-diff", "--quiet", "--"], + ["git", "diff", "--no-ext-diff", "--cached", "--quiet", "--"], ): - completed = subprocess.run(command, cwd=repo_root, check=False) + git_command, git_env = git_invocation(command, repo_root) + completed = subprocess.run(git_command, env=git_env, cwd=repo_root, check=False) if completed.returncode != 0: raise SweepError( "tracked source tree is dirty; commit or revert changes before hardware evidence capture" @@ -374,7 +426,7 @@ def load_receipt(path: Path) -> Any: raise SweepError(f"receipt is not valid UTF-8: {path}") from exc try: return json.loads(text) - except json.JSONDecodeError as exc: + except ValueError as exc: raise SweepError(f"receipt is not valid JSON: {path}") from exc @@ -932,6 +984,15 @@ def main() -> int: require(1 <= args.benchmark_repeats <= 31, "--benchmark-repeats must be in 1..=31") require(0 <= args.benchmark_warmup <= 10, "--benchmark-warmup must be in 0..=10") + require_number(args.dt_myr, "--dt-myr") + require_number(args.theta, "--theta") + require_number(args.softening_kpc, "--softening-kpc") + require(1e-6 <= args.dt_myr <= 1, "--dt-myr must be in [1e-6, 1]") + require(0 <= args.theta <= 2, "--theta must be in [0, 2]") + require(0 <= args.softening_kpc <= 100, "--softening-kpc must be in [0, 100]") + require(1 <= args.direct_probes <= 64, "--direct-probes must be in 1..=64") + require(0 <= args.seed <= 2**64 - 1, "--seed must fit u64") + if args.dry_run: for particles in particles_list: run_dir = args.output / f"n{particles:06d}" @@ -992,7 +1053,7 @@ def main() -> int: }, "runs": [], } - manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True, allow_nan=False) + "\n") adapter: str | None = None for particles in particles_list: @@ -1016,15 +1077,17 @@ def main() -> int: ) command = command_for(args, particles, receipt_path, target_dir) + command[0] = build_toolchain_context["cargo"]["executable"] completed = subprocess.run( command, + env=build_environment(build_toolchain_context), cwd=repo_root, check=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - text=True, ) - log_path.write_text(completed.stdout) + # Preserve every output byte, including non-UTF-8 driver diagnostics. + log_path.write_bytes(completed.stdout) require_source_provenance( repo_root, @@ -1073,7 +1136,7 @@ def main() -> int: summary.pop("adapter_identity") manifest["runs"].append(summary) manifest["adapter_identity"] = adapter - manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True, allow_nan=False) + "\n") require_source_provenance( repo_root, @@ -1085,15 +1148,15 @@ def main() -> int: ) manifest["status"] = "complete" manifest["completed_run_count"] = len(manifest["runs"]) - manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True, allow_nan=False) + "\n") print(manifest_path) return 0 - except (OSError, json.JSONDecodeError, SweepError) as exc: + except (OSError, ValueError, SweepError) as exc: if manifest_path is not None and manifest is not None and manifest_path.parent.exists(): manifest["status"] = "failed" manifest["error"] = str(exc) try: - manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True, allow_nan=False) + "\n") except OSError: pass print(f"BH #2D hardware sweep: {exc}", file=sys.stderr) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 93d4316..856c74c 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -401,6 +401,121 @@ def test_toolchain_context_records_resolved_binaries_and_versions(self): self.assertEqual(len(context["cargo"]["sha256"]), 64) self.assertEqual(len(context["rustc"]["sha256"]), 64) + def test_cli_requires_isolation_before_optional_imports(self): + import sys + for isolated, expected in ((False, 1), (True, 0)): + result = subprocess.run( + [sys.executable, *(["-I"] if isolated else []), str(MODULE_PATH), + "--output", "/unused", "--particles", "512", "--dry-run"], + capture_output=True, text=True, + ) + self.assertEqual(result.returncode, expected, result.stderr) + if not isolated: + self.assertIn("isolated Python", result.stderr) + + def test_system_build_path_and_explicit_rustc(self): + with mock.patch.dict(os.environ, {"PATH": "/unrecorded/bin", "GIT_WORK_TREE": "/other"}): + env = sweep.build_environment({"rustc": {"executable": "/selected/bin/rustc"}}) + self.assertEqual(env["PATH"], "/usr/bin:/bin") + self.assertEqual(env["RUSTC"], "/selected/bin/rustc") + self.assertNotIn("GIT_WORK_TREE", env) + + def test_git_invocation_binds_checkout_and_clears_selectors(self): + with mock.patch.dict(os.environ, {"GIT_WORK_TREE": "/other", "GIT_DIR": "/other/.git", + "GIT_INDEX_FILE": "/other/index"}): + command, env = sweep.git_invocation(["git", "status"], ROOT) + self.assertIn(str(ROOT), command) + self.assertIn(str(ROOT / ".git"), command) + for name in ("GIT_WORK_TREE", "GIT_DIR", "GIT_INDEX_FILE"): + self.assertNotIn(name, env) + self.assertEqual(sweep.git_revision(ROOT), + subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip()) + + def test_relative_cargo_home_is_relative_to_repo(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + home = root / "cargo-home" + home.mkdir() + config = home / "config.toml" + config.write_text("[build]\nincremental = false\n") + with mock.patch.dict(os.environ, {"CARGO_HOME": "cargo-home"}): + self.assertIn(config, sweep.effective_cargo_config_paths(root)) + + def test_rustup_proxy_records_selected_binary(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + rustup = root / "rustup" + rustup.write_bytes(b"proxy identity") + proxy = root / "cargo" + proxy.symlink_to(rustup) + selected = root / "selected-cargo" + selected.write_bytes(b"selected compiler tool") + with mock.patch.object(sweep, "resolve_executable", return_value=proxy), \ + mock.patch.object(sweep, "run_checked", side_effect=[str(selected), "cargo fixture"]): + record = sweep.tool_record("cargo", "cargo", root) + self.assertEqual(record["executable"], str(selected)) + self.assertEqual(record["sha256"], sweep.sha256_file(selected)) + self.assertEqual(record["proxy_sha256"], sweep.sha256_file(rustup)) + self.assertNotEqual(record["sha256"], record["proxy_sha256"]) + + def test_hardlinked_rustup_proxy_uses_rustup_command_name(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + rustup = root / "rustup" + rustup.write_bytes(b"proxy") + proxy = root / "cargo" + os.link(rustup, proxy) + selected = root / "toolchain-cargo" + selected.write_bytes(b"cargo") + with mock.patch.object(sweep, "resolve_executable", return_value=proxy), \ + mock.patch.object(sweep.shutil, "which", return_value=str(rustup)), \ + mock.patch.object(sweep, "run_checked", side_effect=[str(selected), "cargo fixture"]) as run: + record = sweep.tool_record("cargo", "cargo", root) + self.assertEqual(run.call_args_list[0].args[0], [str(rustup), "which", "cargo"]) + self.assertEqual(record["sha256"], sweep.sha256_file(selected)) + + def test_invalid_workloads_do_not_create_output(self): + import sys + for flag, value in (("--dt-myr", "nan"), ("--theta", "inf"), + ("--softening-kpc", "-1"), ("--direct-probes", "65"), + ("--seed", str(2**64))): + with self.subTest(flag=flag), tempfile.TemporaryDirectory() as tmp: + output = Path(tmp) / "evidence" + result = subprocess.run([sys.executable, "-I", str(MODULE_PATH), + "--output", str(output), flag, value], capture_output=True) + self.assertEqual(result.returncode, 1) + self.assertFalse(output.exists()) + + def test_integer_parse_limit_is_normalized(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "receipt.json" + path.write_text('{"value":' + '1' * 5000 + '}') + with self.assertRaisesRegex(sweep.SweepError, "not valid JSON"): + sweep.load_receipt(path) + + def test_non_utf8_run_log_preserved_and_manifest_failed(self): + import argparse + import json + with tempfile.TemporaryDirectory() as tmp: + output = Path(tmp) / "evidence" + args = argparse.Namespace(output=output, particles="512", preset="disc", steps=3, + dt_myr=0.01, seed=303, theta=0.5, softening_kpc=0.05, + direct_probes=12, oracle_limit=4096, benchmark_warmup=2, + benchmark_repeats=7, adapter=None, cargo="cargo", dry_run=False) + context = {"cargo": {"executable": "/selected/cargo"}, + "rustc": {"executable": "/selected/rustc"}} + with mock.patch.object(sweep, "parse_args", return_value=args), \ + mock.patch.object(sweep, "git_revision", return_value="fixture"), \ + mock.patch.object(sweep, "require_clean_source_tree"), \ + mock.patch.object(sweep, "cargo_config_context", return_value=[]), \ + mock.patch.object(sweep, "toolchain_context", return_value=context), \ + mock.patch.object(sweep, "require_source_provenance"), \ + mock.patch.object(sweep.platform, "platform", return_value="fixture"), \ + mock.patch.object(sweep.subprocess, "run", return_value= subprocess.CompletedProcess([], 1, b"driver: \xff\n")): + self.assertEqual(sweep.main(), 1) + self.assertEqual((output / "n000512/run.log").read_bytes(), b"driver: \xff\n") + self.assertEqual(json.loads((output / "manifest.json").read_text())["status"], "failed") + def test_invalid_utf8_receipt_is_normalized_to_sweep_error(self): with tempfile.TemporaryDirectory() as tmp: path = Path(tmp) / "receipt.json" From 9755fd8d02f481d504345da80dcb7f7276eb046f Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:24:30 +0930 Subject: [PATCH 20/82] Make lightweight self-gravitating N-body observatory the home page --- .github/workflows/ci.yml | 1 + .github/workflows/pages.yml | 1 + README.md | 20 ++- ROADMAP.md | 4 + barnes-hut.css | 10 ++ barnes-hut.html | 4 +- docs/BARNES-HUT.md | 13 +- index.html | 208 +++++++++++++------------- nbody-clock.js | 21 +++ nbody-viz.js | 282 ++++++++++++++++++++++++++++++++++++ rotation-lab.html | 121 ++++++++++++++++ scripts/build-site.mjs | 4 +- tests/app.mjs | 2 +- tests/nbody-view.mjs | 72 +++++++++ tests/smoke.mjs | 2 +- 15 files changed, 651 insertions(+), 114 deletions(-) create mode 100644 nbody-clock.js create mode 100644 nbody-viz.js create mode 100644 rotation-lab.html create mode 100644 tests/nbody-view.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef4ddd2..983f66f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,7 @@ jobs: - name: Check Barnes-Hut self-gravity reference run: | node tests/barnes-hut.mjs + node tests/nbody-view.mjs cargo test --manifest-path nbody/Cargo.toml --locked --offline cargo run --manifest-path nbody/Cargo.toml --locked --offline -- verify cargo run --manifest-path nbody/Cargo.toml --locked --offline -- verify-flat diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 41032de..fcaf318 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -29,6 +29,7 @@ jobs: node tests/physics.mjs node tests/app.mjs node tests/barnes-hut.mjs + node tests/nbody-view.mjs cargo test --manifest-path nbody/Cargo.toml --locked --offline cargo run --manifest-path nbody/Cargo.toml --locked --offline -- verify node scripts/build-site.mjs diff --git a/README.md b/README.md index 7fb5da8..fd1f4ab 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,15 @@ It began as an adaptation of the VORTEX 2.1.0 particle lab and now combines: -- interactive rotation-curve visualization; +- a lightweight default N-body simulator: 768 real interacting bodies, luminous sprites and short orbital trails; +- interactive rotation-curve visualization in the preserved rotation-law instrument; - UFF, Newtonian, NFW, Burkert, MOND/RAR, and authored visual rotation laws; - deterministic Rust/WebAssembly sampling in the browser; - native Rust CPU execution with Float/libm and BAM32/Q2.30 LUT backends; - Vulkan/`wgpu` and NVIDIA CUDA compute paths; - memory-bounded exact-u64 logical addressing; - reproducible benchmark receipts, topology evidence, and archived scaling studies; -- an opt-in Barnes–Hut resident self-gravity laboratory with a direct-force oracle and live quadtree visualization; +- a Barnes–Hut resident self-gravity laboratory with a direct-force oracle and live quadtree visualization; - a GPU-oriented BH #2A Morton/Z-order + flat-cell CPU substrate with repeatable topology receipts; - a BH #2B1 explicit f32/u32 transfer ABI with executable Vulkan/WGSL flat-tree traversal and matched CUDA traversal source; - BH #2B2 multi-step resident self-gravity with persistent GPU state, GPU kick/drift/final-kick kernels, and an explicit CPU tree-rebuild boundary; @@ -37,9 +38,10 @@ The v0.4.0 native-CPU evidence baseline remains archived at Zenodo as: | Layer | Current state | | --- | --- | -| Browser instrument | Offline HTML/CSS/JS + bundled Rust/Wasm; no server or CDN required | -| Browser logical population | Up to `2^32 = 4,294,967,296` logical stars on the Rust/Wasm path | -| Browser rendered sample | Up to 65,536 stars per frame on Rust/Wasm + WebGL | +| Browser N-body home page | 128–2,048 interacting bodies (768 default); planar Barnes–Hut gravity, leapfrog integration, glow/trails and orbit/zoom controls | +| Rotation-law instrument | Preserved at `rotation-lab.html`; offline HTML/CSS/JS + bundled Rust/Wasm | +| Rotation-law logical population | Up to `2^32 = 4,294,967,296` logical stars on the Rust/Wasm path | +| Rotation-law rendered sample | Up to 65,536 stars per frame on Rust/Wasm + WebGL | | Native CPU runtime | Exact positive-u64 logical population, bounded resident sample up to 16,777,216 particles | | CPU projection backends | `float-libm` and `bam-lut-q30` | | Native GPU runtime | Rust/`wgpu`/Vulkan plus NVIDIA CUDA/CuPy RawKernel | @@ -61,7 +63,11 @@ The earlier v0.4.0 archive remains the **before-state** for the CPU architecture ## 1. Browser instrument -Open **`index.html`** directly in a modern browser. No install, local server, CDN, or network connection is required, including for the bundled Rust/WebAssembly engine. +Open **`index.html`** directly in a modern browser for the N-body simulator. No install, local server, CDN, or network connection is required. The default 768 bodies all contribute mass; reducing the resident count reduces computation. Glow sprites and ten-step trails add visual density without adding simulated particles. The camera tilts a planar physical system; it is not a 3D force solver. + +Choose a binary encounter, rotating disc or cold collapse. Pause, single-step, change the seed, inspect the tree, or pause for a direct-force audit. Physics uses a fixed time step on a 60 Hz schedule, independent of monitor refresh rate. Overload drops catch-up work rather than enlarging the time step or changing the body count. Reduced-motion preference starts paused and disables trails. + +The original prescribed-field instrument, including its Rust/WebAssembly sampling, UFF controls and exports, is preserved at **`rotation-lab.html`**. The detailed tree laboratory remains at **`barnes-hut.html`**. The following logical-population and sampling controls describe the rotation-law instrument. The separate **`barnes-hut.html`** entrypoint is an opt-in resident self-gravity lab with a live quadtree overlay and direct-force error probes. It does not change the default rotation-law instrument. @@ -275,7 +281,7 @@ BH #2D correctness is exercised through software Vulkan in CI, but **software-Vu The remaining BH #2D evidence item has a fail-closed real-GPU sweep runner: ```bash -python3 scripts/bench-bh2d-hardware.py \ +python3 -I scripts/bench-bh2d-hardware.py \ --output runs/bh2d-hardware-sweep \ --adapter 0 ``` diff --git a/ROADMAP.md b/ROADMAP.md index ce3ef91..39d4764 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -399,3 +399,7 @@ All of the following remain useful research directions, but they are explicitly These items were deferred while PE #15 was active. v0.6.0 closed that phase; any future work now requires an explicit phase with its own evidence boundary. The previous detailed designs are preserved by the immutable `v0.5.0` source archive and tag; they are not discarded, only postponed. + +## Browser N-body observatory + +The home page now runs the existing planar Barnes–Hut solver with a modest 768-body default, selectable 128–2,048 bodies, fixed-rate integration, glow/trails and camera controls. The original prescribed-field visual is preserved at `rotation-lab.html`; `barnes-hut.html` retains the detailed tree lab. This UI upgrade supplies no native GPU timing evidence and does not change BH #2D evidence-pending or BH #2E promotion gates. diff --git a/barnes-hut.css b/barnes-hut.css index 339a46a..c608237 100644 --- a/barnes-hut.css +++ b/barnes-hut.css @@ -69,3 +69,13 @@ footer { min-height: 40px; border-top: 1px solid var(--line); color: var(--muted .readouts div { border-right: 0; border-bottom: 1px solid var(--line); } .readouts div:last-child { border-bottom: 0; } } + +/* N-body observatory controls. */ +.gesture-note { color: var(--muted); font-size: 0.7rem; align-self: center; margin-left: auto; } +#nbodyCanvas { touch-action: none; cursor: grab; } +#nbodyCanvas:active { cursor: grabbing; } +:focus-visible { outline: 2px solid var(--hot); outline-offset: 3px; } +input[type="range"], input[type="checkbox"] { accent-color: var(--hot); } +@media (max-width: 560px) { + .gesture-note { margin-left: 0; width: 100%; line-height: 1.5; } +} diff --git a/barnes-hut.html b/barnes-hut.html index 1abf041..d6b9223 100644 --- a/barnes-hut.html +++ b/barnes-hut.html @@ -18,7 +18,7 @@

QSOL-IMC / GALAXY / SELF-GRAVITY LAB

BARNES–HUT

- +
@@ -48,7 +48,7 @@

BARNES–HUT

What changed?

This lab is a separate self-gravitating execution path. Every resident body contributes mass to a deterministic quadtree. Distant cells may be replaced by their centre of mass when s / d < θ; nearby cells are opened. A kick–drift–kick leapfrog step rebuilds the tree after the drift.

-

The main GALAXY rotation-law instrument remains unchanged. Its huge logical populations are sampled independent test particles; they are not silently reinterpreted as mutually interacting bodies. This lab keeps the resident population explicit because self-gravity creates cross-particle coupling.

+

The GALAXY rotation-law instrument remains available separately. Its huge logical populations are sampled independent test particles; they are not silently reinterpreted as mutually interacting bodies. This lab keeps the resident population explicit because self-gravity creates cross-particle coupling.

diff --git a/docs/BARNES-HUT.md b/docs/BARNES-HUT.md index a1a63ab..9944578 100644 --- a/docs/BARNES-HUT.md +++ b/docs/BARNES-HUT.md @@ -2,7 +2,7 @@ GALAXY now has two deliberately distinct dynamics families: -1. **Prescribed-field / test-particle modes** — the existing browser, CPU and GPU paths. These retain huge logical address spaces because individual resident particles do not affect one another. +1. **Prescribed-field / test-particle modes** — the rotation-law browser instrument (`rotation-lab.html`), CPU and GPU paths. These retain huge logical address spaces because individual resident particles do not affect one another. 2. **Resident self-gravity** — the Barnes–Hut laboratory. Every resident body's mass contributes to the force field, so the complete interacting resident set is explicit and cannot be substituted by independent logical-u64 tiles. ## Why Barnes–Hut @@ -55,6 +55,9 @@ A future GPU implementation should preserve this CPU/direct reference as its cor ## Visualization +`index.html` is the default lightweight N-body observatory. It reuses the verified browser force solver with 768 resident bodies by default (128–2,048 selectable), fixed-rate leapfrog scheduling, seeded presets, optional trails and luminous sprites. Rendering contributes no additional gravitational mass. Orbit/zoom controls project the planar dynamics without modifying them. The scheduler caps catch-up at four steps per frame, discards overload debt, and suspends in hidden tabs; under load simulated time advances more slowly. Force probes pause the simulation so their result remains attached to the displayed state. + + `barnes-hut.html` is an offline browser laboratory. It renders the resident bodies and can overlay quadtree cells while the system evolves. Controls expose: - resident body count; @@ -304,7 +307,7 @@ Counts through 4,096 retain repeat-matched BH #2C timing. Larger points explicit Example on a real GPU: ```bash -python3 scripts/bench-bh2d-hardware.py \ +python3 -I scripts/bench-bh2d-hardware.py \ --output runs/bh2d-hardware-sweep \ --adapter 0 ``` @@ -314,3 +317,9 @@ A completed scaling manifest is hardware evidence for the exact recorded source, ## Next rung After real-hardware BH #2D receipts exist, BH #2E can evaluate scaling, memory/capacity limits and production promotion while retaining BH #2A/B2B2/B2C as frozen oracles. + +### Hardware harness launch and toolchain boundary + +Launch with `python3 -I scripts/bench-bh2d-hardware.py ...`. Non-isolated direct invocation is rejected before optional imports. This prevents repository/PYTHONPATH modules from entering the standalone harness import path. Imported use by the unit-test runner is for host validation only. + +The hardware capture harness currently uses a POSIX system build PATH (`/usr/bin:/bin`). It records selected Cargo/rustc binaries separately from rustup proxies, executes the selected Cargo binary with an explicit recorded `RUSTC`, and fingerprints available system compiler/linker tools. The OS, installed system libraries and toolchain are trusted host prerequisites; this is provenance checking, not a sandbox against a hostile host. Git checks clear ambient `GIT_*` selectors and bind the checkout explicitly, including linked worktrees. Relative `CARGO_HOME` is interpreted from Cargo's repository working directory. Logs preserve raw bytes, receipt parser failures enter the failed-manifest path, and workload values are validated before any output is created. Manifests serialize strict JSON. diff --git a/index.html b/index.html index 6aad4eb..73ef9ae 100644 --- a/index.html +++ b/index.html @@ -1,121 +1,131 @@ - + - - - GALAXY · Stellar motion lab - - - - - - - - + + + GALAXY · N-body simulator + + + +
- -

QSOL-IMC / STELLAR MOTION LAB

GALAXY

- +
+

QSOL-IMC / LIVE N-BODY SIMULATOR

+

GALAXY

+
+
-
-
-

LIVE OBSERVATION

Grand design spiral

Differential rotation
-
- Your browser needs Canvas support to display the galaxy. -
INCLINATION 38°
-
DISC / BULGE / HALO— FPS
+ +
+
+
+

FEWER BODIES. REAL GRAVITY.

Binary disc encounter

+ RUNNING
-
Drag to orbit · scroll to zoom · space to pause
-
-
LOGICAL STARS16,777,2162²⁴ indexed field
-
RENDERED STARS—representative sample / frame
-
PARTICLE BUFFER—sample + orbital-rate buffers
+
+ +
θ 0.50— FPS
+
SELF-GRAVITY / LEAPFROGSTEP 0
-
-

Rotation curve

UFF empirical v4
- -
━ Selected model┄ Baryons○ UFF demo input ± error
-
At 8 kpc —Orbit period —Model time 0 Myr
-

UFF demonstration data · fixed parameters, no fit. Shaded ends extend the supplied component speeds beyond 0.5–12 kpc.

-
Compare the six demo rows - - - - - - -
Radius (kpc)Demo ± error (km/s)Model (km/s)
0.540 ± 5—
1.065 ± 5—
2.090 ± 4—
5.0115 ± 4—
8.0130 ± 6—
12.0135 ± 7—
-
-

Preparing the deterministic star field.

-
About this galaxy

The UFF modes use gas, disc and bulge component curves to calculate circular speeds, with a selectable halo or acceleration law and an optional central mass. Those speeds drive the stars’ orbits. The original visual mode retains its adjustable shear.

The star distribution remains an authored disc, bulge and halo. Morphology controls change that display geometry; mass-to-light controls change the dynamics. Logical stars describe a sampled population. Only the rendered count is drawn.

UFF’s demo table is an included example, not an identified observational catalogue. Source equations and model definitions ↗

+
+ + + + + Drag to orbit · scroll to zoom · space to pause +
+ +
+
BODIES—resident, mutually coupled
+
TREE NODES—— depth
+
FORCE TERMS—— vs direct
+
PROBE RMS ERROR—12 direct-force probes
+
+ +

768 bodies, each contributing mass. Glow and trails add depth without adding gravitational work.

+
+ Real gravity, a little optical theatre +

Every displayed body participates in this planar N-body simulation. A deterministic Barnes–Hut quadtree approximates distant mass; a kick–drift–kick leapfrog integrator evolves positions and velocities. Bodies can pull, scatter and form tidal structures instead of following prescribed orbits.

+

Soft luminous sprites and short position-history trails make a modest population look rich. They add no mass or hidden simulated stars. The 3D-looking inclination is a projection of a 2D physical system, in normalized units with G = 1.

+

Physics targets 60 fixed steps per second at 1× speed. Under load it slows down rather than increasing the integration step or changing the population. This browser view is separate from the native GPU evidence programme.

+
-
- +
GALAXY N-body simulator · normalized planar coordinates · Newtonian point-mass force with Plummer-style softening
diff --git a/nbody-clock.js b/nbody-clock.js new file mode 100644 index 0000000..56bfebe --- /dev/null +++ b/nbody-clock.js @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// Bounded fixed-rate scheduling; never enlarge dt to catch up with rendering. +(function (global) { + "use strict"; + class FixedClock { + constructor() { this.reset(); } + reset() { this.last = null; this.remainder = 0; } + advance(now, active, speed = 1) { + if (!active) { this.reset(); return 0; } + if (this.last === null) { this.last = now; return 0; } + const elapsed = Math.max(0, Math.min(0.1, (now - this.last) / 1000)); + this.last = now; + this.remainder += elapsed * 60 * speed; + const due = Math.floor(this.remainder + 1e-9); + const steps = Math.min(4, due); + this.remainder -= due; // Drop overload debt; no unbounded catch-up burst. + return steps; + } + } + global.GalaxyNBodyClock = FixedClock; +})(globalThis); diff --git a/nbody-viz.js b/nbody-viz.js new file mode 100644 index 0000000..2d17a76 --- /dev/null +++ b/nbody-viz.js @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: Apache-2.0 +(function () { + "use strict"; + const BH = globalThis.GalaxyBarnesHut; + const byId = id => document.getElementById(id); + const canvas = byId("nbodyCanvas"); + const ctx = canvas.getContext("2d", { alpha: false }); + if (!BH || !ctx) throw new Error("N-body simulator could not start"); + + const motion = matchMedia("(prefers-reduced-motion: reduce)"); + const clock = new globalThis.GalaxyNBodyClock(); + const history = []; + const sprites = [makeSprite("224,187,126"), makeSprite("206,211,220")]; + let lastReadout = 0, previousDrawStep = -1; + const state = { + bodies: [], latest: null, running: !motion.matches, step: 0, lastFrame: 0, fpsStart: performance.now(), frames: 0, + preset: "collision", count: 768, seed: 303, theta: 0.5, softening: 0.03, bucket: 4, + dt: 0.004, stepsPerFrame: 1, showTree: false, treeDepth: 5, probeRms: null, simulatedTime: 0, tilt: 38, rotation: -18, zoom: 1, light: 1, trails: !motion.matches, view: { cx: 0, cy: 0, span: 3 } + }; + + function makeSprite(rgb) { + const sprite = document.createElement("canvas"); + sprite.width = sprite.height = 64; + const paint = sprite.getContext("2d"); + const glow = paint.createRadialGradient(32, 32, 0, 32, 32, 32); + glow.addColorStop(0, "rgba(255,249,228,1)"); + glow.addColorStop(0.07, `rgba(${rgb},0.85)`); + glow.addColorStop(0.22, `rgba(${rgb},0.25)`); + glow.addColorStop(0.6, `rgba(${rgb},0.045)`); + glow.addColorStop(1, `rgba(${rgb},0)`); + paint.fillStyle = glow; paint.fillRect(0, 0, 64, 64); + return sprite; + } + + function remember() { + const points = new Float64Array(state.bodies.length * 2); + state.bodies.forEach((b, i) => { points[i * 2] = b.x; points[i * 2 + 1] = b.y; }); + history.push(points); + if (history.length > 10) history.shift(); + } + + function options() { return { theta: state.theta, softening: state.softening, bucket: state.bucket, maxDepth: 40, G: 1 }; } + function formatInt(n) { return Math.round(n).toLocaleString("en-US"); } + function presetName() { return state.preset === "collision" ? "Binary disc encounter" : state.preset === "cold" ? "Cold collapse" : "Single rotating disc"; } + + function reset() { + if (state.preset === "collision") state.bodies = BH.makeCollision(state.count, state.seed); + else { + state.bodies = BH.makeDisc(state.count, state.seed, { radius: state.preset === "cold" ? 1.05 : 0.95, spin: state.preset === "cold" ? 0.05 : 0.72 }); + } + state.latest = BH.accelerations(state.bodies, options()); + state.step = 0; + state.simulatedTime = 0; + state.view = extent(); state.zoom = 1; + byId("zoom").value = 1; + history.length = 0; remember(); clock.reset(); + syncControlLabels(); + invalidateAudit(); + byId("presetTitle").textContent = presetName(); + byId("viewStatus").textContent = `${state.bodies.length.toLocaleString()} bodies, each contributing mass. Glow and trails add depth without adding gravitational work.`; + updateReadouts(); draw(); + } + + function resize() { + const rect = canvas.getBoundingClientRect(); + const dpr = Math.min(globalThis.devicePixelRatio || 1, 2); + const width = Math.max(1, Math.round(rect.width * dpr)); + const height = Math.max(1, Math.round(rect.height * dpr)); + if (canvas.width !== width || canvas.height !== height) { canvas.width = width; canvas.height = height; } + } + + function extent() { + let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; + for (const b of state.bodies) { + minX = Math.min(minX, b.x); minY = Math.min(minY, b.y); maxX = Math.max(maxX, b.x); maxY = Math.max(maxY, b.y); + } + const cx = (minX + maxX) * 0.5, cy = (minY + maxY) * 0.5; + const span = Math.max(maxX - minX, maxY - minY, 2.4) * 1.15; + return { cx, cy, span }; + } + + function draw() { + resize(); + const w = canvas.width, h = canvas.height, view = state.view; + const dpr = Math.min(globalThis.devicePixelRatio || 1, 2); + const scale = Math.min(w, h) / view.span * state.zoom; + const angle = state.rotation * Math.PI / 180; + const c = Math.cos(angle), sn = Math.sin(angle), tilt = Math.cos(state.tilt * Math.PI / 180); + const project = (x, y) => { + x -= view.cx; y -= view.cy; + return [w * 0.5 + (x * c - y * sn) * scale, h * 0.5 - (x * sn + y * c) * tilt * scale]; + }; + ctx.fillStyle = "#020304"; ctx.fillRect(0, 0, w, h); + // Faint reference rings supply depth without extra simulated particles. + ctx.strokeStyle = "rgba(151,143,126,0.08)"; ctx.lineWidth = dpr * 0.65; + for (const radius of [0.5, 1, 1.5]) { + ctx.beginPath(); + for (let k = 0; k <= 96; k++) { + const a = k / 96 * Math.PI * 2; + const [x, y] = project(view.cx + radius * Math.cos(a), view.cy + radius * Math.sin(a)); + if (k === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); + } + ctx.stroke(); + } + if (state.showTree && state.latest) { + ctx.lineWidth = dpr * 0.6; + for (const node of BH.flattenTree(state.latest.tree, state.treeDepth)) { + if (node.depth === 0) continue; + ctx.strokeStyle = `rgba(214,173,103,${Math.max(0.05, 0.3 - node.depth * 0.035)})`; + ctx.beginPath(); + for (const [i, [dx, dy]] of [[-1,-1], [1,-1], [1,1], [-1,1]].entries()) { + const [x, y] = project(node.cx + dx * node.half, node.cy + dy * node.half); + if (!i) ctx.moveTo(x,y); else ctx.lineTo(x,y); + } + ctx.closePath(); ctx.stroke(); + } + } + ctx.globalCompositeOperation = "lighter"; + if (state.trails && history.length > 1) { + ctx.lineWidth = 0.7 * dpr; + for (let k = 1; k < history.length; k++) { + ctx.strokeStyle = `rgba(198,180,150,${0.18 * k / history.length * state.light})`; + ctx.beginPath(); + for (let i = 0; i < state.bodies.length; i++) { + const [x0,y0] = project(history[k-1][i*2], history[k-1][i*2+1]); + const [x1,y1] = project(history[k][i*2], history[k][i*2+1]); + ctx.moveTo(x0,y0); ctx.lineTo(x1,y1); + } + ctx.stroke(); + } + } + for (let i = 0; i < state.bodies.length; i++) { + const b = state.bodies[i]; + const [x, y] = project(b.x, b.y); + const size = (10 + (i % 7) * 1.5) * dpr * Math.sqrt(state.light); + ctx.globalAlpha = 0.65 + (i % 5) * 0.07; + const group = state.preset === "collision" ? (i < state.bodies.length / 2 ? 0 : 1) : i % 2; + ctx.drawImage(sprites[group], x - size/2, y - size/2, size, size); + } + ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; + previousDrawStep = state.step; + } + + function updateReadouts() { + const n = state.bodies.length; + byId("bodyReadout").textContent = formatInt(n); + byId("thetaBadge").textContent = "θ " + state.theta.toFixed(2); + byId("timeReadout").textContent = "t " + state.simulatedTime.toFixed(3) + " · STEP " + formatInt(state.step); + if (state.latest) { + const stats = state.latest.stats; + const terms = stats.direct + stats.approximated; + const exactTerms = n * (n - 1); + const reduction = exactTerms ? Math.max(0, 1 - terms / exactTerms) : 0; + byId("nodeReadout").textContent = formatInt(state.latest.tree.nodeCount); + byId("depthReadout").textContent = state.latest.tree.maxDepth + " levels · " + formatInt(state.latest.tree.leafCount) + " leaves"; + byId("termReadout").textContent = formatInt(terms); + byId("reductionReadout").textContent = (reduction * 100).toFixed(1) + "% fewer force terms than direct"; + } + byId("errorReadout").textContent = state.probeRms === null ? "—" : (state.probeRms * 100).toFixed(2) + "%"; + byId("runBadge").textContent = state.running ? "RUNNING" : "PAUSED"; + byId("playToggle").textContent = state.running ? "Pause" : "Resume"; + byId("playToggle").setAttribute("aria-pressed", String(state.running)); + } + + function invalidateAudit(message = "Run the direct-force probe for the current state.") { + state.probeRms = null; + byId("errorReadout").textContent = "—"; + byId("auditStatus").textContent = message; + } + + function integrateOnce() { + state.latest = BH.stepLeapfrog(state.bodies, state.dt, options()); + state.step++; + state.simulatedTime += state.dt; + if (state.trails) remember(); + invalidateAudit("State advanced; run the direct-force probe again for this step."); + } + + function audit() { + if (!state.latest) return; + state.running = false; clock.reset(); + const n = state.bodies.length; + const probes = Math.min(12, n); + let sumSq = 0, max = 0; + for (let k = 0; k < probes; k++) { + const index = Math.floor(k * (n - 1) / Math.max(1, probes - 1)); + const exact = BH.directAccelerationFor(index, state.bodies, options()); + const approx = state.latest.values[index]; + const denom = Math.max(Math.hypot(exact.ax, exact.ay), 1e-30); + const rel = Math.hypot(approx.ax - exact.ax, approx.ay - exact.ay) / denom; + sumSq += rel * rel; max = Math.max(max, rel); + } + state.probeRms = Math.sqrt(sumSq / probes); + byId("auditStatus").textContent = `Direct audit: ${probes} probes · RMS ${(state.probeRms * 100).toFixed(3)}% · max ${(max * 100).toFixed(3)}%.`; + updateReadouts(); + } + + function syncControlLabels() { + byId("countValue").textContent = state.count; + byId("thetaValue").textContent = state.theta.toFixed(2); + byId("softeningValue").textContent = state.softening.toFixed(3); + byId("bucketValue").textContent = state.bucket; + byId("treeDepthValue").textContent = state.treeDepth; + byId("dtValue").textContent = state.dt.toFixed(4); + byId("stepsValue").textContent = state.stepsPerFrame + "×"; + byId("tiltValue").textContent = state.tilt + "°"; + byId("rotationValue").textContent = state.rotation + "°"; + byId("zoomValue").textContent = state.zoom.toFixed(1) + "×"; + byId("lightValue").textContent = state.light.toFixed(1) + "×"; + byId("trails").checked = state.trails; + } + + function bind() { + byId("playToggle").addEventListener("click", () => { state.running = !state.running; clock.reset(); updateReadouts(); }); + byId("singleStep").addEventListener("click", () => { state.running = false; clock.reset(); integrateOnce(); draw(); updateReadouts(); }); + byId("reset").addEventListener("click", reset); + byId("audit").addEventListener("click", audit); + byId("preset").addEventListener("change", e => { state.preset = e.target.value; reset(); }); + byId("count").addEventListener("input", e => { state.count = Number(e.target.value); syncControlLabels(); }); + byId("count").addEventListener("change", reset); + byId("seed").addEventListener("change", e => { state.seed = Math.min(4294967295, Math.max(1, Math.trunc(Number(e.target.value) || 303))); e.target.value = state.seed; reset(); }); + byId("theta").addEventListener("input", e => { state.theta = Number(e.target.value); syncControlLabels(); state.latest = BH.accelerations(state.bodies, options()); invalidateAudit("Opening angle changed; run the direct-force probe again."); updateReadouts(); draw(); }); + byId("softening").addEventListener("input", e => { state.softening = Number(e.target.value); syncControlLabels(); state.latest = BH.accelerations(state.bodies, options()); invalidateAudit("Softening changed; run the direct-force probe again."); updateReadouts(); draw(); }); + byId("bucket").addEventListener("input", e => { state.bucket = Number(e.target.value); syncControlLabels(); state.latest = BH.accelerations(state.bodies, options()); invalidateAudit("Leaf bucket changed; run the direct-force probe again."); updateReadouts(); draw(); }); + byId("showTree").addEventListener("change", e => { state.showTree = e.target.checked; draw(); }); + byId("treeDepth").addEventListener("input", e => { state.treeDepth = Number(e.target.value); syncControlLabels(); draw(); }); + byId("dt").addEventListener("input", e => { state.dt = Number(e.target.value); syncControlLabels(); }); + byId("steps").addEventListener("input", e => { state.stepsPerFrame = Number(e.target.value); syncControlLabels(); }); + byId("fitView").addEventListener("click", () => { state.view = extent(); state.zoom = 1; byId("zoom").value = 1; syncControlLabels(); draw(); }); + for (const name of ["tilt", "rotation", "zoom", "light"]) { + byId(name).addEventListener("input", e => { state[name] = Number(e.target.value); syncControlLabels(); draw(); }); + } + byId("trails").addEventListener("change", e => { state.trails = e.target.checked; history.length = 0; remember(); draw(); }); + let pointer = null; + canvas.addEventListener("pointerdown", e => { pointer = { id: e.pointerId, x: e.clientX, y: e.clientY }; canvas.setPointerCapture(e.pointerId); }); + canvas.addEventListener("pointermove", e => { + if (!pointer || pointer.id !== e.pointerId) return; + state.rotation = Math.round(((state.rotation + (e.clientX - pointer.x) * 0.4 + 540) % 360) - 180); + state.tilt = Math.round(Math.max(0, Math.min(80, state.tilt + (e.clientY - pointer.y) * 0.3))); + pointer.x = e.clientX; pointer.y = e.clientY; + byId("rotation").value = state.rotation; byId("tilt").value = state.tilt; + syncControlLabels(); draw(); + }); + for (const event of ["pointerup", "pointercancel", "lostpointercapture"]) canvas.addEventListener(event, () => { pointer = null; }); + canvas.addEventListener("wheel", e => { + e.preventDefault(); state.zoom = Math.max(0.3, Math.min(3, state.zoom * Math.exp(-e.deltaY * 0.001))); + byId("zoom").value = state.zoom; syncControlLabels(); draw(); + }, { passive: false }); + document.addEventListener("visibilitychange", () => { clock.reset(); }); + motion.addEventListener("change", e => { + if (e.matches) { state.running = false; state.trails = false; clock.reset(); history.length = 0; syncControlLabels(); updateReadouts(); draw(); } + }); + globalThis.addEventListener("resize", draw); + document.addEventListener("keydown", e => { + if (e.code === "Space" && !e.repeat && !e.target.closest("input, select, button, a, summary")) { + e.preventDefault(); state.running = !state.running; clock.reset(); updateReadouts(); + } + }); + } + + function frame(now) { + const steps = clock.advance(now, state.running && !document.hidden, state.stepsPerFrame); + try { + for (let i = 0; i < steps; i++) integrateOnce(); + } catch (error) { + state.running = false; clock.reset(); + byId("viewStatus").textContent = "Simulation stopped: " + error.message + ". Reduce the time step, then reset."; + } + if (!document.hidden) { + if (previousDrawStep !== state.step) draw(); + if (now - lastReadout > 200 || !state.running) { updateReadouts(); lastReadout = now; } + state.frames++; + if (now - state.fpsStart >= 750) { + byId("fpsReadout").textContent = Math.round(state.frames * 1000 / (now - state.fpsStart)) + " FPS"; + state.frames = 0; state.fpsStart = now; + } + } + requestAnimationFrame(frame); + } + + bind(); syncControlLabels(); reset(); requestAnimationFrame(frame); +})(); diff --git a/rotation-lab.html b/rotation-lab.html new file mode 100644 index 0000000..e05f8f6 --- /dev/null +++ b/rotation-lab.html @@ -0,0 +1,121 @@ + + + + + + + + + + GALAXY · Stellar motion lab + + + + + + + + + + +
+ +

QSOL-IMC / STELLAR MOTION LAB

GALAXY

+ +
+
+
+

LIVE OBSERVATION

Grand design spiral

Differential rotation
+
+ Your browser needs Canvas support to display the galaxy. +
INCLINATION 38°
+
DISC / BULGE / HALO— FPS
+
+
Drag to orbit · scroll to zoom · space to pause
+
+
LOGICAL STARS16,777,2162²⁴ indexed field
+
RENDERED STARS—representative sample / frame
+
PARTICLE BUFFER—sample + orbital-rate buffers
+
+
+

Rotation curve

UFF empirical v4
+ +
━ Selected model┄ Baryons○ UFF demo input ± error
+
At 8 kpc —Orbit period —Model time 0 Myr
+

UFF demonstration data · fixed parameters, no fit. Shaded ends extend the supplied component speeds beyond 0.5–12 kpc.

+
Compare the six demo rows + + + + + + +
Radius (kpc)Demo ± error (km/s)Model (km/s)
0.540 ± 5—
1.065 ± 5—
2.090 ± 4—
5.0115 ± 4—
8.0130 ± 6—
12.0135 ± 7—
+
+

Preparing the deterministic star field.

+
About this galaxy

The UFF modes use gas, disc and bulge component curves to calculate circular speeds, with a selectable halo or acceleration law and an optional central mass. Those speeds drive the stars’ orbits. The original visual mode retains its adjustable shear.

The star distribution remains an authored disc, bulge and halo. Morphology controls change that display geometry; mass-to-light controls change the dynamics. Logical stars describe a sampled population. Only the rendered count is drawn.

UFF’s demo table is an included example, not an identified observational catalogue. Source equations and model definitions ↗

+
+ +
+ + + diff --git a/scripts/build-site.mjs b/scripts/build-site.mjs index c2ca2ce..e0e48bc 100644 --- a/scripts/build-site.mjs +++ b/scripts/build-site.mjs @@ -6,11 +6,11 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const output = path.join(root, "_site"); fs.rmSync(output, { recursive: true, force: true }); fs.mkdirSync(output, { recursive: true }); -for (const file of ["index.html", "style.css", "barnes-hut.html", "barnes-hut.css", "barnes-hut.js", "barnes-hut-viz.js", "galaxy-core.js", "uff-physics.js", "rotation-curve.js", "renderer.js", "app.js", "data/uff-demo.js", "data/uff/DEMO_GALAXY.csv", "data/uff/provenance.json", "data/uff/NOTICE", "wasm/galaxy-wasm.js", "wasm/galaxy_sampler.wasm", "LICENSE", "LICENSES/MPL-2.0.txt", "NOTICE.md"]) { +for (const file of ["index.html", "rotation-lab.html", "nbody-clock.js", "nbody-viz.js", "style.css", "barnes-hut.html", "barnes-hut.css", "barnes-hut.js", "barnes-hut-viz.js", "galaxy-core.js", "uff-physics.js", "rotation-curve.js", "renderer.js", "app.js", "data/uff-demo.js", "data/uff/DEMO_GALAXY.csv", "data/uff/provenance.json", "data/uff/NOTICE", "wasm/galaxy-wasm.js", "wasm/galaxy_sampler.wasm", "LICENSE", "LICENSES/MPL-2.0.txt", "NOTICE.md"]) { fs.mkdirSync(path.dirname(path.join(output, file)), { recursive: true }); fs.copyFileSync(path.join(root, file), path.join(output, file)); } -for (const page of ["index.html", "barnes-hut.html"]) { +for (const page of ["index.html", "barnes-hut.html", "rotation-lab.html"]) { const html = fs.readFileSync(path.join(output, page), "utf8"); for (const [, asset] of html.matchAll(/(?:src|href)="([^"#]+)"/g)) { if (!asset.startsWith("http") && !fs.existsSync(path.join(output, asset))) { diff --git a/tests/app.mjs b/tests/app.mjs index d3626b0..11f8b0c 100644 --- a/tests/app.mjs +++ b/tests/app.mjs @@ -31,7 +31,7 @@ class Element { } async function boot({ rust = true, gpu = true, brokenWasm = false, reducedMotion = false } = {}) { - const html = read("index.html"), elements = new Map(); + const html = read("rotation-lab.html"), elements = new Map(); for (const match of html.matchAll(/<(\w+)\b[^>]*\bid="([^"]+)"[^>]*>/g)) { const el = new Element(match[1], match[2]); el.value = match[0].match(/\bvalue="([^"]*)"/)?.[1] || ""; diff --git a/tests/nbody-view.mjs b/tests/nbody-view.mjs new file mode 100644 index 0000000..e52e5b6 --- /dev/null +++ b/tests/nbody-view.mjs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// Execute the real solver and view with a small DOM/canvas adapter. +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import vm from 'node:vm'; +const read = name => fs.readFileSync(new URL('../' + name, import.meta.url), 'utf8'); +class Element { + constructor(tag = 'div') { this.tagName = tag; this.listeners = {}; this.value = ''; this.checked = false; this.textContent = ''; } + addEventListener(name, fn) { (this.listeners[name] ||= []).push(fn); } + emit(name, extra = {}) { for (const fn of this.listeners[name] || []) fn({target:this, preventDefault(){}, ...extra}); } + setAttribute() {} + getBoundingClientRect() { return {width:900, height:600}; } + closest() { return null; } + setPointerCapture() {} +} +function boot(reduced = false) { + const elements = new Map(); let scheduled, bodies, steps = 0, draws = 0; + for (const m of read('index.html').matchAll(/<(\w+)\b[^>]*\bid="([^"]+)"[^>]*>/g)) { + const el = new Element(m[1]); el.value = m[0].match(/value="([^"]*)"/)?.[1] || ''; + el.checked = m[0].includes(' checked'); elements.set(m[2], el); + } + const paint = new Proxy({}, { get: (o,k) => k === 'createRadialGradient' ? () => ({addColorStop(){}}) : k === 'drawImage' ? () => {draws++;} : () => {}, set:(o,k,v)=>{o[k]=v;return true;} }); + elements.get('nbodyCanvas').getContext = () => paint; + const doc = new Element(); doc.hidden = false; doc.getElementById = id => elements.get(id); + doc.createElement = () => ({getContext:()=>paint}); + const motion = new Element(); motion.matches = reduced; + const ctx = vm.createContext({document:doc, performance:{now:()=>0}, matchMedia:()=>motion, + requestAnimationFrame(fn){scheduled=fn;}, devicePixelRatio:1, addEventListener(){}, console}); + vm.runInContext(read('barnes-hut.js'),ctx); + const solver = ctx.GalaxyBarnesHut; + ctx.GalaxyBarnesHut = {...solver, accelerations(b,o){bodies=b;return solver.accelerations(b,o);}, + stepLeapfrog(b,dt,o){steps++; bodies=b;return solver.stepLeapfrog(b,dt,o);} }; + vm.runInContext(read('nbody-clock.js'),ctx); + vm.runInContext(read('nbody-viz.js'),ctx); + return {elements, doc, motion, frame:now=>scheduled(now), steps:()=>steps, draws:()=>draws, + bodies:()=>JSON.parse(JSON.stringify(bodies)), click:id=>elements.get(id).emit('click'), + set(id,value,event='input'){elements.get(id).value=String(value);elements.get(id).emit(event);} }; +} +// Same simulated wall time on 60 and 144 Hz displays gives the same trajectory. +const a=boot(), b=boot(); +a.set('count',128,'change'); b.set('count',128,'change'); +for(let i=0;i<=60;i++)a.frame(i*1000/60); +for(let i=0;i<=144;i++)b.frame(i*1000/144); +assert.equal(a.steps(),60); assert.equal(b.steps(),60); assert.deepEqual(a.bodies(),b.bodies()); +a.click('playToggle'); const paused=a.bodies(); a.frame(1200);a.frame(2000); +assert.deepEqual(a.bodies(),paused); +a.set('tilt',70);a.set('rotation',90);a.set('zoom',2); +assert.deepEqual(a.bodies(),paused,'camera must not alter dynamics'); +a.click('singleStep');assert.equal(a.steps(),61);assert.equal(a.elements.get('runBadge').textContent,'PAUSED'); +a.click('audit');assert.match(a.elements.get('auditStatus').textContent,/Direct audit: 12 probes/); +a.frame(3000); assert.notEqual(a.elements.get('errorReadout').textContent,'—'); +a.click('playToggle');a.frame(4000);a.frame(4020); +assert.equal(a.elements.get('errorReadout').textContent,'—','advancing invalidates audit'); +a.doc.hidden=true; const hidden=a.steps();a.frame(100000); assert.equal(a.steps(),hidden); +a.doc.hidden=false;a.frame(200000);assert.equal(a.steps(),hidden,'hidden tab does not catch up'); +a.frame(200017);assert.equal(a.steps(),hidden+1); +a.motion.emit('change',{matches:true});assert.equal(a.elements.get('runBadge').textContent,'PAUSED'); +assert.equal(a.elements.get('trails').checked,false); +const reduced=boot(true);reduced.frame(0);reduced.frame(1000);assert.equal(reduced.steps(),0); +for(const preset of ['disc','collision','cold']){ + reduced.set('preset',preset,'change');const initial=reduced.bodies(); + reduced.click('singleStep');assert.ok(reduced.bodies().every(b=>[b.x,b.y,b.vx,b.vy].every(Number.isFinite))); + reduced.click('reset');assert.deepEqual(reduced.bodies(),initial); +} +assert.equal(reduced.bodies().length,768); +// Assets must remain available for the default and both preserved instruments. +for(const page of ['index.html','rotation-lab.html','barnes-hut.html']) { + for(const [,asset] of read(page).matchAll(/(?:src|href)="([^"#]+)"/g)) { + if(!asset.startsWith('http'))assert.ok(fs.existsSync(new URL('../'+asset,import.meta.url)),asset); + } +} +console.log('PASS: N-body refresh-rate invariance, pause/step, view isolation, audits, reduced motion, hidden tabs, presets and offline links.'); diff --git a/tests/smoke.mjs b/tests/smoke.mjs index a8a2d1c..3032400 100644 --- a/tests/smoke.mjs +++ b/tests/smoke.mjs @@ -108,7 +108,7 @@ for (const value of [NaN, Infinity, -1, 0.5, 2 ** 32 + 1]) { assert.ok(wasm.memory.buffer.byteLength < 16 * 1024 * 1024, "Wasm memory must stay bounded across rebuilds"); // Static/offline entrypoints, unique controls, attribution and no missing assets. -const html = read("index.html"); +const html = read("rotation-lab.html"); const ids = [...html.matchAll(/\bid="([^"]+)"/g)].map(match => match[1]); assert.equal(ids.length, new Set(ids).size); for (const [, id] of read("app.js").matchAll(/byId\("([^"]+)"\)/g)) assert.ok(ids.includes(id), `Missing control ${id}`); From 67fae35a9023767d5a5349dfec4358815a974ee1 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:44:49 +0930 Subject: [PATCH 21/82] Harden BH2D hardware evidence provenance --- scripts/bench-bh2d-hardware.py | 143 +++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 95a3ab8..06b6a97 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -40,6 +40,7 @@ ) WORKGROUP_SIZE = 128 TREE_LEVELS = 17 +TREE_BUCKET_SIZE = 4 GPU_BODY_BYTES = 32 GPU_ENTRY_BYTES = 16 GPU_CELL_BYTES = 64 @@ -250,6 +251,94 @@ def build_environment(context: dict[str, Any]) -> dict[str, str]: return env +def dependency_source_context( + repo_root: Path, + toolchain: dict[str, Any], +) -> list[dict[str, Any]]: + """Bind the exact non-repository dependency trees Cargo will compile.""" + command = [ + toolchain["cargo"]["executable"], + "metadata", + "--manifest-path", + "runtime/Cargo.toml", + "--locked", + "--offline", + "--format-version", + "1", + ] + completed = subprocess.run( + command, + cwd=repo_root, + env=build_environment(toolchain), + check=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if completed.returncode != 0: + detail = (completed.stderr or completed.stdout).decode( + "utf-8", errors="replace" + ).strip() + raise SweepError(f"could not resolve locked offline Cargo dependency sources: {detail}") + try: + metadata = json.loads(completed.stdout.decode("utf-8")) + except (UnicodeDecodeError, ValueError) as exc: + raise SweepError("Cargo metadata is not valid UTF-8 JSON") from exc + + root = repo_root.resolve() + packages = require_list( + require_object(metadata, "Cargo metadata").get("packages"), + "Cargo metadata packages", + ) + result: list[dict[str, Any]] = [] + for index, value in enumerate(packages): + package = require_object(value, f"Cargo metadata packages[{index}]") + manifest = Path( + require_string( + package.get("manifest_path"), + f"Cargo metadata packages[{index}].manifest_path", + nonempty=True, + ) + ).resolve() + package_root = manifest.parent + if is_within(package_root, root): + continue + require( + package_root.is_dir(), + f"Cargo dependency source directory is missing: {package_root}", + ) + source = package.get("source") + require( + source is None or isinstance(source, str), + f"Cargo metadata packages[{index}].source must be a string or null", + ) + result.append( + { + "name": require_string( + package.get("name"), + f"Cargo metadata packages[{index}].name", + nonempty=True, + ), + "version": require_string( + package.get("version"), + f"Cargo metadata packages[{index}].version", + nonempty=True, + ), + "source": source, + "path": path_label(package_root, repo_root), + "tree_sha256": sha256_directory(package_root), + } + ) + result.sort( + key=lambda item: ( + item["name"], + item["version"], + item["source"] or "", + item["path"], + ) + ) + return result + + def reject_cargo_config_redirects(document: Any, path: Path) -> None: root = require_object(document, f"Cargo config {path}") @@ -399,6 +488,7 @@ def require_source_provenance( allowed_untracked_root: Path | None = None, expected_cargo_context: list[dict[str, str]] | None = None, expected_toolchain_context: dict[str, Any] | None = None, + expected_dependency_source_context: list[dict[str, Any]] | None = None, cargo_command: str = "cargo", ) -> None: current = git_revision(repo_root) @@ -416,6 +506,17 @@ def require_source_provenance( and current_toolchain_context != expected_toolchain_context ): raise SweepError("Cargo/Rust toolchain changed during hardware evidence capture") + current_dependency_source_context = dependency_source_context( + repo_root, + current_toolchain_context, + ) + if ( + expected_dependency_source_context is not None + and current_dependency_source_context != expected_dependency_source_context + ): + raise SweepError( + "Cargo dependency source cache changed during hardware evidence capture" + ) def load_receipt(path: Path) -> Any: @@ -430,6 +531,25 @@ def load_receipt(path: Path) -> Any: raise SweepError(f"receipt is not valid JSON: {path}") from exc +def sha256_directory(path: Path) -> str: + digest = hashlib.sha256() + root = path.resolve() + entries = sorted(root.rglob("*"), key=lambda entry: entry.relative_to(root).as_posix()) + for entry in entries: + relative = entry.relative_to(root) + if entry.is_symlink(): + raise SweepError(f"Cargo dependency source contains unsupported symlink: {entry}") + if entry.is_dir(): + continue + require(entry.is_file(), f"Cargo dependency source contains non-file entry: {entry}") + relative_bytes = os.fsencode(str(relative)) + file_digest = bytes.fromhex(sha256_file(entry)) + digest.update(len(relative_bytes).to_bytes(8, "big")) + digest.update(relative_bytes) + digest.update(file_digest) + return digest.hexdigest() + + def sha256_file(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as handle: @@ -703,6 +823,15 @@ def validate_receipt( max_depth <= TREE_LEVELS - 1, "receipt.tree.max_depth exceeds the frozen Morton depth", ) + if particles > TREE_BUCKET_SIZE: + require( + active_cell_count > leaf_count, + "receipt.tree must contain an internal cell when particles exceed the frozen bucket size", + ) + require( + max_depth > 0, + "receipt.tree.max_depth must be positive when particles exceed the frozen bucket size", + ) force = require_object(root.get("final_force"), "receipt.final_force") expected_probe_count = min(direct_probes, particles) @@ -979,6 +1108,11 @@ def main() -> int: manifest: dict[str, Any] | None = None try: particles_list = parse_particles(args.particles) + if args.preset == "collision": + require( + particles_list[0] >= 4, + "collision preset requires every particle count to be at least 4", + ) require(1 <= args.steps <= 256, "--steps must be in 1..=256") require(2 <= args.oracle_limit <= BH2C_CAP, "--oracle-limit must be in 2..=4096") require(1 <= args.benchmark_repeats <= 31, "--benchmark-repeats must be in 1..=31") @@ -1009,11 +1143,16 @@ def main() -> int: require_clean_source_tree(repo_root) build_cargo_context = cargo_config_context(repo_root) build_toolchain_context = toolchain_context(args.cargo, repo_root) + build_dependency_source_context = dependency_source_context( + repo_root, + build_toolchain_context, + ) require_source_provenance( repo_root, revision, expected_cargo_context=build_cargo_context, expected_toolchain_context=build_toolchain_context, + expected_dependency_source_context=build_dependency_source_context, cargo_command=args.cargo, ) @@ -1030,6 +1169,7 @@ def main() -> int: "build_context": { "cargo_configuration": build_cargo_context, "toolchain": build_toolchain_context, + "dependency_sources": build_dependency_source_context, "environment_overrides": [], }, "host": { @@ -1063,6 +1203,7 @@ def main() -> int: output, expected_cargo_context=build_cargo_context, expected_toolchain_context=build_toolchain_context, + expected_dependency_source_context=build_dependency_source_context, cargo_command=args.cargo, ) @@ -1095,6 +1236,7 @@ def main() -> int: output, expected_cargo_context=build_cargo_context, expected_toolchain_context=build_toolchain_context, + expected_dependency_source_context=build_dependency_source_context, cargo_command=args.cargo, ) if completed.returncode != 0: @@ -1144,6 +1286,7 @@ def main() -> int: output, expected_cargo_context=build_cargo_context, expected_toolchain_context=build_toolchain_context, + expected_dependency_source_context=build_dependency_source_context, cargo_command=args.cargo, ) manifest["status"] = "complete" From f7e0e08bcba83af78e90e77c11c43a980011253f Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:44:52 +0930 Subject: [PATCH 22/82] Cover BH2D provenance and topology review cases --- tests/test_bh2d_hardware_sweep.py | 79 +++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 856c74c..708cf64 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -332,6 +332,55 @@ def test_build_environment_overrides_are_rejected(self): with self.assertRaisesRegex(sweep.SweepError, name): sweep.require_no_build_environment_overrides() + def test_dependency_source_context_binds_cached_source_bytes(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + repo = root / "repo" + repo.mkdir() + runtime = repo / "runtime" + runtime.mkdir() + (runtime / "Cargo.toml").write_text("[package]\nname='fixture'\nversion='0.1.0'\n") + dependency = root / "cargo-home" / "registry" / "src" / "index" / "wgpu-24.0.5" + dependency.mkdir(parents=True) + manifest = dependency / "Cargo.toml" + source = dependency / "src" / "lib.rs" + source.parent.mkdir() + manifest.write_text("[package]\nname='wgpu'\nversion='24.0.5'\n") + source.write_text("pub const VALUE: u32 = 1;\n") + metadata = { + "packages": [ + { + "name": "fixture", + "version": "0.1.0", + "source": None, + "manifest_path": str(runtime / "Cargo.toml"), + }, + { + "name": "wgpu", + "version": "24.0.5", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "manifest_path": str(manifest), + }, + ] + } + completed = subprocess.CompletedProcess( + [], + 0, + stdout=__import__("json").dumps(metadata).encode(), + stderr=b"", + ) + toolchain = { + "cargo": {"executable": "/selected/cargo"}, + "rustc": {"executable": "/selected/rustc"}, + } + with mock.patch.object(sweep.subprocess, "run", return_value=completed): + first = sweep.dependency_source_context(repo, toolchain) + source.write_text("pub const VALUE: u32 = 2;\n") + second = sweep.dependency_source_context(repo, toolchain) + self.assertEqual(len(first), 1) + self.assertEqual(first[0]["name"], "wgpu") + self.assertNotEqual(first[0]["tree_sha256"], second[0]["tree_sha256"]) + def test_cargo_config_execution_and_source_redirects_are_rejected(self): cases = { "rustc-wrapper": '[build]\nrustc-wrapper = "/tmp/wrapper"\n', @@ -509,6 +558,7 @@ def test_non_utf8_run_log_preserved_and_manifest_failed(self): mock.patch.object(sweep, "require_clean_source_tree"), \ mock.patch.object(sweep, "cargo_config_context", return_value=[]), \ mock.patch.object(sweep, "toolchain_context", return_value=context), \ + mock.patch.object(sweep, "dependency_source_context", return_value=[]), \ mock.patch.object(sweep, "require_source_provenance"), \ mock.patch.object(sweep.platform, "platform", return_value="fixture"), \ mock.patch.object(sweep.subprocess, "run", return_value= subprocess.CompletedProcess([], 1, b"driver: \xff\n")): @@ -546,6 +596,28 @@ def test_verifier_command_uses_fresh_explicit_target_directory(self): self.assertEqual(command[index + 1], str(target_dir)) self.assertNotIn("runtime/target", " ".join(command)) + def test_collision_dry_run_rejects_fewer_than_four_particles(self): + import sys + with tempfile.TemporaryDirectory() as tmp: + result = subprocess.run( + [ + sys.executable, + "-I", + str(MODULE_PATH), + "--output", + str(Path(tmp) / "plan"), + "--preset", + "collision", + "--particles", + "2", + "--dry-run", + ], + capture_output=True, + text=True, + ) + self.assertEqual(result.returncode, 1) + self.assertIn("at least 4", result.stderr) + def test_hardware_receipt_at_oracle_size_is_accepted(self): summary = sweep.validate_receipt(receipt_for(4096), **validation_kwargs(4096)) self.assertEqual(summary["particles"], 4096) @@ -664,6 +736,13 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): with self.assertRaisesRegex(sweep.SweepError, "frozen Morton depth"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = 1 + receipt["tree"]["leaf_count"] = 1 + receipt["tree"]["max_depth"] = 0 + with self.assertRaisesRegex(sweep.SweepError, "internal cell"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_executed_oracle_payloads_are_required(self): receipt = receipt_for(512) receipt["gpu_oracles"] = {"status": "executed"} From 95b051f25b4fe28e1b62632b6fff9d6a1b43bac9 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:44:55 +0930 Subject: [PATCH 23/82] Correct browser entrypoint licence attribution --- NOTICE.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index 86f8464..025398b 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -9,14 +9,15 @@ and local PNG/WebM/JSON capture. Galaxy orbital equations and the accelerated renderer replace the original gate–centre–mouth transfer geometry. The adapted browser sources (`galaxy-core.js`, `app.js`, `renderer.js`, -`index.html`, `style.css`, and the JavaScript smoke/application tests) retain +`rotation-lab.html`, `style.css`, and the JavaScript smoke/application tests) retain **Mozilla Public License 2.0** file notices. The full licence is in [`LICENSES/MPL-2.0.txt`](LICENSES/MPL-2.0.txt). Their corresponding source is provided directly in this repository and static distribution. -The new Rust sampler and native example, build scripts, generated Wasm module -and browser payload, and benchmark are -**Apache-2.0**, under the repository's existing [`LICENSE`](LICENSE). +The new N-body browser entrypoint (`index.html`, `nbody-clock.js`, and +`nbody-viz.js`), Rust sampler and native example, build scripts, generated Wasm +module and browser payload, and benchmark are **Apache-2.0**, under the +repository's existing [`LICENSE`](LICENSE). The existing repository licence has not been used to relicense the MPL files. VORTEX's reference photographs, artwork and historical stress-test reports are From 053efca24cbeefe3e4515eee58408b3371ee6182 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:11:11 +0930 Subject: [PATCH 24/82] Harden BH2D Git and build provenance checks --- scripts/bench-bh2d-hardware.py | 138 ++++++++++++++++++++++++++------- 1 file changed, 111 insertions(+), 27 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 06b6a97..e6ebffa 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -24,6 +24,7 @@ import re import shlex import shutil +import stat import subprocess import tomllib from pathlib import Path @@ -72,9 +73,10 @@ } BUILD_ENV_PATTERNS = ( re.compile(r"^CARGO_TARGET_.+_(?:RUSTFLAGS|LINKER|RUNNER)$"), - re.compile( - r"^CARGO_PROFILE_.+_(?:CODEGEN_UNITS|DEBUG|INCREMENTAL|LTO|OPT_LEVEL|PANIC|RPATH|STRIP)$" - ), + # Cargo exposes profile configuration through CARGO_PROFILE__*. + # Fail closed for the whole namespace so newly added profile keys cannot + # silently change evidence-build semantics. + re.compile(r"^CARGO_PROFILE_.+$"), ) @@ -103,26 +105,54 @@ def git_invocation(command: list[str], cwd: Path) -> tuple[list[str], dict[str, if git is None: raise SweepError("system Git is required") # rev-parse also handles a linked worktree's .git file. No ambient selectors. - result = subprocess.run([git, "-C", str(cwd), "rev-parse", "--absolute-git-dir"], - env=env, capture_output=True, check=False) + result = subprocess.run( + [git, "--no-replace-objects", "-C", str(cwd), "rev-parse", "--absolute-git-dir"], + env=env, + capture_output=True, + check=False, + ) if result.returncode: raise SweepError("could not locate the checkout Git directory") git_dir = result.stdout.decode("utf-8").strip() - return [git, "--git-dir", git_dir, "--work-tree", str(cwd.resolve()), - "-c", "core.fsmonitor=false", *command[1:]], env - - -def run_checked(command: list[str], cwd: Path) -> str: + return [ + git, + "--no-replace-objects", + "--git-dir", + git_dir, + "--work-tree", + str(cwd.resolve()), + "-c", + "core.fsmonitor=false", + *command[1:], + ], env + + +def run_checked_bytes(command: list[str], cwd: Path) -> bytes: env = None if command[0] == "git": command, env = git_invocation(command, cwd) - completed = subprocess.run(command, cwd=cwd, env=env, check=False, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + completed = subprocess.run( + command, + cwd=cwd, + env=env, + check=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) if completed.returncode != 0: - detail = (completed.stderr or completed.stdout).decode("utf-8", errors="replace").strip() - raise SweepError(f"command failed ({completed.returncode}): {' '.join(command)}\n{detail}") + detail = (completed.stderr or completed.stdout).decode( + "utf-8", errors="replace" + ).strip() + raise SweepError( + f"command failed ({completed.returncode}): {' '.join(command)}\n{detail}" + ) + return completed.stdout + + +def run_checked(command: list[str], cwd: Path) -> str: + payload = run_checked_bytes(command, cwd) try: - return completed.stdout.decode("utf-8") + return payload.decode("utf-8") except UnicodeDecodeError as exc: raise SweepError("command output is not valid UTF-8") from exc @@ -430,6 +460,50 @@ def cargo_config_context(repo_root: Path) -> list[dict[str, str]]: return context +def require_raw_tracked_worktree_matches_index(repo_root: Path) -> None: + records = run_checked(["git", "ls-files", "--stage", "-z"], repo_root) + for record in (item for item in records.split("\0") if item): + try: + metadata, relative = record.split("\t", 1) + mode, object_id, stage = metadata.split(" ") + except ValueError as exc: + raise SweepError("could not parse tracked index evidence") from exc + if stage != "0": + raise SweepError( + f"tracked path has an unresolved index stage and cannot be evidence: {relative}" + ) + + candidate = repo_root / relative + expected = run_checked_bytes(["git", "cat-file", "blob", object_id], repo_root) + if mode == "120000": + if not candidate.is_symlink(): + raise SweepError( + f"tracked source tree is dirty; symlink state changed: {relative}" + ) + actual = os.fsencode(os.readlink(candidate)) + elif mode in ("100644", "100755"): + if candidate.is_symlink() or not candidate.is_file(): + raise SweepError( + f"tracked source tree is dirty; file state changed: {relative}" + ) + actual = candidate.read_bytes() + executable = bool(candidate.stat().st_mode & stat.S_IXUSR) + if executable != (mode == "100755"): + raise SweepError( + f"tracked source tree is dirty; executable mode changed: {relative}" + ) + else: + raise SweepError( + f"unsupported tracked Git mode {mode} during evidence capture: {relative}" + ) + + if actual != expected: + raise SweepError( + "tracked source tree is dirty in raw bytes; commit or revert changes " + f"before hardware evidence capture: {relative}" + ) + + def require_clean_source_tree( repo_root: Path, allowed_untracked_root: Path | None = None, @@ -451,16 +525,20 @@ def require_clean_source_tree( f"clear assume-unchanged/skip-worktree and restore a normal index first: {preview}{suffix}" ) - for command in ( - ["git", "diff", "--no-ext-diff", "--quiet", "--"], + git_command, git_env = git_invocation( ["git", "diff", "--no-ext-diff", "--cached", "--quiet", "--"], - ): - git_command, git_env = git_invocation(command, repo_root) - completed = subprocess.run(git_command, env=git_env, cwd=repo_root, check=False) - if completed.returncode != 0: - raise SweepError( - "tracked source tree is dirty; commit or revert changes before hardware evidence capture" - ) + repo_root, + ) + completed = subprocess.run(git_command, env=git_env, cwd=repo_root, check=False) + if completed.returncode != 0: + raise SweepError( + "tracked index differs from HEAD; commit or revert changes before hardware evidence capture" + ) + + # Git worktree diffs can apply repository-local clean filters. Compare the + # raw filesystem bytes against the index blobs instead, so attributes and + # filter commands cannot make altered compiler inputs appear clean. + require_raw_tracked_worktree_matches_index(repo_root) raw_untracked = run_checked( ["git", "ls-files", "--others", "--exclude-standard", "-z"], @@ -823,6 +901,10 @@ def validate_receipt( max_depth <= TREE_LEVELS - 1, "receipt.tree.max_depth exceeds the frozen Morton depth", ) + require( + active_cell_count >= max_depth + 1, + "receipt.tree.active_cell_count is too small for the reported maximum depth", + ) if particles > TREE_BUCKET_SIZE: require( active_cell_count > leaf_count, @@ -1127,15 +1209,17 @@ def main() -> int: require(1 <= args.direct_probes <= 64, "--direct-probes must be in 1..=64") require(0 <= args.seed <= 2**64 - 1, "--seed must fit u64") + output = args.output.expanduser().resolve() if args.dry_run: + plan_args = argparse.Namespace(**vars(args)) + plan_args.cargo = tool_record(args.cargo, "cargo", repo_root)["executable"] for particles in particles_list: - run_dir = args.output / f"n{particles:06d}" + run_dir = output / f"n{particles:06d}" receipt = run_dir / "receipt.json" target_dir = run_dir / "cargo-target" - print(shlex.join(command_for(args, particles, receipt, target_dir))) + print(shlex.join(command_for(plan_args, particles, receipt, target_dir))) return 0 - output = args.output.expanduser().resolve() if output.exists(): raise SweepError(f"output directory already exists: {output}") From bf1b41150606beb8995db3703885edbdbde2769c Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:11:13 +0930 Subject: [PATCH 25/82] Preserve fixed-rate 4x N-body scheduling --- nbody-clock.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nbody-clock.js b/nbody-clock.js index 56bfebe..e2b52b8 100644 --- a/nbody-clock.js +++ b/nbody-clock.js @@ -2,17 +2,20 @@ // Bounded fixed-rate scheduling; never enlarge dt to catch up with rendering. (function (global) { "use strict"; + const MAX_SPEED = 4; + const MAX_ELAPSED_SECONDS = 0.1; + const MAX_STEPS_PER_FRAME = Math.ceil(60 * MAX_SPEED * MAX_ELAPSED_SECONDS); class FixedClock { constructor() { this.reset(); } reset() { this.last = null; this.remainder = 0; } advance(now, active, speed = 1) { if (!active) { this.reset(); return 0; } if (this.last === null) { this.last = now; return 0; } - const elapsed = Math.max(0, Math.min(0.1, (now - this.last) / 1000)); + const elapsed = Math.max(0, Math.min(MAX_ELAPSED_SECONDS, (now - this.last) / 1000)); this.last = now; this.remainder += elapsed * 60 * speed; const due = Math.floor(this.remainder + 1e-9); - const steps = Math.min(4, due); + const steps = Math.min(MAX_STEPS_PER_FRAME, due); this.remainder -= due; // Drop overload debt; no unbounded catch-up burst. return steps; } From 1a774f53e26c5b582fa2c9d855c9e7e6cff96c02 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:12:06 +0930 Subject: [PATCH 26/82] Cover BH2D provenance edge cases --- tests/test_bh2d_hardware_sweep.py | 112 ++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 708cf64..0783063 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -240,6 +240,46 @@ def test_cleanliness_rejects_assume_unchanged_and_skip_worktree(self): with self.assertRaisesRegex(sweep.SweepError, "index flags"): sweep.require_clean_source_tree(repo) + def test_cleanliness_uses_raw_bytes_instead_of_clean_filters(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + repo = root / "repo" + repo.mkdir() + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + subprocess.run(["git", "config", "user.name", "GALAXY Test"], cwd=repo, check=True) + subprocess.run( + ["git", "config", "user.email", "galaxy-test@example.invalid"], + cwd=repo, + check=True, + ) + source = repo / "source.rs" + source.write_text("original\n") + subprocess.run(["git", "add", "source.rs"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "fixture"], cwd=repo, check=True) + + clean = root / "clean-filter.sh" + clean.write_text("#!/bin/sh\nsed 's/replacement/original/g'\n") + clean.chmod(0o755) + subprocess.run( + ["git", "config", "filter.hide.clean", str(clean)], + cwd=repo, + check=True, + ) + info_attributes = repo / ".git" / "info" / "attributes" + info_attributes.write_text("source.rs filter=hide\n") + source.write_text("replacement\n") + + self.assertEqual( + subprocess.run( + ["git", "diff", "--quiet", "--", "source.rs"], + cwd=repo, + check=False, + ).returncode, + 0, + ) + with self.assertRaisesRegex(sweep.SweepError, "raw bytes"): + sweep.require_clean_source_tree(repo) + def test_cleanliness_allows_only_the_evidence_output(self): with tempfile.TemporaryDirectory() as tmp: repo = Path(tmp) @@ -286,6 +326,39 @@ def test_source_provenance_rejects_head_change(self): with self.assertRaisesRegex(sweep.SweepError, "source revision changed"): sweep.require_source_provenance(repo, revision) + def test_git_replacement_objects_cannot_rewrite_provenance(self): + with tempfile.TemporaryDirectory() as tmp: + repo = Path(tmp) + subprocess.run(["git", "init", "-q"], cwd=repo, check=True) + subprocess.run(["git", "config", "user.name", "GALAXY Test"], cwd=repo, check=True) + subprocess.run( + ["git", "config", "user.email", "galaxy-test@example.invalid"], + cwd=repo, + check=True, + ) + source = repo / "source.rs" + source.write_text("original\n") + subprocess.run(["git", "add", "source.rs"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "A"], cwd=repo, check=True) + revision_a = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=repo, text=True + ).strip() + + source.write_text("replacement\n") + subprocess.run(["git", "add", "source.rs"], cwd=repo, check=True) + subprocess.run(["git", "commit", "-qm", "B"], cwd=repo, check=True) + revision_b = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=repo, text=True + ).strip() + + subprocess.run(["git", "checkout", "--detach", "-q", revision_a], cwd=repo, check=True) + subprocess.run(["git", "replace", revision_a, revision_b], cwd=repo, check=True) + subprocess.run(["git", "reset", "--hard", "-q", "HEAD"], cwd=repo, check=True) + self.assertEqual(source.read_text(), "replacement\n") + self.assertEqual(sweep.git_revision(repo), revision_a) + with self.assertRaisesRegex(sweep.SweepError, "differs from HEAD|raw bytes"): + sweep.require_clean_source_tree(repo) + def test_cargo_config_context_detects_mid_sweep_change(self): with tempfile.TemporaryDirectory() as tmp: repo = Path(tmp) @@ -323,6 +396,9 @@ def test_build_environment_overrides_are_rejected(self): "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS", "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER", "CARGO_PROFILE_RELEASE_LTO", + "CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS", + "CARGO_PROFILE_RELEASE_OVERFLOW_CHECKS", + "CARGO_PROFILE_RELEASE_BUILD_OVERRIDE_OPT_LEVEL", ): with self.subTest(name=name), mock.patch.dict( os.environ, @@ -618,6 +694,35 @@ def test_collision_dry_run_rejects_fewer_than_four_particles(self): self.assertEqual(result.returncode, 1) self.assertIn("at least 4", result.stderr) + def test_dry_run_normalizes_output_and_cargo_paths(self): + import sys + with tempfile.TemporaryDirectory() as tmp: + fake_home = Path(tmp) / "home" + fake_home.mkdir() + env = dict(os.environ) + env["HOME"] = str(fake_home) + result = subprocess.run( + [ + sys.executable, + "-I", + str(MODULE_PATH), + "--output", + "~/evidence", + "--particles", + "512", + "--dry-run", + ], + capture_output=True, + text=True, + env=env, + ) + self.assertEqual(result.returncode, 0, result.stderr) + command = __import__("shlex").split(result.stdout.strip()) + self.assertTrue(Path(command[0]).is_absolute()) + expected = str((fake_home / "evidence" / "n000512" / "receipt.json").resolve()) + self.assertIn(expected, command) + self.assertNotIn("~/evidence", result.stdout) + def test_hardware_receipt_at_oracle_size_is_accepted(self): summary = sweep.validate_receipt(receipt_for(4096), **validation_kwargs(4096)) self.assertEqual(summary["particles"], 4096) @@ -743,6 +848,13 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): with self.assertRaisesRegex(sweep.SweepError, "internal cell"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = 2 + receipt["tree"]["leaf_count"] = 1 + receipt["tree"]["max_depth"] = 16 + with self.assertRaisesRegex(sweep.SweepError, "too small"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_executed_oracle_payloads_are_required(self): receipt = receipt_for(512) receipt["gpu_oracles"] = {"status": "executed"} From e4105e7bde03e6747cae35619004b9722cc1424e Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:12:09 +0930 Subject: [PATCH 27/82] Test 4x N-body refresh-rate invariance --- tests/nbody-view.mjs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/nbody-view.mjs b/tests/nbody-view.mjs index e52e5b6..e8ebe85 100644 --- a/tests/nbody-view.mjs +++ b/tests/nbody-view.mjs @@ -36,6 +36,17 @@ function boot(reduced = false) { bodies:()=>JSON.parse(JSON.stringify(bodies)), click:id=>elements.get(id).emit('click'), set(id,value,event='input'){elements.get(id).value=String(value);elements.get(id).emit(event);} }; } +function clockSteps(hz, speed) { + const ctx = vm.createContext({}); + vm.runInContext(read('nbody-clock.js'), ctx); + const clock = new ctx.GalaxyNBodyClock(); + let total = 0; + for (let i = 0; i <= hz; i++) total += clock.advance(i * 1000 / hz, true, speed); + return total; +} +assert.equal(clockSteps(50, 4), 240); +assert.equal(clockSteps(100, 4), 240); + // Same simulated wall time on 60 and 144 Hz displays gives the same trajectory. const a=boot(), b=boot(); a.set('count',128,'change'); b.set('count',128,'change'); @@ -69,4 +80,4 @@ for(const page of ['index.html','rotation-lab.html','barnes-hut.html']) { if(!asset.startsWith('http'))assert.ok(fs.existsSync(new URL('../'+asset,import.meta.url)),asset); } } -console.log('PASS: N-body refresh-rate invariance, pause/step, view isolation, audits, reduced motion, hidden tabs, presets and offline links.'); +console.log('PASS: N-body refresh-rate invariance through 4x speed, pause/step, view isolation, audits, reduced motion, hidden tabs, presets and offline links.'); From f24b5fd38a98d11a6716dbc3c38b6ab033a4725d Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:12:33 +0930 Subject: [PATCH 28/82] Keep dry-run path regression toolchain-neutral --- tests/test_bh2d_hardware_sweep.py | 36 +++++++++++++------------------ 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 0783063..b0b74b8 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -696,30 +696,24 @@ def test_collision_dry_run_rejects_fewer_than_four_particles(self): def test_dry_run_normalizes_output_and_cargo_paths(self): import sys - with tempfile.TemporaryDirectory() as tmp: - fake_home = Path(tmp) / "home" - fake_home.mkdir() - env = dict(os.environ) - env["HOME"] = str(fake_home) - result = subprocess.run( - [ - sys.executable, - "-I", - str(MODULE_PATH), - "--output", - "~/evidence", - "--particles", - "512", - "--dry-run", - ], - capture_output=True, - text=True, - env=env, - ) + result = subprocess.run( + [ + sys.executable, + "-I", + str(MODULE_PATH), + "--output", + "~/evidence", + "--particles", + "512", + "--dry-run", + ], + capture_output=True, + text=True, + ) self.assertEqual(result.returncode, 0, result.stderr) command = __import__("shlex").split(result.stdout.strip()) self.assertTrue(Path(command[0]).is_absolute()) - expected = str((fake_home / "evidence" / "n000512" / "receipt.json").resolve()) + expected = str((Path.home() / "evidence" / "n000512" / "receipt.json").resolve()) self.assertIn(expected, command) self.assertNotIn("~/evidence", result.stdout) From 5a7a0dc7f120168f57c0f1b55b58f066863115c6 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:53:54 +0930 Subject: [PATCH 29/82] Validate BH2D leaf capacity evidence --- scripts/bench-bh2d-hardware.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index e6ebffa..2c05a24 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -905,6 +905,12 @@ def validate_receipt( active_cell_count >= max_depth + 1, "receipt.tree.active_cell_count is too small for the reported maximum depth", ) + if max_depth < TREE_LEVELS - 1: + minimum_leaf_count = (particles + TREE_BUCKET_SIZE - 1) // TREE_BUCKET_SIZE + require( + leaf_count >= minimum_leaf_count, + "receipt.tree.leaf_count is too small for the frozen bucket size below maximum depth", + ) if particles > TREE_BUCKET_SIZE: require( active_cell_count > leaf_count, From 3c4339f4d482e3f2c815cb12abd8bf6b27679396 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:53:57 +0930 Subject: [PATCH 30/82] Cover BH2D undersized leaf evidence --- tests/test_bh2d_hardware_sweep.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index b0b74b8..4dfa7e1 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -849,6 +849,13 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): with self.assertRaisesRegex(sweep.SweepError, "too small"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = 2 + receipt["tree"]["leaf_count"] = 1 + receipt["tree"]["max_depth"] = 1 + with self.assertRaisesRegex(sweep.SweepError, "bucket size"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_executed_oracle_payloads_are_required(self): receipt = receipt_for(512) receipt["gpu_oracles"] = {"status": "executed"} From ad3a8e78b04fbca98df08e89210ca63ce3667ad1 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:54:00 +0930 Subject: [PATCH 31/82] Document bounded N-body catch-up accurately --- docs/BARNES-HUT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/BARNES-HUT.md b/docs/BARNES-HUT.md index 9944578..9928dd1 100644 --- a/docs/BARNES-HUT.md +++ b/docs/BARNES-HUT.md @@ -55,7 +55,7 @@ A future GPU implementation should preserve this CPU/direct reference as its cor ## Visualization -`index.html` is the default lightweight N-body observatory. It reuses the verified browser force solver with 768 resident bodies by default (128–2,048 selectable), fixed-rate leapfrog scheduling, seeded presets, optional trails and luminous sprites. Rendering contributes no additional gravitational mass. Orbit/zoom controls project the planar dynamics without modifying them. The scheduler caps catch-up at four steps per frame, discards overload debt, and suspends in hidden tabs; under load simulated time advances more slowly. Force probes pause the simulation so their result remains attached to the displayed state. +`index.html` is the default lightweight N-body observatory. It reuses the verified browser force solver with 768 resident bodies by default (128–2,048 selectable), fixed-rate leapfrog scheduling, seeded presets, optional trails and luminous sprites. Rendering contributes no additional gravitational mass. Orbit/zoom controls project the planar dynamics without modifying them. At 4× speed on an ordinary 60 Hz display, each rendered frame advances four physics steps. After a render gap the scheduler may batch up to 24 steps, derived from the 100 ms elapsed-time cap, then discards any remaining overload debt; hidden tabs suspend physics and under sustained load simulated time advances more slowly. Force probes pause the simulation so their result remains attached to the displayed state. `barnes-hut.html` is an offline browser laboratory. It renders the resident bodies and can overlay quadtree cells while the system evolves. Controls expose: From e3236edbf4f41b37202afa62aeb881d7d42ea75b Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:58:43 +0930 Subject: [PATCH 32/82] Fix BH2D synthetic receipt fixture --- tests/test_bh2d_hardware_sweep.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 4dfa7e1..5b54a6f 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -38,6 +38,13 @@ def receipt_for( warmup: int = 2, repeats: int = 7, ): + # Synthetic tree statistics scale with the workload so the shared fixture + # remains structurally valid as validator invariants tighten. These are + # test-only values, not measured GPU topology evidence. + leaf_count = particles + active_cell_count = 2 * leaf_count - 1 + max_depth = (particles - 1).bit_length() + oracle_status = "executed" if particles <= oracle_limit else "skipped-particle-limit" if oracle_status == "executed": oracles = { @@ -114,9 +121,9 @@ def receipt_for( "final_checksum_fnv_mix64": "2222222222222222", "repeat_checksum_fnv_mix64": "2222222222222222", "repeat_rebuild_matches": True, - "active_cell_count": 17, - "leaf_count": 8, - "max_depth": 6, + "active_cell_count": active_cell_count, + "leaf_count": leaf_count, + "max_depth": max_depth, }, "final_force": { "bh2a_flat_status": oracle_status, @@ -717,6 +724,15 @@ def test_dry_run_normalizes_output_and_cargo_paths(self): self.assertIn(expected, command) self.assertNotIn("~/evidence", result.stdout) + def test_shared_receipt_fixture_is_valid_across_sweep_sizes(self): + for particles in (512, 4096, 8192, 65536): + with self.subTest(particles=particles): + summary = sweep.validate_receipt( + receipt_for(particles), + **validation_kwargs(particles), + ) + self.assertEqual(summary["particles"], particles) + def test_hardware_receipt_at_oracle_size_is_accepted(self): summary = sweep.validate_receipt(receipt_for(4096), **validation_kwargs(4096)) self.assertEqual(summary["particles"], 4096) @@ -839,7 +855,7 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): receipt["tree"]["active_cell_count"] = 1 receipt["tree"]["leaf_count"] = 1 receipt["tree"]["max_depth"] = 0 - with self.assertRaisesRegex(sweep.SweepError, "internal cell"): + with self.assertRaisesRegex(sweep.SweepError, "bucket size"): sweep.validate_receipt(receipt, **validation_kwargs(512)) receipt = receipt_for(512) From 035f072be0bfcd2a4b905f49e8eeb989cb1335fd Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 19:40:28 +0930 Subject: [PATCH 33/82] Tighten BH2D evidence consistency checks --- scripts/bench-bh2d-hardware.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 2c05a24..8cd538e 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -58,6 +58,8 @@ "RUSTC", "RUSTC_WRAPPER", "RUSTC_WORKSPACE_WRAPPER", + "CARGO_BUILD_RUSTC_WRAPPER", + "CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "RUSTC_BOOTSTRAP", "RUSTUP_TOOLCHAIN", "CARGO_BUILD_TARGET", @@ -779,6 +781,7 @@ def validate_force_errors(value: Any, name: str) -> tuple[float, float]: f"{name}.force_max_relative", nonnegative=True, ) + require(rms <= maximum, f"{name} force RMS cannot exceed force maximum") require(rms < 0.04, f"{name} force RMS gate failed") require(maximum < 0.30, f"{name} force max gate failed") return rms, maximum @@ -893,6 +896,11 @@ def validate_receipt( leaf_count <= active_cell_count, "receipt.tree.leaf_count cannot exceed active_cell_count", ) + internal_cell_count = active_cell_count - leaf_count + require( + leaf_count <= 3 * internal_cell_count + 1, + "receipt.tree.leaf_count exceeds quadtree fan-out capacity", + ) require( leaf_count <= particles, "receipt.tree.leaf_count cannot exceed the resident particle count", @@ -938,6 +946,7 @@ def validate_receipt( "receipt.final_force.direct_probe_max_relative", nonnegative=True, ) + require(direct_rms <= direct_max, "direct-force RMS cannot exceed maximum") require(direct_rms < 0.04, "direct-force RMS gate failed") require(direct_max < 0.30, "direct-force max gate failed") @@ -957,6 +966,10 @@ def validate_receipt( "receipt.final_force.gpu_vs_bh2a_flat_max_relative", nonnegative=True, ) + require( + flat_rms <= flat_max, + "BH #2A final-force RMS cannot exceed maximum", + ) require(flat_rms < 0.04, "BH #2A final-force RMS gate failed") require(flat_max < 0.30, "BH #2A final-force max gate failed") From bc981e22af2150dcbb60d303262edbdf6ac2faa7 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 19:40:33 +0930 Subject: [PATCH 34/82] Cover BH2D fanout and force consistency --- tests/test_bh2d_hardware_sweep.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 5b54a6f..6c570b6 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -399,6 +399,8 @@ def test_build_environment_overrides_are_rejected(self): "CARGO_BUILD_RUSTFLAGS", "RUSTC", "RUSTC_WRAPPER", + "CARGO_BUILD_RUSTC_WRAPPER", + "CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "RUSTUP_TOOLCHAIN", "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS", "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER", @@ -872,6 +874,34 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): with self.assertRaisesRegex(sweep.SweepError, "bucket size"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = 145 + receipt["tree"]["leaf_count"] = 129 + receipt["tree"]["max_depth"] = 16 + with self.assertRaisesRegex(sweep.SweepError, "fan-out"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + def test_force_rms_cannot_exceed_reported_maximum(self): + receipt = receipt_for(512) + receipt["final_force"]["direct_probe_rms_relative"] = 0.03 + receipt["final_force"]["direct_probe_max_relative"] = 0.001 + with self.assertRaisesRegex(sweep.SweepError, "direct-force RMS cannot exceed maximum"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + receipt = receipt_for(512) + receipt["final_force"]["gpu_vs_bh2a_flat_rms_relative"] = 0.03 + receipt["final_force"]["gpu_vs_bh2a_flat_max_relative"] = 0.001 + with self.assertRaisesRegex(sweep.SweepError, "BH #2A final-force RMS cannot exceed maximum"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + for key in ("bh2c_serial_gpu", "bh2b2_host_tree_gpu"): + with self.subTest(key=key): + receipt = receipt_for(512) + receipt["gpu_oracles"][key]["force_rms_relative"] = 0.03 + receipt["gpu_oracles"][key]["force_max_relative"] = 0.001 + with self.assertRaisesRegex(sweep.SweepError, "force RMS cannot exceed force maximum"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_executed_oracle_payloads_are_required(self): receipt = receipt_for(512) receipt["gpu_oracles"] = {"status": "executed"} From 52d743186259b6793a413bf0337780f54c312f68 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 19:40:38 +0930 Subject: [PATCH 35/82] Count N-body root in level readout --- nbody-viz.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nbody-viz.js b/nbody-viz.js index 2d17a76..5eb7d27 100644 --- a/nbody-viz.js +++ b/nbody-viz.js @@ -152,7 +152,7 @@ const exactTerms = n * (n - 1); const reduction = exactTerms ? Math.max(0, 1 - terms / exactTerms) : 0; byId("nodeReadout").textContent = formatInt(state.latest.tree.nodeCount); - byId("depthReadout").textContent = state.latest.tree.maxDepth + " levels · " + formatInt(state.latest.tree.leafCount) + " leaves"; + byId("depthReadout").textContent = (state.latest.tree.maxDepth + 1) + " levels · " + formatInt(state.latest.tree.leafCount) + " leaves"; byId("termReadout").textContent = formatInt(terms); byId("reductionReadout").textContent = (reduction * 100).toFixed(1) + "% fewer force terms than direct"; } From fb132ad159ad71e1ec51228228f2e166f76ab7e6 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 19:40:49 +0930 Subject: [PATCH 36/82] Test N-body level readout --- tests/nbody-view.mjs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/nbody-view.mjs b/tests/nbody-view.mjs index e8ebe85..b157d4f 100644 --- a/tests/nbody-view.mjs +++ b/tests/nbody-view.mjs @@ -14,7 +14,7 @@ class Element { setPointerCapture() {} } function boot(reduced = false) { - const elements = new Map(); let scheduled, bodies, steps = 0, draws = 0; + const elements = new Map(); let scheduled, bodies, latest, steps = 0, draws = 0; for (const m of read('index.html').matchAll(/<(\w+)\b[^>]*\bid="([^"]+)"[^>]*>/g)) { const el = new Element(m[1]); el.value = m[0].match(/value="([^"]*)"/)?.[1] || ''; el.checked = m[0].includes(' checked'); elements.set(m[2], el); @@ -28,12 +28,12 @@ function boot(reduced = false) { requestAnimationFrame(fn){scheduled=fn;}, devicePixelRatio:1, addEventListener(){}, console}); vm.runInContext(read('barnes-hut.js'),ctx); const solver = ctx.GalaxyBarnesHut; - ctx.GalaxyBarnesHut = {...solver, accelerations(b,o){bodies=b;return solver.accelerations(b,o);}, - stepLeapfrog(b,dt,o){steps++; bodies=b;return solver.stepLeapfrog(b,dt,o);} }; + ctx.GalaxyBarnesHut = {...solver, accelerations(b,o){bodies=b;latest=solver.accelerations(b,o);return latest;}, + stepLeapfrog(b,dt,o){steps++; bodies=b;latest=solver.stepLeapfrog(b,dt,o);return latest;} }; vm.runInContext(read('nbody-clock.js'),ctx); vm.runInContext(read('nbody-viz.js'),ctx); return {elements, doc, motion, frame:now=>scheduled(now), steps:()=>steps, draws:()=>draws, - bodies:()=>JSON.parse(JSON.stringify(bodies)), click:id=>elements.get(id).emit('click'), + bodies:()=>JSON.parse(JSON.stringify(bodies)), latest:()=>latest, click:id=>elements.get(id).emit('click'), set(id,value,event='input'){elements.get(id).value=String(value);elements.get(id).emit(event);} }; } function clockSteps(hz, speed) { @@ -49,6 +49,10 @@ assert.equal(clockSteps(100, 4), 240); // Same simulated wall time on 60 and 144 Hz displays gives the same trajectory. const a=boot(), b=boot(); +assert.equal( + a.elements.get('depthReadout').textContent, + (a.latest().tree.maxDepth + 1) + " levels · " + a.latest().tree.leafCount.toLocaleString("en-US") + " leaves" +); a.set('count',128,'change'); b.set('count',128,'change'); for(let i=0;i<=60;i++)a.frame(i*1000/60); for(let i=0;i<=144;i++)b.frame(i*1000/144); From 0413c4f4080fda561c8a02246aac9e1b77d59a9c Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 19:42:13 +0930 Subject: [PATCH 37/82] Keep topology negative tests independent --- tests/test_bh2d_hardware_sweep.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 6c570b6..da5b55f 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -843,8 +843,9 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): sweep.validate_receipt(receipt, **validation_kwargs(512)) receipt = receipt_for(512) - receipt["tree"]["active_cell_count"] = 600 + receipt["tree"]["active_cell_count"] = 1201 receipt["tree"]["leaf_count"] = 600 + receipt["tree"]["max_depth"] = 16 with self.assertRaisesRegex(sweep.SweepError, "resident particle count"): sweep.validate_receipt(receipt, **validation_kwargs(512)) From 9178a6d256771ea919c915d570af03919114651c Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 20:58:12 +0930 Subject: [PATCH 38/82] Harden BH2D adapter and subprocess evidence checks --- scripts/bench-bh2d-hardware.py | 51 ++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 8cd538e..79df2e6 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -50,7 +50,7 @@ RADIX_DIGITS = 16 SYSTEM_PATH = "/usr/bin:/bin" BUILD_ENV_EXACT = { - "LD_PRELOAD", "LD_LIBRARY_PATH", "LIBRARY_PATH", "COMPILER_PATH", + "LD_PRELOAD", "LD_AUDIT", "LD_LIBRARY_PATH", "LIBRARY_PATH", "COMPILER_PATH", "GCC_EXEC_PREFIX", "CPATH", "C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH", "RUSTFLAGS", "CARGO_ENCODED_RUSTFLAGS", @@ -277,7 +277,13 @@ def toolchain_context(cargo_command: str, repo_root: Path) -> dict[str, Any]: def build_environment(context: dict[str, Any]) -> dict[str, str]: - env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")} + env = { + key: value + for key, value in os.environ.items() + if not key.startswith("GIT_") + and key not in BUILD_ENV_EXACT + and not any(pattern.fullmatch(key) for pattern in BUILD_ENV_PATTERNS) + } env["PATH"] = SYSTEM_PATH env["RUSTC"] = context["rustc"]["executable"] return env @@ -769,7 +775,11 @@ def validate_state_error(value: Any, name: str) -> dict[str, float]: return result -def validate_force_errors(value: Any, name: str) -> tuple[float, float]: +def validate_force_errors( + value: Any, + name: str, + sample_count: int, +) -> tuple[float, float]: evidence = require_object(value, name) rms = require_number( evidence.get("force_rms_relative"), @@ -782,14 +792,25 @@ def validate_force_errors(value: Any, name: str) -> tuple[float, float]: nonnegative=True, ) require(rms <= maximum, f"{name} force RMS cannot exceed force maximum") + require( + maximum <= rms * math.sqrt(sample_count), + f"{name} force maximum is too large for RMS and sample count", + ) require(rms < 0.04, f"{name} force RMS gate failed") require(maximum < 0.30, f"{name} force max gate failed") return rms, maximum -def adapter_identity(gpu: Any) -> str: +def adapter_identity(gpu: Any, adapter_selector: str | None = None) -> str: info = require_object(gpu, "receipt.gpu") + index = require_int(info.get("index"), "receipt.gpu.index", minimum=0) + if adapter_selector is not None and re.fullmatch(r"[0-9]+", adapter_selector): + require( + index == int(adapter_selector), + "receipt.gpu.index does not match the numeric adapter selector", + ) required = { + "index": index, "name": require_string(info.get("name"), "receipt.gpu.name", nonempty=True), "backend": require_string(info.get("backend"), "receipt.gpu.backend", nonempty=True), "device_type": require_string( @@ -820,6 +841,7 @@ def validate_receipt( oracle_limit: int, benchmark_warmup: int, benchmark_repeats: int, + adapter_selector: str | None = None, ) -> dict[str, Any]: root = require_object(receipt, "receipt") require(root.get("schema") == RECEIPT_SCHEMA, "unexpected BH #2D receipt schema") @@ -860,7 +882,7 @@ def validate_receipt( require(root.get("force_solves") == steps + 1, "unexpected force-solve count") require(root.get("evolution_tree_builds") == steps + 1, "unexpected tree-build count") - identity = adapter_identity(root.get("gpu")) + identity = adapter_identity(root.get("gpu"), adapter_selector) tree = require_object(root.get("tree"), "receipt.tree") require(tree.get("repeat_rebuild_matches") is True, "same-state repeat tree checksum changed") @@ -913,6 +935,10 @@ def validate_receipt( active_cell_count >= max_depth + 1, "receipt.tree.active_cell_count is too small for the reported maximum depth", ) + require( + internal_cell_count >= max_depth, + "receipt.tree has too few internal cells for the reported maximum depth", + ) if max_depth < TREE_LEVELS - 1: minimum_leaf_count = (particles + TREE_BUCKET_SIZE - 1) // TREE_BUCKET_SIZE require( @@ -947,6 +973,10 @@ def validate_receipt( nonnegative=True, ) require(direct_rms <= direct_max, "direct-force RMS cannot exceed maximum") + require( + direct_max <= direct_rms * math.sqrt(expected_probe_count), + "direct-force maximum is too large for RMS and probe count", + ) require(direct_rms < 0.04, "direct-force RMS gate failed") require(direct_max < 0.30, "direct-force max gate failed") @@ -970,6 +1000,10 @@ def validate_receipt( flat_rms <= flat_max, "BH #2A final-force RMS cannot exceed maximum", ) + require( + flat_max <= flat_rms * math.sqrt(particles), + "BH #2A final-force maximum is too large for RMS and particle count", + ) require(flat_rms < 0.04, "BH #2A final-force RMS gate failed") require(flat_max < 0.30, "BH #2A final-force max gate failed") @@ -985,7 +1019,7 @@ def validate_receipt( ): oracle = require_object(oracles.get(key), label) validate_state_error(oracle.get("state_error"), f"{label}.state_error") - validate_force_errors(oracle, label) + validate_force_errors(oracle, label, particles) else: require( require_int(oracles.get("limit"), "receipt.gpu_oracles.limit") == oracle_limit, @@ -1197,7 +1231,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument( "--dry-run", action="store_true", - help="print the exact hardware commands without creating files or running cargo", + help="print planned hardware commands without creating files or running Cargo", ) return parser.parse_args() @@ -1231,7 +1265,7 @@ def main() -> int: output = args.output.expanduser().resolve() if args.dry_run: plan_args = argparse.Namespace(**vars(args)) - plan_args.cargo = tool_record(args.cargo, "cargo", repo_root)["executable"] + plan_args.cargo = str(resolve_executable(args.cargo, "cargo").absolute()) for particles in particles_list: run_dir = output / f"n{particles:06d}" receipt = run_dir / "receipt.json" @@ -1363,6 +1397,7 @@ def main() -> int: oracle_limit=args.oracle_limit, benchmark_warmup=args.benchmark_warmup, benchmark_repeats=args.benchmark_repeats, + adapter_selector=args.adapter, ) if adapter is None: adapter = summary["adapter_identity"] From 6ca1066ba2b036b257d1ea1ab50a342092866b80 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 20:58:16 +0930 Subject: [PATCH 39/82] Cover BH2D adapter and evidence edge cases --- tests/test_bh2d_hardware_sweep.py | 77 +++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index da5b55f..8a346ab 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -104,6 +104,7 @@ def receipt_for( "theta": theta, "softening_kpc": softening_kpc, "gpu": { + "index": 0, "name": "Synthetic GPU", "backend": "Vulkan", "device_type": "DiscreteGpu", @@ -160,6 +161,7 @@ def validation_kwargs(particles: int, **overrides): "oracle_limit": 4096, "benchmark_warmup": 2, "benchmark_repeats": 7, + "adapter_selector": None, } values.update(overrides) return values @@ -395,6 +397,7 @@ def test_cargo_config_context_detects_mid_sweep_change(self): def test_build_environment_overrides_are_rejected(self): for name in ( + "LD_AUDIT", "RUSTFLAGS", "CARGO_BUILD_RUSTFLAGS", "RUSTC", @@ -553,6 +556,8 @@ def test_system_build_path_and_explicit_rustc(self): self.assertEqual(env["PATH"], "/usr/bin:/bin") self.assertEqual(env["RUSTC"], "/selected/bin/rustc") self.assertNotIn("GIT_WORK_TREE", env) + self.assertNotIn("LD_AUDIT", env) + self.assertNotIn("LD_PRELOAD", env) def test_git_invocation_binds_checkout_and_clears_selectors(self): with mock.patch.dict(os.environ, {"GIT_WORK_TREE": "/other", "GIT_DIR": "/other/.git", @@ -703,6 +708,36 @@ def test_collision_dry_run_rejects_fewer_than_four_particles(self): self.assertEqual(result.returncode, 1) self.assertIn("at least 4", result.stderr) + def test_dry_run_does_not_execute_cargo(self): + import sys + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + marker = root / "executed" + fake_cargo = root / "cargo" + fake_cargo.write_text( + "#!/bin/sh\nprintf '%s\\n' \"$*\" >> " + repr(str(marker)) + "\n" + ) + fake_cargo.chmod(0o755) + result = subprocess.run( + [ + sys.executable, + "-I", + str(MODULE_PATH), + "--output", + str(root / "plan"), + "--particles", + "512", + "--cargo", + str(fake_cargo), + "--dry-run", + ], + capture_output=True, + text=True, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(marker.exists()) + self.assertTrue(result.stdout.startswith(str(fake_cargo))) + def test_dry_run_normalizes_output_and_cargo_paths(self): import sys result = subprocess.run( @@ -776,6 +811,20 @@ def test_receipt_must_bind_the_full_requested_workload(self): with self.assertRaisesRegex(sweep.SweepError, "repeat count"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_adapter_index_is_bound_into_identity_and_numeric_selector(self): + receipt0 = receipt_for(512) + receipt1 = receipt_for(512) + receipt1["gpu"]["index"] = 1 + summary0 = sweep.validate_receipt(receipt0, **validation_kwargs(512)) + summary1 = sweep.validate_receipt(receipt1, **validation_kwargs(512)) + self.assertNotEqual(summary0["adapter_identity"], summary1["adapter_identity"]) + + with self.assertRaisesRegex(sweep.SweepError, "numeric adapter selector"): + sweep.validate_receipt( + receipt1, + **validation_kwargs(512, adapter_selector="0"), + ) + def test_adapter_identity_is_mandatory_and_hardware_bound(self): for gpu in ( {}, @@ -882,6 +931,13 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): with self.assertRaisesRegex(sweep.SweepError, "fan-out"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = 17 + receipt["tree"]["leaf_count"] = 12 + receipt["tree"]["max_depth"] = 16 + with self.assertRaisesRegex(sweep.SweepError, "internal cells"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_force_rms_cannot_exceed_reported_maximum(self): receipt = receipt_for(512) receipt["final_force"]["direct_probe_rms_relative"] = 0.03 @@ -903,6 +959,27 @@ def test_force_rms_cannot_exceed_reported_maximum(self): with self.assertRaisesRegex(sweep.SweepError, "force RMS cannot exceed force maximum"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_force_maximum_has_lower_rms_bound(self): + receipt = receipt_for(512) + receipt["final_force"]["direct_probe_rms_relative"] = 0.0 + receipt["final_force"]["direct_probe_max_relative"] = 0.29 + with self.assertRaisesRegex(sweep.SweepError, "probe count"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + receipt = receipt_for(512) + receipt["final_force"]["gpu_vs_bh2a_flat_rms_relative"] = 0.0 + receipt["final_force"]["gpu_vs_bh2a_flat_max_relative"] = 0.29 + with self.assertRaisesRegex(sweep.SweepError, "particle count"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + for key in ("bh2c_serial_gpu", "bh2b2_host_tree_gpu"): + with self.subTest(key=key): + receipt = receipt_for(512) + receipt["gpu_oracles"][key]["force_rms_relative"] = 0.0 + receipt["gpu_oracles"][key]["force_max_relative"] = 0.29 + with self.assertRaisesRegex(sweep.SweepError, "sample count"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_executed_oracle_payloads_are_required(self): receipt = receipt_for(512) receipt["gpu_oracles"] = {"status": "executed"} From 6f396bc4fa5fda1e9166dd5b9a1060c387341681 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 20:58:20 +0930 Subject: [PATCH 40/82] Reset N-body FPS accounting on visibility changes --- nbody-viz.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nbody-viz.js b/nbody-viz.js index 5eb7d27..b1fe677 100644 --- a/nbody-viz.js +++ b/nbody-viz.js @@ -246,7 +246,11 @@ e.preventDefault(); state.zoom = Math.max(0.3, Math.min(3, state.zoom * Math.exp(-e.deltaY * 0.001))); byId("zoom").value = state.zoom; syncControlLabels(); draw(); }, { passive: false }); - document.addEventListener("visibilitychange", () => { clock.reset(); }); + document.addEventListener("visibilitychange", () => { + clock.reset(); + state.frames = 0; + state.fpsStart = performance.now(); + }); motion.addEventListener("change", e => { if (e.matches) { state.running = false; state.trails = false; clock.reset(); history.length = 0; syncControlLabels(); updateReadouts(); draw(); } }); From 8ebc548739d36ccf870109cbfa9e6eea55017a32 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 20:58:23 +0930 Subject: [PATCH 41/82] Test FPS reset after hidden tabs --- tests/nbody-view.mjs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/nbody-view.mjs b/tests/nbody-view.mjs index b157d4f..7d2b91d 100644 --- a/tests/nbody-view.mjs +++ b/tests/nbody-view.mjs @@ -14,7 +14,7 @@ class Element { setPointerCapture() {} } function boot(reduced = false) { - const elements = new Map(); let scheduled, bodies, latest, steps = 0, draws = 0; + const elements = new Map(); let scheduled, bodies, latest, steps = 0, draws = 0, currentNow = 0; for (const m of read('index.html').matchAll(/<(\w+)\b[^>]*\bid="([^"]+)"[^>]*>/g)) { const el = new Element(m[1]); el.value = m[0].match(/value="([^"]*)"/)?.[1] || ''; el.checked = m[0].includes(' checked'); elements.set(m[2], el); @@ -24,7 +24,7 @@ function boot(reduced = false) { const doc = new Element(); doc.hidden = false; doc.getElementById = id => elements.get(id); doc.createElement = () => ({getContext:()=>paint}); const motion = new Element(); motion.matches = reduced; - const ctx = vm.createContext({document:doc, performance:{now:()=>0}, matchMedia:()=>motion, + const ctx = vm.createContext({document:doc, performance:{now:()=>currentNow}, matchMedia:()=>motion, requestAnimationFrame(fn){scheduled=fn;}, devicePixelRatio:1, addEventListener(){}, console}); vm.runInContext(read('barnes-hut.js'),ctx); const solver = ctx.GalaxyBarnesHut; @@ -32,7 +32,7 @@ function boot(reduced = false) { stepLeapfrog(b,dt,o){steps++; bodies=b;latest=solver.stepLeapfrog(b,dt,o);return latest;} }; vm.runInContext(read('nbody-clock.js'),ctx); vm.runInContext(read('nbody-viz.js'),ctx); - return {elements, doc, motion, frame:now=>scheduled(now), steps:()=>steps, draws:()=>draws, + return {elements, doc, motion, frame(now){currentNow=now;scheduled(now);}, steps:()=>steps, draws:()=>draws, bodies:()=>JSON.parse(JSON.stringify(bodies)), latest:()=>latest, click:id=>elements.get(id).emit('click'), set(id,value,event='input'){elements.get(id).value=String(value);elements.get(id).emit(event);} }; } @@ -66,8 +66,10 @@ a.click('audit');assert.match(a.elements.get('auditStatus').textContent,/Direct a.frame(3000); assert.notEqual(a.elements.get('errorReadout').textContent,'—'); a.click('playToggle');a.frame(4000);a.frame(4020); assert.equal(a.elements.get('errorReadout').textContent,'—','advancing invalidates audit'); -a.doc.hidden=true; const hidden=a.steps();a.frame(100000); assert.equal(a.steps(),hidden); -a.doc.hidden=false;a.frame(200000);assert.equal(a.steps(),hidden,'hidden tab does not catch up'); +a.doc.hidden=true; a.doc.emit('visibilitychange'); const hidden=a.steps();a.frame(100000); assert.equal(a.steps(),hidden); +a.doc.hidden=false; a.doc.emit('visibilitychange');a.frame(100000); +assert.notEqual(a.elements.get('fpsReadout').textContent,'0 FPS','visibility resume must not include hidden time in FPS'); +a.frame(200000);assert.equal(a.steps(),hidden,'hidden tab does not catch up'); a.frame(200017);assert.equal(a.steps(),hidden+1); a.motion.emit('change',{matches:true});assert.equal(a.elements.get('runBadge').textContent,'PAUSED'); assert.equal(a.elements.get('trails').checked,false); From 512602528fe23ce127c806f54a394dc5e539a386 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 20:59:04 +0930 Subject: [PATCH 42/82] Model visibility timing without extra physics step --- tests/nbody-view.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/nbody-view.mjs b/tests/nbody-view.mjs index 7d2b91d..8c06bb3 100644 --- a/tests/nbody-view.mjs +++ b/tests/nbody-view.mjs @@ -32,7 +32,7 @@ function boot(reduced = false) { stepLeapfrog(b,dt,o){steps++; bodies=b;latest=solver.stepLeapfrog(b,dt,o);return latest;} }; vm.runInContext(read('nbody-clock.js'),ctx); vm.runInContext(read('nbody-viz.js'),ctx); - return {elements, doc, motion, frame(now){currentNow=now;scheduled(now);}, steps:()=>steps, draws:()=>draws, + return {elements, doc, motion, frame(now){currentNow=now;scheduled(now);}, setNow(now){currentNow=now;}, steps:()=>steps, draws:()=>draws, bodies:()=>JSON.parse(JSON.stringify(bodies)), latest:()=>latest, click:id=>elements.get(id).emit('click'), set(id,value,event='input'){elements.get(id).value=String(value);elements.get(id).emit(event);} }; } @@ -67,9 +67,9 @@ a.frame(3000); assert.notEqual(a.elements.get('errorReadout').textContent,'—') a.click('playToggle');a.frame(4000);a.frame(4020); assert.equal(a.elements.get('errorReadout').textContent,'—','advancing invalidates audit'); a.doc.hidden=true; a.doc.emit('visibilitychange'); const hidden=a.steps();a.frame(100000); assert.equal(a.steps(),hidden); -a.doc.hidden=false; a.doc.emit('visibilitychange');a.frame(100000); +a.doc.hidden=false; a.setNow(200000); a.doc.emit('visibilitychange');a.frame(200000); +assert.equal(a.steps(),hidden,'hidden tab does not catch up'); assert.notEqual(a.elements.get('fpsReadout').textContent,'0 FPS','visibility resume must not include hidden time in FPS'); -a.frame(200000);assert.equal(a.steps(),hidden,'hidden tab does not catch up'); a.frame(200017);assert.equal(a.steps(),hidden+1); a.motion.emit('change',{matches:true});assert.equal(a.elements.get('runBadge').textContent,'PAUSED'); assert.equal(a.elements.get('trails').checked,false); From 53c30e4dee4cefc30d84eab33d895a0989668c39 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 20:59:07 +0930 Subject: [PATCH 43/82] Exercise loader environment sanitization --- tests/test_bh2d_hardware_sweep.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 8a346ab..634c1c1 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -551,7 +551,15 @@ def test_cli_requires_isolation_before_optional_imports(self): self.assertIn("isolated Python", result.stderr) def test_system_build_path_and_explicit_rustc(self): - with mock.patch.dict(os.environ, {"PATH": "/unrecorded/bin", "GIT_WORK_TREE": "/other"}): + with mock.patch.dict( + os.environ, + { + "PATH": "/unrecorded/bin", + "GIT_WORK_TREE": "/other", + "LD_AUDIT": "/tmp/audit.so", + "LD_PRELOAD": "/tmp/preload.so", + }, + ): env = sweep.build_environment({"rustc": {"executable": "/selected/bin/rustc"}}) self.assertEqual(env["PATH"], "/usr/bin:/bin") self.assertEqual(env["RUSTC"], "/selected/bin/rustc") From ae8d6d3cd41d9e37a2c74068cc2c69c9009742fa Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:00:54 +0930 Subject: [PATCH 44/82] Keep synthetic force summaries mathematically valid --- tests/test_bh2d_hardware_sweep.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 634c1c1..6466e94 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -45,24 +45,30 @@ def receipt_for( active_cell_count = 2 * leaf_count - 1 max_depth = (particles - 1).bit_length() + force_rms = 0.001 + force_max = force_rms * min(particles ** 0.5, 10.0) + probe_count = min(direct_probes, particles) + direct_rms = 0.001 + direct_max = direct_rms * (probe_count ** 0.5) + oracle_status = "executed" if particles <= oracle_limit else "skipped-particle-limit" if oracle_status == "executed": oracles = { "status": "executed", "bh2c_serial_gpu": { "state_error": dict(STATE_ERROR), - "force_rms_relative": 0.001, - "force_max_relative": 0.01, + "force_rms_relative": force_rms, + "force_max_relative": force_max, }, "bh2b2_host_tree_gpu": { "state_error": dict(STATE_ERROR), - "force_rms_relative": 0.001, - "force_max_relative": 0.01, + "force_rms_relative": force_rms, + "force_max_relative": force_max, }, } trajectory = {"status": "executed", "state_error": dict(STATE_ERROR)} - flat_rms = 0.001 - flat_max = 0.01 + flat_rms = force_rms + flat_max = force_max else: oracles = { "status": "skipped-particle-limit", @@ -130,9 +136,9 @@ def receipt_for( "bh2a_flat_status": oracle_status, "gpu_vs_bh2a_flat_rms_relative": flat_rms, "gpu_vs_bh2a_flat_max_relative": flat_max, - "direct_probe_count": min(direct_probes, particles), - "direct_probe_rms_relative": 0.001, - "direct_probe_max_relative": 0.01, + "direct_probe_count": probe_count, + "direct_probe_rms_relative": direct_rms, + "direct_probe_max_relative": direct_max, }, "trajectory_vs_bh2a_flat_f64": trajectory, "gpu_oracles": oracles, From 6da5fc3060a537dc5b078cf580c2e8fa106d059e Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:11:52 +0930 Subject: [PATCH 45/82] Reject contradictory BH2D state-error summaries --- scripts/bench-bh2d-hardware.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 79df2e6..a9ee960 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -768,6 +768,16 @@ def validate_state_error(value: Any, name: str) -> dict[str, float]: nonnegative=True, ), } + require( + (result["position_rms_relative_l2"] == 0.0) + == (result["position_max_relative"] == 0.0), + f"{name} position RMS/max zero-state mismatch", + ) + require( + (result["velocity_rms_relative_l2"] == 0.0) + == (result["velocity_max_relative"] == 0.0), + f"{name} velocity RMS/max zero-state mismatch", + ) require(result["position_rms_relative_l2"] < 0.03, f"{name} position RMS gate failed") require(result["position_max_relative"] < 0.30, f"{name} position max gate failed") require(result["velocity_rms_relative_l2"] < 0.03, f"{name} velocity RMS gate failed") From 00de9c945f72cab1c40328e964f1671297a7158d Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:11:57 +0930 Subject: [PATCH 46/82] Cover contradictory state-error summaries --- tests/test_bh2d_hardware_sweep.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 6466e94..e30a669 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -952,6 +952,31 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): with self.assertRaisesRegex(sweep.SweepError, "internal cells"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_state_error_zero_maximum_matches_rms(self): + for key in ("bh2c_serial_gpu", "bh2b2_host_tree_gpu"): + with self.subTest(key=key, component="position"): + receipt = receipt_for(512) + state = receipt["gpu_oracles"][key]["state_error"] + state["position_rms_relative_l2"] = 0.02 + state["position_max_relative"] = 0.0 + with self.assertRaisesRegex(sweep.SweepError, "position RMS/max zero-state mismatch"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + with self.subTest(key=key, component="velocity"): + receipt = receipt_for(512) + state = receipt["gpu_oracles"][key]["state_error"] + state["velocity_rms_relative_l2"] = 0.02 + state["velocity_max_relative"] = 0.0 + with self.assertRaisesRegex(sweep.SweepError, "velocity RMS/max zero-state mismatch"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + receipt = receipt_for(512) + state = receipt["trajectory_vs_bh2a_flat_f64"]["state_error"] + state["position_rms_relative_l2"] = 0.0 + state["position_max_relative"] = 0.01 + with self.assertRaisesRegex(sweep.SweepError, "position RMS/max zero-state mismatch"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_force_rms_cannot_exceed_reported_maximum(self): receipt = receipt_for(512) receipt["final_force"]["direct_probe_rms_relative"] = 0.03 From b8ccf443bf71bdc4db783d4d16684f36fcd47233 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:01:33 +0930 Subject: [PATCH 47/82] Harden BH2D Vulkan and topology provenance --- scripts/bench-bh2d-hardware.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index a9ee960..4c8a640 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -51,6 +51,7 @@ SYSTEM_PATH = "/usr/bin:/bin" BUILD_ENV_EXACT = { "LD_PRELOAD", "LD_AUDIT", "LD_LIBRARY_PATH", "LIBRARY_PATH", "COMPILER_PATH", + "VK_DRIVER_FILES", "VK_ICD_FILENAMES", "VK_LAYER_PATH", "VK_INSTANCE_LAYERS", "GCC_EXEC_PREFIX", "CPATH", "C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH", "RUSTFLAGS", "CARGO_ENCODED_RUSTFLAGS", @@ -814,14 +815,21 @@ def validate_force_errors( def adapter_identity(gpu: Any, adapter_selector: str | None = None) -> str: info = require_object(gpu, "receipt.gpu") index = require_int(info.get("index"), "receipt.gpu.index", minimum=0) - if adapter_selector is not None and re.fullmatch(r"[0-9]+", adapter_selector): - require( - index == int(adapter_selector), - "receipt.gpu.index does not match the numeric adapter selector", - ) + name = require_string(info.get("name"), "receipt.gpu.name", nonempty=True) + if adapter_selector is not None: + if re.fullmatch(r"[0-9]+", adapter_selector): + require( + index == int(adapter_selector), + "receipt.gpu.index does not match the numeric adapter selector", + ) + else: + require( + adapter_selector.lower() in name.lower(), + "receipt.gpu.name does not match the textual adapter selector", + ) required = { "index": index, - "name": require_string(info.get("name"), "receipt.gpu.name", nonempty=True), + "name": name, "backend": require_string(info.get("backend"), "receipt.gpu.backend", nonempty=True), "device_type": require_string( info.get("device_type"), "receipt.gpu.device_type", nonempty=True @@ -941,6 +949,14 @@ def validate_receipt( max_depth <= TREE_LEVELS - 1, "receipt.tree.max_depth exceeds the frozen Morton depth", ) + topology_capacity = sum( + min(particles, 4 ** depth) + for depth in range(max_depth + 1) + ) + require( + active_cell_count <= topology_capacity, + "receipt.tree.active_cell_count exceeds per-depth quadtree occupancy", + ) require( active_cell_count >= max_depth + 1, "receipt.tree.active_cell_count is too small for the reported maximum depth", From 4f1ba55c7922c0d2b39fb10189629787a17d73a0 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:01:37 +0930 Subject: [PATCH 48/82] Cover Vulkan, adapter, and occupancy edge cases --- tests/test_bh2d_hardware_sweep.py | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index e30a669..0d48d57 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -404,6 +404,10 @@ def test_cargo_config_context_detects_mid_sweep_change(self): def test_build_environment_overrides_are_rejected(self): for name in ( "LD_AUDIT", + "VK_DRIVER_FILES", + "VK_ICD_FILENAMES", + "VK_LAYER_PATH", + "VK_INSTANCE_LAYERS", "RUSTFLAGS", "CARGO_BUILD_RUSTFLAGS", "RUSTC", @@ -564,6 +568,10 @@ def test_system_build_path_and_explicit_rustc(self): "GIT_WORK_TREE": "/other", "LD_AUDIT": "/tmp/audit.so", "LD_PRELOAD": "/tmp/preload.so", + "VK_DRIVER_FILES": "/tmp/icd.json", + "VK_ICD_FILENAMES": "/tmp/legacy-icd.json", + "VK_LAYER_PATH": "/tmp/layers", + "VK_INSTANCE_LAYERS": "VK_LAYER_SYNTHETIC", }, ): env = sweep.build_environment({"rustc": {"executable": "/selected/bin/rustc"}}) @@ -572,6 +580,13 @@ def test_system_build_path_and_explicit_rustc(self): self.assertNotIn("GIT_WORK_TREE", env) self.assertNotIn("LD_AUDIT", env) self.assertNotIn("LD_PRELOAD", env) + for name in ( + "VK_DRIVER_FILES", + "VK_ICD_FILENAMES", + "VK_LAYER_PATH", + "VK_INSTANCE_LAYERS", + ): + self.assertNotIn(name, env) def test_git_invocation_binds_checkout_and_clears_selectors(self): with mock.patch.dict(os.environ, {"GIT_WORK_TREE": "/other", "GIT_DIR": "/other/.git", @@ -839,6 +854,20 @@ def test_adapter_index_is_bound_into_identity_and_numeric_selector(self): **validation_kwargs(512, adapter_selector="0"), ) + receipt = receipt_for(512) + receipt["gpu"]["name"] = "AMD Radeon RX 7900 XTX" + with self.assertRaisesRegex(sweep.SweepError, "textual adapter selector"): + sweep.validate_receipt( + receipt, + **validation_kwargs(512, adapter_selector="NVIDIA"), + ) + + summary = sweep.validate_receipt( + receipt, + **validation_kwargs(512, adapter_selector="radeon rx"), + ) + self.assertEqual(summary["particles"], 512) + def test_adapter_identity_is_mandatory_and_hardware_bound(self): for gpu in ( {}, @@ -917,6 +946,13 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): with self.assertRaisesRegex(sweep.SweepError, "frozen Morton depth"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = 6742 + receipt["tree"]["leaf_count"] = 512 + receipt["tree"]["max_depth"] = 16 + with self.assertRaisesRegex(sweep.SweepError, "per-depth quadtree occupancy"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) receipt["tree"]["active_cell_count"] = 1 receipt["tree"]["leaf_count"] = 1 From fd624a3c0a694e72964e510373b9bf8db515ec8a Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:01:40 +0930 Subject: [PATCH 49/82] Point engine guide at rotation instrument --- docs/ENGINE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ENGINE.md b/docs/ENGINE.md index 544b3ed..65e7f16 100644 --- a/docs/ENGINE.md +++ b/docs/ENGINE.md @@ -95,7 +95,7 @@ represent a 2³² logical population. WebGL context loss stops the current recor and switches to a fresh Canvas element with the smaller sample. The payload is base64 in an external classic script, avoiding fetch and file-URL -CORS restrictions when opening `index.html` directly. It needs no `eval`, no +CORS restrictions when opening `rotation-lab.html` directly. It needs no `eval`, no worker, no remote package, and no cross-origin-isolation headers. CSP explicitly permits Wasm compilation using `wasm-unsafe-eval`. Compilation failure uses the bounded JavaScript fallback and is reported in the interface. From 8730131265794607b6fb7d6ae6dfa8f9bc9c7303 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:19:23 +0930 Subject: [PATCH 50/82] Harden BH2D receipt and Vulkan validation --- scripts/bench-bh2d-hardware.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 4c8a640..06fd87e 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -51,7 +51,7 @@ SYSTEM_PATH = "/usr/bin:/bin" BUILD_ENV_EXACT = { "LD_PRELOAD", "LD_AUDIT", "LD_LIBRARY_PATH", "LIBRARY_PATH", "COMPILER_PATH", - "VK_DRIVER_FILES", "VK_ICD_FILENAMES", "VK_LAYER_PATH", "VK_INSTANCE_LAYERS", + "VK_DRIVER_FILES", "VK_ADD_DRIVER_FILES", "VK_ICD_FILENAMES", "VK_LAYER_PATH", "VK_INSTANCE_LAYERS", "GCC_EXEC_PREFIX", "CPATH", "C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH", "RUSTFLAGS", "CARGO_ENCODED_RUSTFLAGS", @@ -612,8 +612,11 @@ def load_receipt(path: Path) -> Any: text = payload.decode("utf-8") except UnicodeDecodeError as exc: raise SweepError(f"receipt is not valid UTF-8: {path}") from exc + def reject_nonstandard_constant(value: str) -> None: + raise ValueError(f"non-standard JSON constant: {value}") + try: - return json.loads(text) + return json.loads(text, parse_constant=reject_nonstandard_constant) except ValueError as exc: raise SweepError(f"receipt is not valid JSON: {path}") from exc @@ -892,9 +895,19 @@ def validate_receipt( root.get("hardware_performance_claim_allowed") is True, "receipt does not authorize hardware performance evidence", ) - require(root.get("host_tree_rebuilds") == 0, "host tree rebuild occurred") + host_tree_rebuilds = require_int( + root.get("host_tree_rebuilds"), + "receipt.host_tree_rebuilds", + minimum=0, + ) + require(host_tree_rebuilds == 0, "host tree rebuild occurred") + host_particle_readbacks = require_int( + root.get("host_particle_readbacks_during_steps"), + "receipt.host_particle_readbacks_during_steps", + minimum=0, + ) require( - root.get("host_particle_readbacks_during_steps") == 0, + host_particle_readbacks == 0, "host particle readback occurred inside the evolution loop", ) require(root.get("force_solves") == steps + 1, "unexpected force-solve count") From 89b5e3fb15d53587ba65b15abc67793039158d75 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:19:54 +0930 Subject: [PATCH 51/82] Cover additive Vulkan and strict receipt parsing --- tests/test_bh2d_hardware_sweep.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 0d48d57..8d912da 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -405,6 +405,7 @@ def test_build_environment_overrides_are_rejected(self): for name in ( "LD_AUDIT", "VK_DRIVER_FILES", + "VK_ADD_DRIVER_FILES", "VK_ICD_FILENAMES", "VK_LAYER_PATH", "VK_INSTANCE_LAYERS", @@ -569,6 +570,7 @@ def test_system_build_path_and_explicit_rustc(self): "LD_AUDIT": "/tmp/audit.so", "LD_PRELOAD": "/tmp/preload.so", "VK_DRIVER_FILES": "/tmp/icd.json", + "VK_ADD_DRIVER_FILES": "/tmp/additional-icd.json", "VK_ICD_FILENAMES": "/tmp/legacy-icd.json", "VK_LAYER_PATH": "/tmp/layers", "VK_INSTANCE_LAYERS": "VK_LAYER_SYNTHETIC", @@ -582,6 +584,7 @@ def test_system_build_path_and_explicit_rustc(self): self.assertNotIn("LD_PRELOAD", env) for name in ( "VK_DRIVER_FILES", + "VK_ADD_DRIVER_FILES", "VK_ICD_FILENAMES", "VK_LAYER_PATH", "VK_INSTANCE_LAYERS", @@ -685,6 +688,19 @@ def test_non_utf8_run_log_preserved_and_manifest_failed(self): self.assertEqual((output / "n000512/run.log").read_bytes(), b"driver: \xff\n") self.assertEqual(json.loads((output / "manifest.json").read_text())["status"], "failed") + def test_receipt_rejects_nonstandard_json_constants(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "receipt.json" + for constant in ("NaN", "Infinity", "-Infinity"): + with self.subTest(constant=constant): + path.write_text( + '{"timings_seconds":{"parallel_tree_build_total":' + + constant + + "}}" + ) + with self.assertRaisesRegex(sweep.SweepError, "not valid JSON"): + sweep.load_receipt(path) + def test_invalid_utf8_receipt_is_normalized_to_sweep_error(self): with tempfile.TemporaryDirectory() as tmp: path = Path(tmp) / "receipt.json" @@ -900,6 +916,17 @@ def test_malformed_typed_receipt_is_rejected_as_sweep_error(self): with self.assertRaisesRegex(sweep.SweepError, "active_cell_count must be an integer"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_zero_work_counters_require_integer_types(self): + for field in ( + "host_tree_rebuilds", + "host_particle_readbacks_during_steps", + ): + with self.subTest(field=field): + receipt = receipt_for(512) + receipt[field] = False + with self.assertRaisesRegex(sweep.SweepError, field): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_repeat_tree_mismatch_is_rejected(self): receipt = receipt_for(512) receipt["tree"]["repeat_rebuild_matches"] = False From a9cd8cc36ea1528e74146252a937a941f5d35a49 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:30:56 +0930 Subject: [PATCH 52/82] Bound BH2D internal cells by bucket occupancy --- scripts/bench-bh2d-hardware.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 06fd87e..bcaec05 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -962,9 +962,10 @@ def validate_receipt( max_depth <= TREE_LEVELS - 1, "receipt.tree.max_depth exceeds the frozen Morton depth", ) - topology_capacity = sum( - min(particles, 4 ** depth) - for depth in range(max_depth + 1) + internal_cells_per_depth = particles // (TREE_BUCKET_SIZE + 1) + topology_capacity = leaf_count + sum( + min(4 ** depth, internal_cells_per_depth) + for depth in range(max_depth) ) require( active_cell_count <= topology_capacity, From 75be300cac8337aae2eeeb84f58d9c2d953c4655 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:31:00 +0930 Subject: [PATCH 53/82] Cover bucket-bounded BH2D occupancy --- tests/test_bh2d_hardware_sweep.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 8d912da..79b1926 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -980,6 +980,13 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): with self.assertRaisesRegex(sweep.SweepError, "per-depth quadtree occupancy"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = 6485 + receipt["tree"]["leaf_count"] = 512 + receipt["tree"]["max_depth"] = 16 + with self.assertRaisesRegex(sweep.SweepError, "per-depth quadtree occupancy"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) receipt["tree"]["active_cell_count"] = 1 receipt["tree"]["leaf_count"] = 1 From f78d73305b7146326498bdad0786650dd9a28b4d Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:31:04 +0930 Subject: [PATCH 54/82] Clarify GALAXY browser entrypoints --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd1f4ab..9df1577 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Choose a binary encounter, rotating disc or cold collapse. Pause, single-step, c The original prescribed-field instrument, including its Rust/WebAssembly sampling, UFF controls and exports, is preserved at **`rotation-lab.html`**. The detailed tree laboratory remains at **`barnes-hut.html`**. The following logical-population and sampling controls describe the rotation-law instrument. -The separate **`barnes-hut.html`** entrypoint is an opt-in resident self-gravity lab with a live quadtree overlay and direct-force error probes. It does not change the default rotation-law instrument. +The separate **`barnes-hut.html`** entrypoint is an opt-in resident self-gravity lab with a live quadtree overlay and direct-force error probes. The default browser entrypoint is the N-body simulator at **`index.html`**, while the preserved rotation-law instrument remains at **`rotation-lab.html`**. The browser instrument lets you change morphology, mass model, viewing geometry, and time while watching the galaxy and its rotation curve respond together. From 6f61b90519275bccad4f78e97bb205a3c6ce6f59 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 23:12:45 +0930 Subject: [PATCH 55/82] Harden BH2D loader and failed-run evidence --- scripts/bench-bh2d-hardware.py | 38 ++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index bcaec05..4f83537 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -51,7 +51,10 @@ SYSTEM_PATH = "/usr/bin:/bin" BUILD_ENV_EXACT = { "LD_PRELOAD", "LD_AUDIT", "LD_LIBRARY_PATH", "LIBRARY_PATH", "COMPILER_PATH", - "VK_DRIVER_FILES", "VK_ADD_DRIVER_FILES", "VK_ICD_FILENAMES", "VK_LAYER_PATH", "VK_INSTANCE_LAYERS", + "VK_DRIVER_FILES", "VK_ADD_DRIVER_FILES", "VK_ICD_FILENAMES", + "VK_LAYER_PATH", "VK_ADD_LAYER_PATH", "VK_INSTANCE_LAYERS", + "VK_LOADER_LAYERS_ENABLE", "VK_LOADER_LAYERS_DISABLE", + "VK_LOADER_DRIVERS_SELECT", "VK_LOADER_DRIVERS_DISABLE", "GCC_EXEC_PREFIX", "CPATH", "C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH", "RUSTFLAGS", "CARGO_ENCODED_RUSTFLAGS", @@ -830,13 +833,26 @@ def adapter_identity(gpu: Any, adapter_selector: str | None = None) -> str: adapter_selector.lower() in name.lower(), "receipt.gpu.name does not match the textual adapter selector", ) + device_type = require_string( + info.get("device_type"), "receipt.gpu.device_type", nonempty=True + ) + software_name = name.lower() + producer_classifies_software = ( + device_type.lower() == "cpu" + or any( + marker in software_name + for marker in ("llvmpipe", "lavapipe", "swiftshader", "software") + ) + ) + require( + not producer_classifies_software, + "receipt.gpu contradicts the producer software-adapter classification", + ) required = { "index": index, "name": name, "backend": require_string(info.get("backend"), "receipt.gpu.backend", nonempty=True), - "device_type": require_string( - info.get("device_type"), "receipt.gpu.device_type", nonempty=True - ), + "device_type": device_type, "driver": require_string(info.get("driver"), "receipt.gpu.driver"), "driver_info": require_string(info.get("driver_info"), "receipt.gpu.driver_info"), } @@ -1406,6 +1422,20 @@ def main() -> int: ) # Preserve every output byte, including non-UTF-8 driver diagnostics. log_path.write_bytes(completed.stdout) + if completed.returncode != 0: + manifest["runs"].append( + { + "particles": particles, + "status": "failed", + "exit_code": completed.returncode, + "log": str(log_path.relative_to(output)), + "log_sha256": sha256_file(log_path), + "cargo_target_dir": str(target_dir.relative_to(output)), + } + ) + manifest_path.write_text( + json.dumps(manifest, indent=2, sort_keys=True, allow_nan=False) + "\n" + ) require_source_provenance( repo_root, From 1e8a34acdb793d1f6e858bfd0a690be13f9fea26 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 23:13:19 +0930 Subject: [PATCH 56/82] Cover loader controls and failed-run log binding --- tests/test_bh2d_hardware_sweep.py | 43 +++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 79b1926..d0f6771 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -408,7 +408,12 @@ def test_build_environment_overrides_are_rejected(self): "VK_ADD_DRIVER_FILES", "VK_ICD_FILENAMES", "VK_LAYER_PATH", + "VK_ADD_LAYER_PATH", "VK_INSTANCE_LAYERS", + "VK_LOADER_LAYERS_ENABLE", + "VK_LOADER_LAYERS_DISABLE", + "VK_LOADER_DRIVERS_SELECT", + "VK_LOADER_DRIVERS_DISABLE", "RUSTFLAGS", "CARGO_BUILD_RUSTFLAGS", "RUSTC", @@ -573,7 +578,12 @@ def test_system_build_path_and_explicit_rustc(self): "VK_ADD_DRIVER_FILES": "/tmp/additional-icd.json", "VK_ICD_FILENAMES": "/tmp/legacy-icd.json", "VK_LAYER_PATH": "/tmp/layers", + "VK_ADD_LAYER_PATH": "/tmp/additional-layers", "VK_INSTANCE_LAYERS": "VK_LAYER_SYNTHETIC", + "VK_LOADER_LAYERS_ENABLE": "VK_LAYER_SYNTHETIC", + "VK_LOADER_LAYERS_DISABLE": "~implicit~", + "VK_LOADER_DRIVERS_SELECT": "*synthetic*", + "VK_LOADER_DRIVERS_DISABLE": "*other*", }, ): env = sweep.build_environment({"rustc": {"executable": "/selected/bin/rustc"}}) @@ -587,7 +597,12 @@ def test_system_build_path_and_explicit_rustc(self): "VK_ADD_DRIVER_FILES", "VK_ICD_FILENAMES", "VK_LAYER_PATH", + "VK_ADD_LAYER_PATH", "VK_INSTANCE_LAYERS", + "VK_LOADER_LAYERS_ENABLE", + "VK_LOADER_LAYERS_DISABLE", + "VK_LOADER_DRIVERS_SELECT", + "VK_LOADER_DRIVERS_DISABLE", ): self.assertNotIn(name, env) @@ -685,8 +700,17 @@ def test_non_utf8_run_log_preserved_and_manifest_failed(self): mock.patch.object(sweep.platform, "platform", return_value="fixture"), \ mock.patch.object(sweep.subprocess, "run", return_value= subprocess.CompletedProcess([], 1, b"driver: \xff\n")): self.assertEqual(sweep.main(), 1) - self.assertEqual((output / "n000512/run.log").read_bytes(), b"driver: \xff\n") - self.assertEqual(json.loads((output / "manifest.json").read_text())["status"], "failed") + log_path = output / "n000512/run.log" + self.assertEqual(log_path.read_bytes(), b"driver: \xff\n") + manifest = json.loads((output / "manifest.json").read_text()) + self.assertEqual(manifest["status"], "failed") + self.assertEqual(len(manifest["runs"]), 1) + failed = manifest["runs"][0] + self.assertEqual(failed["particles"], 512) + self.assertEqual(failed["status"], "failed") + self.assertEqual(failed["exit_code"], 1) + self.assertEqual(failed["log"], "n000512/run.log") + self.assertEqual(failed["log_sha256"], sweep.sha256_file(log_path)) def test_receipt_rejects_nonstandard_json_constants(self): with tempfile.TemporaryDirectory() as tmp: @@ -902,6 +926,21 @@ def test_adapter_identity_is_mandatory_and_hardware_bound(self): with self.assertRaises(sweep.SweepError): sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_producer_software_adapter_classification_is_enforced(self): + receipt = receipt_for(512) + receipt["gpu"]["device_type"] = "Cpu" + receipt["gpu"]["software"] = False + with self.assertRaisesRegex(sweep.SweepError, "software-adapter classification"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + + for name in ("llvmpipe", "lavapipe GPU", "SwiftShader Device", "Software Rasterizer"): + with self.subTest(name=name): + receipt = receipt_for(512) + receipt["gpu"]["name"] = name + receipt["gpu"]["software"] = False + with self.assertRaisesRegex(sweep.SweepError, "software-adapter classification"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_software_receipt_is_rejected(self): receipt = receipt_for(512) receipt["measurement_class"] = "software-validation" From af0339c7e1477b3eac2668fcb81cdcdfac4715cb Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 23:39:34 +0930 Subject: [PATCH 57/82] Bind all unusable BH2D runs and harden Darwin env --- scripts/bench-bh2d-hardware.py | 114 +++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 4f83537..8866bdc 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -78,6 +78,9 @@ "LDFLAGS", } BUILD_ENV_PATTERNS = ( + # Darwin dyld variables can inject or redirect libraries into evidence + # subprocesses. Fail closed for the namespace rather than chasing keys. + re.compile(r"^DYLD_.+$"), re.compile(r"^CARGO_TARGET_.+_(?:RUSTFLAGS|LINKER|RUNNER)$"), # Cargo exposes profile configuration through CARGO_PROFILE__*. # Fail closed for the whole namespace so newly added profile keys cannot @@ -926,8 +929,18 @@ def validate_receipt( host_particle_readbacks == 0, "host particle readback occurred inside the evolution loop", ) - require(root.get("force_solves") == steps + 1, "unexpected force-solve count") - require(root.get("evolution_tree_builds") == steps + 1, "unexpected tree-build count") + force_solves = require_int( + root.get("force_solves"), + "receipt.force_solves", + minimum=0, + ) + require(force_solves == steps + 1, "unexpected force-solve count") + evolution_tree_builds = require_int( + root.get("evolution_tree_builds"), + "receipt.evolution_tree_builds", + minimum=0, + ) + require(evolution_tree_builds == steps + 1, "unexpected tree-build count") identity = adapter_identity(root.get("gpu"), adapter_selector) @@ -1422,57 +1435,60 @@ def main() -> int: ) # Preserve every output byte, including non-UTF-8 driver diagnostics. log_path.write_bytes(completed.stdout) - if completed.returncode != 0: - manifest["runs"].append( - { - "particles": particles, - "status": "failed", - "exit_code": completed.returncode, - "log": str(log_path.relative_to(output)), - "log_sha256": sha256_file(log_path), - "cargo_target_dir": str(target_dir.relative_to(output)), - } + try: + require_source_provenance( + repo_root, + revision, + output, + expected_cargo_context=build_cargo_context, + expected_toolchain_context=build_toolchain_context, + expected_dependency_source_context=build_dependency_source_context, + cargo_command=args.cargo, ) + if completed.returncode != 0: + raise SweepError( + f"BH #2D hardware run failed at {particles} particles; see {log_path}" + ) + if not receipt_path.is_file(): + raise SweepError(f"missing receipt after {particles}-particle run") + + receipt = load_receipt(receipt_path) + summary = validate_receipt( + receipt, + particles=particles, + preset=args.preset, + steps=args.steps, + dt_myr=args.dt_myr, + seed=args.seed, + theta=args.theta, + softening_kpc=args.softening_kpc, + direct_probes=args.direct_probes, + oracle_limit=args.oracle_limit, + benchmark_warmup=args.benchmark_warmup, + benchmark_repeats=args.benchmark_repeats, + adapter_selector=args.adapter, + ) + if adapter is None: + adapter = summary["adapter_identity"] + elif summary["adapter_identity"] != adapter: + raise SweepError("adapter identity changed during the scaling sweep") + except (OSError, ValueError, SweepError): + failed_run = { + "particles": particles, + "status": "failed", + "exit_code": completed.returncode, + "log": str(log_path.relative_to(output)), + "log_sha256": sha256_file(log_path), + "cargo_target_dir": str(target_dir.relative_to(output)), + } + if receipt_path.is_file(): + failed_run["receipt"] = str(receipt_path.relative_to(output)) + failed_run["receipt_sha256"] = sha256_file(receipt_path) + manifest["runs"].append(failed_run) manifest_path.write_text( json.dumps(manifest, indent=2, sort_keys=True, allow_nan=False) + "\n" ) - - require_source_provenance( - repo_root, - revision, - output, - expected_cargo_context=build_cargo_context, - expected_toolchain_context=build_toolchain_context, - expected_dependency_source_context=build_dependency_source_context, - cargo_command=args.cargo, - ) - if completed.returncode != 0: - raise SweepError( - f"BH #2D hardware run failed at {particles} particles; see {log_path}" - ) - if not receipt_path.is_file(): - raise SweepError(f"missing receipt after {particles}-particle run") - - receipt = load_receipt(receipt_path) - summary = validate_receipt( - receipt, - particles=particles, - preset=args.preset, - steps=args.steps, - dt_myr=args.dt_myr, - seed=args.seed, - theta=args.theta, - softening_kpc=args.softening_kpc, - direct_probes=args.direct_probes, - oracle_limit=args.oracle_limit, - benchmark_warmup=args.benchmark_warmup, - benchmark_repeats=args.benchmark_repeats, - adapter_selector=args.adapter, - ) - if adapter is None: - adapter = summary["adapter_identity"] - elif summary["adapter_identity"] != adapter: - raise SweepError("adapter identity changed during the scaling sweep") + raise summary.update( { From f6822b5967a95d4826e6cf1c39f628e203ca82ff Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Sun, 27 Sep 2026 23:40:14 +0930 Subject: [PATCH 58/82] Cover Darwin env and unusable successful runs --- tests/test_bh2d_hardware_sweep.py | 93 +++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index d0f6771..23f6605 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -404,6 +404,9 @@ def test_cargo_config_context_detects_mid_sweep_change(self): def test_build_environment_overrides_are_rejected(self): for name in ( "LD_AUDIT", + "DYLD_INSERT_LIBRARIES", + "DYLD_LIBRARY_PATH", + "DYLD_FRAMEWORK_PATH", "VK_DRIVER_FILES", "VK_ADD_DRIVER_FILES", "VK_ICD_FILENAMES", @@ -574,6 +577,9 @@ def test_system_build_path_and_explicit_rustc(self): "GIT_WORK_TREE": "/other", "LD_AUDIT": "/tmp/audit.so", "LD_PRELOAD": "/tmp/preload.so", + "DYLD_INSERT_LIBRARIES": "/tmp/inject.dylib", + "DYLD_LIBRARY_PATH": "/tmp/dylibs", + "DYLD_FRAMEWORK_PATH": "/tmp/frameworks", "VK_DRIVER_FILES": "/tmp/icd.json", "VK_ADD_DRIVER_FILES": "/tmp/additional-icd.json", "VK_ICD_FILENAMES": "/tmp/legacy-icd.json", @@ -593,6 +599,9 @@ def test_system_build_path_and_explicit_rustc(self): self.assertNotIn("LD_AUDIT", env) self.assertNotIn("LD_PRELOAD", env) for name in ( + "DYLD_INSERT_LIBRARIES", + "DYLD_LIBRARY_PATH", + "DYLD_FRAMEWORK_PATH", "VK_DRIVER_FILES", "VK_ADD_DRIVER_FILES", "VK_ICD_FILENAMES", @@ -712,6 +721,82 @@ def test_non_utf8_run_log_preserved_and_manifest_failed(self): self.assertEqual(failed["log"], "n000512/run.log") self.assertEqual(failed["log_sha256"], sweep.sha256_file(log_path)) + def test_successful_unusable_receipts_bind_run_log(self): + import argparse + import json + + cases = { + "missing": None, + "malformed": b"{not-json", + "rejected": (lambda: ( + lambda receipt: json.dumps(receipt).encode() + )({**receipt_for(512), "status": "incomplete"}))(), + } + for name, receipt_payload in cases.items(): + with self.subTest(name=name), tempfile.TemporaryDirectory() as tmp: + output = Path(tmp) / "evidence" + args = argparse.Namespace( + output=output, + particles="512", + preset="disc", + steps=3, + dt_myr=0.01, + seed=303, + theta=0.5, + softening_kpc=0.05, + direct_probes=12, + oracle_limit=4096, + benchmark_warmup=2, + benchmark_repeats=7, + adapter=None, + cargo="cargo", + dry_run=False, + ) + context = { + "cargo": {"executable": "/selected/cargo"}, + "rustc": {"executable": "/selected/rustc"}, + } + + def verifier_run(command, **kwargs): + receipt_arg = Path(command[command.index("--receipt") + 1]) + if receipt_payload is not None: + receipt_arg.write_bytes(receipt_payload) + return subprocess.CompletedProcess( + command, + 0, + b"exited cleanly but no usable receipt\n", + ) + + with mock.patch.object(sweep, "parse_args", return_value=args), \ + mock.patch.object(sweep, "git_revision", return_value="fixture"), \ + mock.patch.object(sweep, "require_clean_source_tree"), \ + mock.patch.object(sweep, "cargo_config_context", return_value=[]), \ + mock.patch.object(sweep, "toolchain_context", return_value=context), \ + mock.patch.object(sweep, "dependency_source_context", return_value=[]), \ + mock.patch.object(sweep, "require_source_provenance"), \ + mock.patch.object(sweep.platform, "platform", return_value="fixture"), \ + mock.patch.object(sweep.subprocess, "run", side_effect=verifier_run): + self.assertEqual(sweep.main(), 1) + + log_path = output / "n000512/run.log" + manifest = json.loads((output / "manifest.json").read_text()) + self.assertEqual(manifest["status"], "failed") + self.assertEqual(len(manifest["runs"]), 1) + failed = manifest["runs"][0] + self.assertEqual(failed["status"], "failed") + self.assertEqual(failed["exit_code"], 0) + self.assertEqual(failed["log"], "n000512/run.log") + self.assertEqual(failed["log_sha256"], sweep.sha256_file(log_path)) + receipt_path = output / "n000512/receipt.json" + if receipt_payload is None: + self.assertNotIn("receipt", failed) + else: + self.assertEqual(failed["receipt"], "n000512/receipt.json") + self.assertEqual( + failed["receipt_sha256"], + sweep.sha256_file(receipt_path), + ) + def test_receipt_rejects_nonstandard_json_constants(self): with tempfile.TemporaryDirectory() as tmp: path = Path(tmp) / "receipt.json" @@ -966,6 +1051,14 @@ def test_zero_work_counters_require_integer_types(self): with self.assertRaisesRegex(sweep.SweepError, field): sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_execution_counters_require_integer_types(self): + for field in ("force_solves", "evolution_tree_builds"): + with self.subTest(field=field): + receipt = receipt_for(512) + receipt[field] = 4.0 + with self.assertRaisesRegex(sweep.SweepError, field): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_repeat_tree_mismatch_is_rejected(self): receipt = receipt_for(512) receipt["tree"]["repeat_rebuild_matches"] = False From c95db12623c50768762da4ffe861d41344d3c426 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:03:45 +0930 Subject: [PATCH 59/82] Harden BH2D parser and topology invariants --- scripts/bench-bh2d-hardware.py | 48 ++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 8866bdc..d735830 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -621,8 +621,20 @@ def load_receipt(path: Path) -> Any: def reject_nonstandard_constant(value: str) -> None: raise ValueError(f"non-standard JSON constant: {value}") + def reject_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in pairs: + if key in result: + raise ValueError(f"duplicate JSON object key: {key}") + result[key] = value + return result + try: - return json.loads(text, parse_constant=reject_nonstandard_constant) + return json.loads( + text, + parse_constant=reject_nonstandard_constant, + object_pairs_hook=reject_duplicate_keys, + ) except ValueError as exc: raise SweepError(f"receipt is not valid JSON: {path}") from exc @@ -635,14 +647,18 @@ def sha256_directory(path: Path) -> str: relative = entry.relative_to(root) if entry.is_symlink(): raise SweepError(f"Cargo dependency source contains unsupported symlink: {entry}") - if entry.is_dir(): - continue - require(entry.is_file(), f"Cargo dependency source contains non-file entry: {entry}") + mode = entry.lstat().st_mode + require( + entry.is_dir() or entry.is_file(), + f"Cargo dependency source contains unsupported entry: {entry}", + ) relative_bytes = os.fsencode(str(relative)) - file_digest = bytes.fromhex(sha256_file(entry)) + mode_bits = stat.S_IFMT(mode) | stat.S_IMODE(mode) digest.update(len(relative_bytes).to_bytes(8, "big")) digest.update(relative_bytes) - digest.update(file_digest) + digest.update(mode_bits.to_bytes(4, "big")) + if entry.is_file(): + digest.update(bytes.fromhex(sha256_file(entry))) return digest.hexdigest() @@ -821,14 +837,26 @@ def validate_force_errors( return rms, maximum +def parse_adapter_index_selector(value: str) -> int | None: + # Mirror Rust usize::from_str closely enough for the producer contract: + # optional leading '+', ASCII decimal digits, no whitespace/sign-minus, + # and overflow falls back to textual name selection. + if re.fullmatch(r"\+?[0-9]+", value) is None: + return None + parsed = int(value, 10) + usize_max = (sys.maxsize << 1) | 1 + return parsed if parsed <= usize_max else None + + def adapter_identity(gpu: Any, adapter_selector: str | None = None) -> str: info = require_object(gpu, "receipt.gpu") index = require_int(info.get("index"), "receipt.gpu.index", minimum=0) name = require_string(info.get("name"), "receipt.gpu.name", nonempty=True) if adapter_selector is not None: - if re.fullmatch(r"[0-9]+", adapter_selector): + selected_index = parse_adapter_index_selector(adapter_selector) + if selected_index is not None: require( - index == int(adapter_selector), + index == selected_index, "receipt.gpu.index does not match the numeric adapter selector", ) else: @@ -1008,6 +1036,10 @@ def validate_receipt( internal_cell_count >= max_depth, "receipt.tree has too few internal cells for the reported maximum depth", ) + require( + internal_cell_count <= leaf_count * max_depth, + "receipt.tree has too many internal cells for its leaf-path capacity", + ) if max_depth < TREE_LEVELS - 1: minimum_leaf_count = (particles + TREE_BUCKET_SIZE - 1) // TREE_BUCKET_SIZE require( From 049152dfaeb82dfb9414daa411c2c47c06f972d8 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:04:36 +0930 Subject: [PATCH 60/82] Cover BH2D selector, topology, hash, and JSON edge cases --- tests/test_bh2d_hardware_sweep.py | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 23f6605..705e7c2 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -488,6 +488,17 @@ def test_dependency_source_context_binds_cached_source_bytes(self): self.assertEqual(first[0]["name"], "wgpu") self.assertNotEqual(first[0]["tree_sha256"], second[0]["tree_sha256"]) + def test_dependency_tree_hash_binds_file_modes(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + helper = root / "helper.sh" + helper.write_text("#!/bin/sh\nexit 0\n") + helper.chmod(0o644) + first = sweep.sha256_directory(root) + helper.chmod(0o755) + second = sweep.sha256_directory(root) + self.assertNotEqual(first, second) + def test_cargo_config_execution_and_source_redirects_are_rejected(self): cases = { "rustc-wrapper": '[build]\nrustc-wrapper = "/tmp/wrapper"\n', @@ -810,6 +821,16 @@ def test_receipt_rejects_nonstandard_json_constants(self): with self.assertRaisesRegex(sweep.SweepError, "not valid JSON"): sweep.load_receipt(path) + def test_receipt_rejects_duplicate_object_keys(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "receipt.json" + path.write_text( + '{"measurement_class":"software-validation",' + '"measurement_class":"hardware"}' + ) + with self.assertRaisesRegex(sweep.SweepError, "not valid JSON"): + sweep.load_receipt(path) + def test_invalid_utf8_receipt_is_normalized_to_sweep_error(self): with tempfile.TemporaryDirectory() as tmp: path = Path(tmp) / "receipt.json" @@ -979,6 +1000,15 @@ def test_adapter_index_is_bound_into_identity_and_numeric_selector(self): **validation_kwargs(512, adapter_selector="0"), ) + summary = sweep.validate_receipt( + receipt0, + **validation_kwargs(512, adapter_selector="+0"), + ) + self.assertEqual(summary["particles"], 512) + self.assertEqual(sweep.parse_adapter_index_selector("+0"), 0) + self.assertIsNone(sweep.parse_adapter_index_selector("-0")) + self.assertIsNone(sweep.parse_adapter_index_selector(" 0")) + receipt = receipt_for(512) receipt["gpu"]["name"] = "AMD Radeon RX 7900 XTX" with self.assertRaisesRegex(sweep.SweepError, "textual adapter selector"): @@ -1154,6 +1184,13 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): with self.assertRaisesRegex(sweep.SweepError, "internal cells"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = 100 + receipt["tree"]["leaf_count"] = 1 + receipt["tree"]["max_depth"] = 16 + with self.assertRaisesRegex(sweep.SweepError, "leaf-path capacity"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_state_error_zero_maximum_matches_rms(self): for key in ("bh2c_serial_gpu", "bh2b2_host_tree_gpu"): with self.subTest(key=key, component="position"): From df4ffcc02dd85fc7da6083d6baabedfd01e77412 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:28:49 +0930 Subject: [PATCH 61/82] Bind GPU adapter enumeration count --- runtime/src/nbody_gpu.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/runtime/src/nbody_gpu.rs b/runtime/src/nbody_gpu.rs index fc2ea11..c1860ab 100644 --- a/runtime/src/nbody_gpu.rs +++ b/runtime/src/nbody_gpu.rs @@ -295,10 +295,11 @@ fn select_adapter( }) } -fn describe(adapter: &wgpu::Adapter, index: usize) -> Value { +fn describe(adapter: &wgpu::Adapter, index: usize, adapter_count: usize) -> Value { let info = adapter.get_info(); json!({ "index": index, + "adapter_count": adapter_count, "name": info.name, "backend": format!("{:?}", info.backend), "device_type": format!("{:?}", info.device_type), @@ -333,8 +334,9 @@ impl NbodyGpu { let index = select_adapter(&infos, name, allow_software).ok_or( "No matching hardware compute adapter. --allow-software permits a software adapter for validation.", )?; + let adapter_count = infos.len(); let adapter = available_adapters.swap_remove(index); - let info = describe(&adapter, index); + let info = describe(&adapter, index, adapter_count); let available = adapter.limits(); let limits = wgpu::Limits { max_storage_buffer_binding_size: available.max_storage_buffer_binding_size, From 2ad1c95daf7a4e25530e59153c16a444c8bfa5b5 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:28:53 +0930 Subject: [PATCH 62/82] Tighten BH2D adapter and dependency provenance --- scripts/bench-bh2d-hardware.py | 37 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index d735830..52d540b 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -345,17 +345,20 @@ def dependency_source_context( ) ).resolve() package_root = manifest.parent - if is_within(package_root, root): - continue - require( - package_root.is_dir(), - f"Cargo dependency source directory is missing: {package_root}", - ) source = package.get("source") require( source is None or isinstance(source, str), f"Cargo metadata packages[{index}].source must be a string or null", ) + # Repository path/workspace packages are already bound by Git + # provenance. Registry/git packages remain external compiler inputs + # even when CARGO_HOME happens to live under the checkout. + if source is None and is_within(package_root, root): + continue + require( + package_root.is_dir(), + f"Cargo dependency source directory is missing: {package_root}", + ) result.append( { "name": require_string( @@ -851,10 +854,16 @@ def parse_adapter_index_selector(value: str) -> int | None: def adapter_identity(gpu: Any, adapter_selector: str | None = None) -> str: info = require_object(gpu, "receipt.gpu") index = require_int(info.get("index"), "receipt.gpu.index", minimum=0) + adapter_count = require_int( + info.get("adapter_count"), + "receipt.gpu.adapter_count", + minimum=1, + ) + require(index < adapter_count, "receipt.gpu.index must be below adapter_count") name = require_string(info.get("name"), "receipt.gpu.name", nonempty=True) if adapter_selector is not None: selected_index = parse_adapter_index_selector(adapter_selector) - if selected_index is not None: + if selected_index is not None and selected_index < adapter_count: require( index == selected_index, "receipt.gpu.index does not match the numeric adapter selector", @@ -879,10 +888,16 @@ def adapter_identity(gpu: Any, adapter_selector: str | None = None) -> str: not producer_classifies_software, "receipt.gpu contradicts the producer software-adapter classification", ) + backend = require_string(info.get("backend"), "receipt.gpu.backend", nonempty=True) + require( + backend in {"Vulkan", "Metal", "Dx12"}, + "receipt.gpu.backend is not enabled by the producer", + ) required = { "index": index, + "adapter_count": adapter_count, "name": name, - "backend": require_string(info.get("backend"), "receipt.gpu.backend", nonempty=True), + "backend": backend, "device_type": device_type, "driver": require_string(info.get("driver"), "receipt.gpu.driver"), "driver_info": require_string(info.get("driver_info"), "receipt.gpu.driver_info"), @@ -1036,8 +1051,12 @@ def validate_receipt( internal_cell_count >= max_depth, "receipt.tree has too few internal cells for the reported maximum depth", ) + leaf_path_capacity = sum( + min(4 ** depth, leaf_count) + for depth in range(max_depth) + ) require( - internal_cell_count <= leaf_count * max_depth, + internal_cell_count <= leaf_path_capacity, "receipt.tree has too many internal cells for its leaf-path capacity", ) if max_depth < TREE_LEVELS - 1: From cb46356587344c4c32d6775f3fbeeb3ae972af81 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:29:27 +0930 Subject: [PATCH 63/82] Cover adapter fallback and in-repo dependency provenance --- tests/test_bh2d_hardware_sweep.py | 78 +++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 705e7c2..2ea121b 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -111,6 +111,7 @@ def receipt_for( "softening_kpc": softening_kpc, "gpu": { "index": 0, + "adapter_count": 1, "name": "Synthetic GPU", "backend": "Vulkan", "device_type": "DiscreteGpu", @@ -488,6 +489,62 @@ def test_dependency_source_context_binds_cached_source_bytes(self): self.assertEqual(first[0]["name"], "wgpu") self.assertNotEqual(first[0]["tree_sha256"], second[0]["tree_sha256"]) + def test_in_repo_registry_dependency_is_still_hashed(self): + with tempfile.TemporaryDirectory() as tmp: + repo = Path(tmp) + runtime = repo / "runtime" + runtime.mkdir() + (runtime / "Cargo.toml").write_text( + "[package]\nname='fixture'\nversion='0.1.0'\n" + ) + dependency = ( + repo + / "runtime" + / "target" + / "cargo-home" + / "registry" + / "src" + / "index" + / "wgpu-24.0.5" + ) + dependency.mkdir(parents=True) + manifest = dependency / "Cargo.toml" + source = dependency / "src" / "lib.rs" + source.parent.mkdir() + manifest.write_text("[package]\nname='wgpu'\nversion='24.0.5'\n") + source.write_text("pub const VALUE: u32 = 1;\n") + metadata = { + "packages": [ + { + "name": "fixture", + "version": "0.1.0", + "source": None, + "manifest_path": str(runtime / "Cargo.toml"), + }, + { + "name": "wgpu", + "version": "24.0.5", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "manifest_path": str(manifest), + }, + ] + } + completed = subprocess.CompletedProcess( + [], + 0, + stdout=__import__("json").dumps(metadata).encode(), + stderr=b"", + ) + toolchain = { + "cargo": {"executable": "/selected/cargo"}, + "rustc": {"executable": "/selected/rustc"}, + } + with mock.patch.object(sweep.subprocess, "run", return_value=completed): + context = sweep.dependency_source_context(repo, toolchain) + self.assertEqual(len(context), 1) + self.assertEqual(context[0]["name"], "wgpu") + self.assertTrue(context[0]["path"].startswith("runtime/target/cargo-home/")) + def test_dependency_tree_hash_binds_file_modes(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) @@ -990,6 +1047,7 @@ def test_adapter_index_is_bound_into_identity_and_numeric_selector(self): receipt0 = receipt_for(512) receipt1 = receipt_for(512) receipt1["gpu"]["index"] = 1 + receipt1["gpu"]["adapter_count"] = 2 summary0 = sweep.validate_receipt(receipt0, **validation_kwargs(512)) summary1 = sweep.validate_receipt(receipt1, **validation_kwargs(512)) self.assertNotEqual(summary0["adapter_identity"], summary1["adapter_identity"]) @@ -1023,6 +1081,19 @@ def test_adapter_index_is_bound_into_identity_and_numeric_selector(self): ) self.assertEqual(summary["particles"], 512) + receipt = receipt_for(512) + receipt["gpu"]["name"] = "NVIDIA GeForce RTX 4090" + summary = sweep.validate_receipt( + receipt, + **validation_kwargs(512, adapter_selector="4090"), + ) + self.assertEqual(summary["particles"], 512) + + receipt = receipt_for(512) + receipt["gpu"]["backend"] = "Gl" + with self.assertRaisesRegex(sweep.SweepError, "not enabled by the producer"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_adapter_identity_is_mandatory_and_hardware_bound(self): for gpu in ( {}, @@ -1191,6 +1262,13 @@ def test_tree_structure_is_bounded_by_frozen_sparse_representation(self): with self.assertRaisesRegex(sweep.SweepError, "leaf-path capacity"): sweep.validate_receipt(receipt, **validation_kwargs(512)) + receipt = receipt_for(512) + receipt["tree"]["active_cell_count"] = 34 + receipt["tree"]["leaf_count"] = 2 + receipt["tree"]["max_depth"] = 16 + with self.assertRaisesRegex(sweep.SweepError, "leaf-path capacity"): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_state_error_zero_maximum_matches_rms(self): for key in ("bh2c_serial_gpu", "bh2b2_host_tree_gpu"): with self.subTest(key=key, component="position"): From 89b5080dd75d44eb2a5240152eb854cd55d8ef0a Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:55:04 +0930 Subject: [PATCH 64/82] Add clean launch and platform tool policy --- scripts/bench-bh2d-hardware.py | 123 +++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 12 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 52d540b..8e1d1f2 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -13,7 +13,9 @@ # sys is built in: fail before importing anything from a caller-controlled path. import sys if __name__ == "__main__" and not sys.flags.isolated: - raise SystemExit("Hardware capture requires isolated Python: python3 -I scripts/bench-bh2d-hardware.py ...") + raise SystemExit( + "Hardware capture requires the clean launcher: scripts/bench-bh2d-hardware ..." + ) import argparse import hashlib @@ -27,7 +29,7 @@ import stat import subprocess import tomllib -from pathlib import Path +from pathlib import Path, PureWindowsPath from typing import Any RECEIPT_SCHEMA = "galaxy.barnes-hut-parallel-gpu-tree-receipt.v1" @@ -48,7 +50,7 @@ TREE_META_BYTES = 32 BOUNDS_RECORD_BYTES = 16 RADIX_DIGITS = 16 -SYSTEM_PATH = "/usr/bin:/bin" +CLEAN_LAUNCH_ENV = "GALAXY_BH2D_CLEAN_LAUNCH" BUILD_ENV_EXACT = { "LD_PRELOAD", "LD_AUDIT", "LD_LIBRARY_PATH", "LIBRARY_PATH", "COMPILER_PATH", "VK_DRIVER_FILES", "VK_ADD_DRIVER_FILES", "VK_ICD_FILENAMES", @@ -93,6 +95,71 @@ class SweepError(RuntimeError): pass +def trusted_system_path(system_name: str | None = None) -> str: + """Return the minimal host-tool search path used after tool selection.""" + system_name = system_name or platform.system() + if system_name == "Windows": + candidates: list[str] = [] + system_root = os.environ.get("SystemRoot", r"C:\Windows") + candidates.append(str(PureWindowsPath(system_root) / "System32")) + for variable in ("ProgramFiles", "ProgramFiles(x86)"): + root = os.environ.get(variable) + if root: + base = PureWindowsPath(root) / "Git" + candidates.extend((str(base / "cmd"), str(base / "bin"))) + local = os.environ.get("LOCALAPPDATA") + if local: + base = PureWindowsPath(local) / "Programs" / "Git" + candidates.extend((str(base / "cmd"), str(base / "bin"))) + home = os.environ.get("USERPROFILE") + if home: + candidates.append(str(PureWindowsPath(home) / ".cargo" / "bin")) + separator = ";" + elif system_name == "Darwin": + candidates = [ + "/usr/bin", + "/bin", + "/usr/sbin", + "/sbin", + "/usr/local/bin", + "/opt/homebrew/bin", + ] + separator = ":" + else: + candidates = ["/usr/bin", "/bin", "/usr/local/bin"] + separator = ":" + + unique: list[str] = [] + seen: set[str] = set() + for candidate in candidates: + key = candidate.casefold() if system_name == "Windows" else candidate + if key not in seen: + seen.add(key) + unique.append(candidate) + return separator.join(unique) + + +def extend_tool_path(base: str, directories: list[str], system_name: str) -> str: + separator = ";" if system_name == "Windows" else ":" + parts = [item for item in base.split(separator) if item] + seen = {item.casefold() if system_name == "Windows" else item for item in parts} + for directory in directories: + key = directory.casefold() if system_name == "Windows" else directory + if key not in seen: + seen.add(key) + parts.append(directory) + return separator.join(parts) + + +def require_clean_launcher() -> None: + if os.environ.get(CLEAN_LAUNCH_ENV) != "1": + raise SweepError( + "hardware evidence capture must start through the clean launcher " + "(scripts/bench-bh2d-hardware on POSIX or " + "scripts\\bench-bh2d-hardware.cmd on Windows)" + ) + + def parse_particles(raw: str) -> list[int]: try: values = [int(item.strip()) for item in raw.split(",") if item.strip()] @@ -108,9 +175,10 @@ def parse_particles(raw: str) -> list[int]: def git_invocation(command: list[str], cwd: Path) -> tuple[list[str], dict[str, str]]: + system_path = trusted_system_path() env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")} - env.update(PATH=SYSTEM_PATH, GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL=os.devnull) - git = shutil.which("git", path=SYSTEM_PATH) + env.update(PATH=system_path, GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL=os.devnull) + git = shutil.which("git", path=system_path) if git is None: raise SweepError("system Git is required") # rev-parse also handles a linked worktree's .git file. No ambient selectors. @@ -271,16 +339,46 @@ def tool_record(command: str, name: str, repo_root: Path) -> dict[str, Any]: def toolchain_context(cargo_command: str, repo_root: Path) -> dict[str, Any]: require_no_build_environment_overrides() + system_name = platform.system() + base_path = trusted_system_path(system_name) cargo = tool_record(cargo_command, "cargo", repo_root) cargo["requested"] = cargo_command rustc = tool_record("rustc", "rustc", repo_root) - system_tools = {} - for name in ("cc", "c++", "gcc", "g++", "ld", "as", "ar", "ranlib", "git"): - path = shutil.which(name, path=SYSTEM_PATH) + + if system_name == "Windows": + candidates = ("git", "cl", "link", "lib", "rc", "lld-link") + discovery_path = os.environ.get("PATH", "") + else: + candidates = ("cc", "c++", "clang", "clang++", "gcc", "g++", "ld", "as", "ar", "ranlib", "git") + discovery_path = base_path + + system_tools: dict[str, dict[str, str]] = {} + tool_directories: list[str] = [] + for name in candidates: + path = shutil.which(name, path=discovery_path) if path: - system_tools[name] = {"path": str(Path(path).resolve()), "sha256": sha256_file(Path(path))} - require("cc" in system_tools, "system C linker (cc) is required") - return {"cargo": cargo, "rustc": rustc, "build_path": SYSTEM_PATH, "system_tools": system_tools} + resolved = Path(path).resolve() + system_tools[name] = { + "path": str(resolved), + "sha256": sha256_file(resolved), + } + tool_directories.append(str(resolved.parent)) + + require("git" in system_tools, "trusted Git executable is required") + if system_name != "Windows": + require( + "cc" in system_tools or "clang" in system_tools or "gcc" in system_tools, + "system C compiler/linker driver is required", + ) + + build_path = extend_tool_path(base_path, tool_directories, system_name) + return { + "platform": system_name, + "cargo": cargo, + "rustc": rustc, + "build_path": build_path, + "system_tools": system_tools, + } def build_environment(context: dict[str, Any]) -> dict[str, str]: @@ -291,7 +389,7 @@ def build_environment(context: dict[str, Any]) -> dict[str, str]: and key not in BUILD_ENV_EXACT and not any(pattern.fullmatch(key) for pattern in BUILD_ENV_PATTERNS) } - env["PATH"] = SYSTEM_PATH + env["PATH"] = context["build_path"] env["RUSTC"] = context["rustc"]["executable"] return env @@ -1357,6 +1455,7 @@ def parse_args() -> argparse.Namespace: def main() -> int: + require_clean_launcher() args = parse_args() repo_root = Path(__file__).resolve().parents[1] manifest_path: Path | None = None From 1ead9517dde2a870533e65efb841aced68d3223c Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:55:52 +0930 Subject: [PATCH 65/82] Add clean POSIX BH2D launcher --- scripts/bench-bh2d-hardware-launch.sh | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 scripts/bench-bh2d-hardware-launch.sh diff --git a/scripts/bench-bh2d-hardware-launch.sh b/scripts/bench-bh2d-hardware-launch.sh new file mode 100644 index 0000000..cf0ba02 --- /dev/null +++ b/scripts/bench-bh2d-hardware-launch.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# SPDX-License-Identifier: Apache-2.0 +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +PYTHON_BIN=$(command -v python3 || true) +if [ -z "$PYTHON_BIN" ]; then + echo "BH #2D hardware sweep: python3 is required" >&2 + exit 127 +fi + +if [ -x /usr/bin/env ]; then + ENV_BIN=/usr/bin/env +elif [ -x /bin/env ]; then + ENV_BIN=/bin/env +else + echo "BH #2D hardware sweep: system env executable is required" >&2 + exit 127 +fi + +PATH_VALUE=${PATH-} +HOME_VALUE=${HOME-} +USER_VALUE=${USER-} +LOGNAME_VALUE=${LOGNAME-} +TMPDIR_VALUE=${TMPDIR-} +CARGO_HOME_VALUE=${CARGO_HOME-} +RUSTUP_HOME_VALUE=${RUSTUP_HOME-} + +exec "$ENV_BIN" -i \ + GALAXY_BH2D_CLEAN_LAUNCH=1 \ + PATH="$PATH_VALUE" \ + HOME="$HOME_VALUE" \ + USER="$USER_VALUE" \ + LOGNAME="$LOGNAME_VALUE" \ + TMPDIR="$TMPDIR_VALUE" \ + CARGO_HOME="$CARGO_HOME_VALUE" \ + RUSTUP_HOME="$RUSTUP_HOME_VALUE" \ + "$PYTHON_BIN" -I "$SCRIPT_DIR/bench-bh2d-hardware.py" "$@" From 806b9dc1c44b15fa85c2363b40deecc89bd65f7d Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:56:03 +0930 Subject: [PATCH 66/82] Add native Windows BH2D launcher --- scripts/bench-bh2d-hardware-launch.cmd | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 scripts/bench-bh2d-hardware-launch.cmd diff --git a/scripts/bench-bh2d-hardware-launch.cmd b/scripts/bench-bh2d-hardware-launch.cmd new file mode 100644 index 0000000..90f9c9d --- /dev/null +++ b/scripts/bench-bh2d-hardware-launch.cmd @@ -0,0 +1,35 @@ +@echo off +rem SPDX-License-Identifier: Apache-2.0 +setlocal + +set "LD_PRELOAD=" +set "LD_AUDIT=" +set "LD_LIBRARY_PATH=" +set "DYLD_INSERT_LIBRARIES=" +set "DYLD_LIBRARY_PATH=" +set "DYLD_FRAMEWORK_PATH=" +set "VK_DRIVER_FILES=" +set "VK_ADD_DRIVER_FILES=" +set "VK_ICD_FILENAMES=" +set "VK_LAYER_PATH=" +set "VK_ADD_LAYER_PATH=" +set "VK_INSTANCE_LAYERS=" +set "VK_LOADER_LAYERS_ENABLE=" +set "VK_LOADER_LAYERS_DISABLE=" +set "VK_LOADER_DRIVERS_SELECT=" +set "VK_LOADER_DRIVERS_DISABLE=" +set "GALAXY_BH2D_CLEAN_LAUNCH=1" + +where py >nul 2>nul +if %ERRORLEVEL% EQU 0 ( + py -3 -I "%~dp0bench-bh2d-hardware.py" %* + exit /b %ERRORLEVEL% +) + +where python >nul 2>nul +if %ERRORLEVEL% NEQ 0 ( + echo BH #2D hardware sweep: Python 3 is required 1>&2 + exit /b 127 +) +python -I "%~dp0bench-bh2d-hardware.py" %* +exit /b %ERRORLEVEL% From a4f39d6d41f765b1b2398596c91d805bc6e618f5 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:57:19 +0930 Subject: [PATCH 67/82] Record clean launcher and platform tools --- scripts/bench-bh2d-hardware.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 8e1d1f2..c396a5c 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -355,7 +355,8 @@ def toolchain_context(cargo_command: str, repo_root: Path) -> dict[str, Any]: system_tools: dict[str, dict[str, str]] = {} tool_directories: list[str] = [] for name in candidates: - path = shutil.which(name, path=discovery_path) + search_path = base_path if name == "git" else discovery_path + path = shutil.which(name, path=search_path) if path: resolved = Path(path).resolve() system_tools[name] = { @@ -1531,7 +1532,10 @@ def main() -> int: "host": { "platform": platform.platform(), "python": sys.version.split()[0], + "python_executable": str(Path(sys.executable).resolve()), + "python_sha256": sha256_file(Path(sys.executable).resolve()), "machine": platform.machine(), + "launch_boundary": "clean-environment-launcher-v1", }, "configuration": { "particles": particles_list, From ccbb75e27e9b91088ec6ae544d03c48beca1a23d Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:57:30 +0930 Subject: [PATCH 68/82] Default launcher Rust homes safely --- scripts/bench-bh2d-hardware-launch.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/bench-bh2d-hardware-launch.sh b/scripts/bench-bh2d-hardware-launch.sh index cf0ba02..7f2d428 100644 --- a/scripts/bench-bh2d-hardware-launch.sh +++ b/scripts/bench-bh2d-hardware-launch.sh @@ -23,8 +23,8 @@ HOME_VALUE=${HOME-} USER_VALUE=${USER-} LOGNAME_VALUE=${LOGNAME-} TMPDIR_VALUE=${TMPDIR-} -CARGO_HOME_VALUE=${CARGO_HOME-} -RUSTUP_HOME_VALUE=${RUSTUP_HOME-} +CARGO_HOME_VALUE=${CARGO_HOME:-$HOME_VALUE/.cargo} +RUSTUP_HOME_VALUE=${RUSTUP_HOME:-$HOME_VALUE/.rustup} exec "$ENV_BIN" -i \ GALAXY_BH2D_CLEAN_LAUNCH=1 \ From 39700546f0a52a5d5ad46b460007b0b2e810db90 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:58:38 +0930 Subject: [PATCH 69/82] Test clean launch and platform tool policies --- tests/test_bh2d_hardware_sweep.py | 122 +++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 2ea121b..9dfcc2a 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -10,6 +10,15 @@ ROOT = Path(__file__).resolve().parents[1] MODULE_PATH = ROOT / "scripts" / "bench-bh2d-hardware.py" +LAUNCHER_PATH = ROOT / "scripts" / "bench-bh2d-hardware-launch.sh" + + +def launched_env(): + env = os.environ.copy() + env["GALAXY_BH2D_CLEAN_LAUNCH"] = "1" + return env + + SPEC = importlib.util.spec_from_file_location("bench_bh2d_hardware", MODULE_PATH) sweep = importlib.util.module_from_spec(SPEC) assert SPEC.loader is not None @@ -625,17 +634,92 @@ def test_toolchain_context_records_resolved_binaries_and_versions(self): self.assertEqual(len(context["cargo"]["sha256"]), 64) self.assertEqual(len(context["rustc"]["sha256"]), 64) - def test_cli_requires_isolation_before_optional_imports(self): + def test_cli_requires_clean_launcher_and_isolated_python(self): import sys - for isolated, expected in ((False, 1), (True, 0)): + for isolated in (False, True): result = subprocess.run( [sys.executable, *(["-I"] if isolated else []), str(MODULE_PATH), "--output", "/unused", "--particles", "512", "--dry-run"], capture_output=True, text=True, ) - self.assertEqual(result.returncode, expected, result.stderr) - if not isolated: - self.assertIn("isolated Python", result.stderr) + self.assertEqual(result.returncode, 1) + self.assertIn("clean launcher", result.stderr) + + with tempfile.TemporaryDirectory() as tmp: + result = subprocess.run( + [ + "sh", + str(LAUNCHER_PATH), + "--output", + str(Path(tmp) / "plan"), + "--particles", + "512", + "--dry-run", + ], + capture_output=True, + text=True, + ) + self.assertEqual(result.returncode, 0, result.stderr) + + def test_platform_tool_paths_cover_windows_and_darwin(self): + with mock.patch.dict( + os.environ, + { + "SystemRoot": r"C:\\Windows", + "ProgramFiles": r"C:\\Program Files", + "USERPROFILE": r"C:\\Users\\tester", + }, + clear=True, + ): + windows = sweep.trusted_system_path("Windows") + self.assertIn(";", windows) + self.assertIn(r"C:\\Windows\\System32", windows) + self.assertIn(r"C:\\Program Files\\Git\\cmd", windows) + self.assertIn(r"C:\\Users\\tester\\.cargo\\bin", windows) + + darwin = sweep.trusted_system_path("Darwin") + self.assertIn("/usr/bin", darwin) + self.assertIn("/opt/homebrew/bin", darwin) + + def test_windows_toolchain_does_not_require_posix_cc(self): + records = { + "cargo": { + "path": "cargo.exe", + "executable": r"C:\\Rust\\cargo.exe", + "sha256": "a" * 64, + "invocation": r"C:\\Rust\\cargo.exe", + "proxy_sha256": None, + "version_verbose": "cargo fixture", + }, + "rustc": { + "path": "rustc.exe", + "executable": r"C:\\Rust\\rustc.exe", + "sha256": "b" * 64, + "invocation": r"C:\\Rust\\rustc.exe", + "proxy_sha256": None, + "version_verbose": "rustc fixture", + }, + } + + def which(name, path=None): + if name == "git": + return r"C:\\Program Files\\Git\\cmd\\git.exe" + if name == "link": + return r"C:\\VS\\bin\\link.exe" + return None + + with mock.patch.object(sweep, "require_no_build_environment_overrides"), \ + mock.patch.object(sweep.platform, "system", return_value="Windows"), \ + mock.patch.object(sweep, "tool_record", side_effect=lambda command, name, root: dict(records[name])), \ + mock.patch.object(sweep.shutil, "which", side_effect=which), \ + mock.patch.object(sweep, "sha256_file", return_value="c" * 64): + context = sweep.toolchain_context("cargo", ROOT) + + self.assertEqual(context["platform"], "Windows") + self.assertIn("git", context["system_tools"]) + self.assertIn("link", context["system_tools"]) + self.assertNotIn("cc", context["system_tools"]) + self.assertIn(";", context["build_path"]) def test_system_build_path_and_explicit_rustc(self): with mock.patch.dict( @@ -660,8 +744,11 @@ def test_system_build_path_and_explicit_rustc(self): "VK_LOADER_DRIVERS_DISABLE": "*other*", }, ): - env = sweep.build_environment({"rustc": {"executable": "/selected/bin/rustc"}}) - self.assertEqual(env["PATH"], "/usr/bin:/bin") + env = sweep.build_environment({ + "build_path": "/trusted/bin:/trusted/tools", + "rustc": {"executable": "/selected/bin/rustc"}, + }) + self.assertEqual(env["PATH"], "/trusted/bin:/trusted/tools") self.assertEqual(env["RUSTC"], "/selected/bin/rustc") self.assertNotIn("GIT_WORK_TREE", env) self.assertNotIn("LD_AUDIT", env) @@ -744,8 +831,12 @@ def test_invalid_workloads_do_not_create_output(self): ("--seed", str(2**64))): with self.subTest(flag=flag), tempfile.TemporaryDirectory() as tmp: output = Path(tmp) / "evidence" - result = subprocess.run([sys.executable, "-I", str(MODULE_PATH), - "--output", str(output), flag, value], capture_output=True) + result = subprocess.run( + [sys.executable, "-I", str(MODULE_PATH), + "--output", str(output), flag, value], + capture_output=True, + env=launched_env(), + ) self.assertEqual(result.returncode, 1) self.assertFalse(output.exists()) @@ -766,8 +857,10 @@ def test_non_utf8_run_log_preserved_and_manifest_failed(self): direct_probes=12, oracle_limit=4096, benchmark_warmup=2, benchmark_repeats=7, adapter=None, cargo="cargo", dry_run=False) context = {"cargo": {"executable": "/selected/cargo"}, - "rustc": {"executable": "/selected/rustc"}} - with mock.patch.object(sweep, "parse_args", return_value=args), \ + "rustc": {"executable": "/selected/rustc"}, + "build_path": "/usr/bin:/bin"} + with mock.patch.dict(os.environ, {sweep.CLEAN_LAUNCH_ENV: "1"}, clear=False), \ + mock.patch.object(sweep, "parse_args", return_value=args), \ mock.patch.object(sweep, "git_revision", return_value="fixture"), \ mock.patch.object(sweep, "require_clean_source_tree"), \ mock.patch.object(sweep, "cargo_config_context", return_value=[]), \ @@ -823,6 +916,7 @@ def test_successful_unusable_receipts_bind_run_log(self): context = { "cargo": {"executable": "/selected/cargo"}, "rustc": {"executable": "/selected/rustc"}, + "build_path": "/usr/bin:/bin", } def verifier_run(command, **kwargs): @@ -835,7 +929,8 @@ def verifier_run(command, **kwargs): b"exited cleanly but no usable receipt\n", ) - with mock.patch.object(sweep, "parse_args", return_value=args), \ + with mock.patch.dict(os.environ, {sweep.CLEAN_LAUNCH_ENV: "1"}, clear=False), \ + mock.patch.object(sweep, "parse_args", return_value=args), \ mock.patch.object(sweep, "git_revision", return_value="fixture"), \ mock.patch.object(sweep, "require_clean_source_tree"), \ mock.patch.object(sweep, "cargo_config_context", return_value=[]), \ @@ -936,6 +1031,7 @@ def test_collision_dry_run_rejects_fewer_than_four_particles(self): ], capture_output=True, text=True, + env=launched_env(), ) self.assertEqual(result.returncode, 1) self.assertIn("at least 4", result.stderr) @@ -965,6 +1061,7 @@ def test_dry_run_does_not_execute_cargo(self): ], capture_output=True, text=True, + env=launched_env(), ) self.assertEqual(result.returncode, 0, result.stderr) self.assertFalse(marker.exists()) @@ -985,6 +1082,7 @@ def test_dry_run_normalizes_output_and_cargo_paths(self): ], capture_output=True, text=True, + env=launched_env(), ) self.assertEqual(result.returncode, 0, result.stderr) command = __import__("shlex").split(result.stdout.strip()) From 82109d520f16ebc152aacd8570cb7da212698e9f Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:59:02 +0930 Subject: [PATCH 70/82] Exercise clean BH2D launcher in CI --- .github/workflows/native-gpu.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/native-gpu.yml b/.github/workflows/native-gpu.yml index cc37d90..8f719fa 100644 --- a/.github/workflows/native-gpu.yml +++ b/.github/workflows/native-gpu.yml @@ -3,9 +3,9 @@ name: Native GPU runtime on: push: branches: [main] - paths: ['runtime/**', 'rust/**', 'nbody/**', 'data/uff/**', 'tests/uff-reference.json', 'tests/test_bh2d_hardware_sweep.py', 'scripts/*gpu*', 'scripts/bench-bh2d-hardware.py', 'scripts/vast-runner.py', 'rust-toolchain.toml', '.github/workflows/native-gpu.yml', '.dockerignore', 'LICENSE', 'NOTICE.md'] + paths: ['runtime/**', 'rust/**', 'nbody/**', 'data/uff/**', 'tests/uff-reference.json', 'tests/test_bh2d_hardware_sweep.py', 'scripts/*gpu*', 'scripts/bench-bh2d-hardware.py', 'scripts/bench-bh2d-hardware-launch.sh', 'scripts/bench-bh2d-hardware-launch.cmd', 'scripts/vast-runner.py', 'rust-toolchain.toml', '.github/workflows/native-gpu.yml', '.dockerignore', 'LICENSE', 'NOTICE.md'] pull_request: - paths: ['runtime/**', 'rust/**', 'nbody/**', 'data/uff/**', 'tests/uff-reference.json', 'tests/test_bh2d_hardware_sweep.py', 'scripts/*gpu*', 'scripts/bench-bh2d-hardware.py', 'scripts/vast-runner.py', 'rust-toolchain.toml', '.github/workflows/native-gpu.yml', '.dockerignore', 'LICENSE', 'NOTICE.md'] + paths: ['runtime/**', 'rust/**', 'nbody/**', 'data/uff/**', 'tests/uff-reference.json', 'tests/test_bh2d_hardware_sweep.py', 'scripts/*gpu*', 'scripts/bench-bh2d-hardware.py', 'scripts/bench-bh2d-hardware-launch.sh', 'scripts/bench-bh2d-hardware-launch.cmd', 'scripts/vast-runner.py', 'rust-toolchain.toml', '.github/workflows/native-gpu.yml', '.dockerignore', 'LICENSE', 'NOTICE.md'] workflow_dispatch: permissions: @@ -61,7 +61,7 @@ jobs: python runtime/tests/test_runner.py python runtime/cuda/check_barnes_hut_layout.py python tests/test_bh2d_hardware_sweep.py - python -I scripts/bench-bh2d-hardware.py --dry-run --output "$RUNNER_TEMP/bh2d-plan" --particles 512,4096,8192 + sh scripts/bench-bh2d-hardware-launch.sh --dry-run --output "$RUNNER_TEMP/bh2d-plan" --particles 512,4096,8192 - name: Install software Vulkan for compute validation run: | sudo apt-get update -qq From 413a405d5fc63999faa43e9bdc91e6ca765f77e3 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:59:26 +0930 Subject: [PATCH 71/82] Document clean BH2D launch boundary --- docs/BARNES-HUT.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/BARNES-HUT.md b/docs/BARNES-HUT.md index 9928dd1..05494a1 100644 --- a/docs/BARNES-HUT.md +++ b/docs/BARNES-HUT.md @@ -307,7 +307,7 @@ Counts through 4,096 retain repeat-matched BH #2C timing. Larger points explicit Example on a real GPU: ```bash -python3 -I scripts/bench-bh2d-hardware.py \ +sh scripts/bench-bh2d-hardware-launch.sh \ --output runs/bh2d-hardware-sweep \ --adapter 0 ``` @@ -320,6 +320,6 @@ After real-hardware BH #2D receipts exist, BH #2E can evaluate scaling, memory/c ### Hardware harness launch and toolchain boundary -Launch with `python3 -I scripts/bench-bh2d-hardware.py ...`. Non-isolated direct invocation is rejected before optional imports. This prevents repository/PYTHONPATH modules from entering the standalone harness import path. Imported use by the unit-test runner is for host validation only. +Launch real evidence capture through the clean launcher: `sh scripts/bench-bh2d-hardware-launch.sh ...` on POSIX or `scripts\\bench-bh2d-hardware-launch.cmd ...` on native Windows. The launcher starts isolated Python under a newly constructed environment so inherited loader-injection variables are not resident in the evidence process. Direct Python execution is rejected. Imported use by the unit-test runner is for host validation only. -The hardware capture harness currently uses a POSIX system build PATH (`/usr/bin:/bin`). It records selected Cargo/rustc binaries separately from rustup proxies, executes the selected Cargo binary with an explicit recorded `RUSTC`, and fingerprints available system compiler/linker tools. The OS, installed system libraries and toolchain are trusted host prerequisites; this is provenance checking, not a sandbox against a hostile host. Git checks clear ambient `GIT_*` selectors and bind the checkout explicitly, including linked worktrees. Relative `CARGO_HOME` is interpreted from Cargo's repository working directory. Logs preserve raw bytes, receipt parser failures enter the failed-manifest path, and workload values are validated before any output is created. Manifests serialize strict JSON. +The harness now selects and records a platform-specific tool boundary. Linux uses the system compiler/linker/Git path, macOS uses the native system paths plus standard Homebrew locations, and Windows records Git plus any active MSVC/LLVM linker tools before narrowing the build PATH to those resolved tool directories. Cargo and rustc are recorded separately from rustup proxies and invoked through their resolved binaries. The OS, installed system libraries and toolchain are trusted host prerequisites; this is provenance checking, not a sandbox against a hostile host. Git checks clear ambient `GIT_*` selectors and bind the checkout explicitly, including linked worktrees. Relative `CARGO_HOME` is interpreted from Cargo's repository working directory. Logs preserve raw bytes, receipt parser failures enter the failed-manifest path, and workload values are validated before any output is created. Manifests serialize strict JSON. From 1c2e59a208dc678bf4477da8793d3fabe663e6b9 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:59:32 +0930 Subject: [PATCH 72/82] Use clean BH2D launcher in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9df1577..1fd4eb8 100644 --- a/README.md +++ b/README.md @@ -281,7 +281,7 @@ BH #2D correctness is exercised through software Vulkan in CI, but **software-Vu The remaining BH #2D evidence item has a fail-closed real-GPU sweep runner: ```bash -python3 -I scripts/bench-bh2d-hardware.py \ +sh scripts/bench-bh2d-hardware-launch.sh \ --output runs/bh2d-hardware-sweep \ --adapter 0 ``` From 816e31a146c63527cccaddf989b237f45c2e53f2 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:59:41 +0930 Subject: [PATCH 73/82] Document Windows BH2D tool policy --- docs/GPU-RUNTIME.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/GPU-RUNTIME.md b/docs/GPU-RUNTIME.md index 14e2bf0..603bc9a 100644 --- a/docs/GPU-RUNTIME.md +++ b/docs/GPU-RUNTIME.md @@ -29,7 +29,9 @@ Install Rust through [rustup](https://rustup.rs/) and a working native GPU drive The repository pins Rust 1.85.1. Linux uses Vulkan; Windows can use Vulkan or D3D12, and macOS uses Metal through [wgpu](https://github.com/gfx-rs/wgpu/tree/v24.0.5). The Linux path is the cloud target. Windows/Metal hardware execution has not been -validated by the Linux CI gate. +validated by the Linux CI gate. The BH #2D evidence harness itself is platform-aware: +use `scripts\\bench-bh2d-hardware-launch.cmd` on native Windows so Cargo can retain +its recorded MSVC/LLVM linker environment without requiring a POSIX `cc`. On Ubuntu, with the GPU vendor driver already installed: From 31568aeb8d1faed28f572ce79766e0a9be14ffe0 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:00:24 +0930 Subject: [PATCH 74/82] Keep Windows trusted base system-only --- scripts/bench-bh2d-hardware.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index c396a5c..889e16a 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -111,9 +111,6 @@ def trusted_system_path(system_name: str | None = None) -> str: if local: base = PureWindowsPath(local) / "Programs" / "Git" candidates.extend((str(base / "cmd"), str(base / "bin"))) - home = os.environ.get("USERPROFILE") - if home: - candidates.append(str(PureWindowsPath(home) / ".cargo" / "bin")) separator = ";" elif system_name == "Darwin": candidates = [ From c313e9ca38dfe27ff865b0222b06023cb562e04e Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:00:29 +0930 Subject: [PATCH 75/82] Align Windows trusted-path regression --- tests/test_bh2d_hardware_sweep.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index 9dfcc2a..bdb74ea 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -675,7 +675,6 @@ def test_platform_tool_paths_cover_windows_and_darwin(self): self.assertIn(";", windows) self.assertIn(r"C:\\Windows\\System32", windows) self.assertIn(r"C:\\Program Files\\Git\\cmd", windows) - self.assertIn(r"C:\\Users\\tester\\.cargo\\bin", windows) darwin = sweep.trusted_system_path("Darwin") self.assertIn("/usr/bin", darwin) From 682369c4dd665c8f222f2e55b8397c948748d7f1 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:01:53 +0930 Subject: [PATCH 76/82] Fix platform-policy test fixtures --- tests/test_bh2d_hardware_sweep.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index bdb74ea..dfe1b85 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -489,6 +489,7 @@ def test_dependency_source_context_binds_cached_source_bytes(self): toolchain = { "cargo": {"executable": "/selected/cargo"}, "rustc": {"executable": "/selected/rustc"}, + "build_path": "/usr/bin:/bin", } with mock.patch.object(sweep.subprocess, "run", return_value=completed): first = sweep.dependency_source_context(repo, toolchain) @@ -547,6 +548,7 @@ def test_in_repo_registry_dependency_is_still_hashed(self): toolchain = { "cargo": {"executable": "/selected/cargo"}, "rustc": {"executable": "/selected/rustc"}, + "build_path": "/usr/bin:/bin", } with mock.patch.object(sweep.subprocess, "run", return_value=completed): context = sweep.dependency_source_context(repo, toolchain) @@ -673,8 +675,8 @@ def test_platform_tool_paths_cover_windows_and_darwin(self): ): windows = sweep.trusted_system_path("Windows") self.assertIn(";", windows) - self.assertIn(r"C:\\Windows\\System32", windows) - self.assertIn(r"C:\\Program Files\\Git\\cmd", windows) + self.assertIn(r"C:\Windows\System32", windows) + self.assertIn(r"C:\Program Files\Git\cmd", windows) darwin = sweep.trusted_system_path("Darwin") self.assertIn("/usr/bin", darwin) From 657d45bab8320e394766dd26d26b650ee6a99fb5 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:46:02 +0930 Subject: [PATCH 77/82] Propagate Windows BH2D launcher failures --- scripts/bench-bh2d-hardware-launch.cmd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/bench-bh2d-hardware-launch.cmd b/scripts/bench-bh2d-hardware-launch.cmd index 90f9c9d..dd9b7ef 100644 --- a/scripts/bench-bh2d-hardware-launch.cmd +++ b/scripts/bench-bh2d-hardware-launch.cmd @@ -21,11 +21,11 @@ set "VK_LOADER_DRIVERS_DISABLE=" set "GALAXY_BH2D_CLEAN_LAUNCH=1" where py >nul 2>nul -if %ERRORLEVEL% EQU 0 ( - py -3 -I "%~dp0bench-bh2d-hardware.py" %* - exit /b %ERRORLEVEL% -) +if errorlevel 1 goto :python_fallback +py -3 -I "%~dp0bench-bh2d-hardware.py" %* +exit /b %ERRORLEVEL% +:python_fallback where python >nul 2>nul if %ERRORLEVEL% NEQ 0 ( echo BH #2D hardware sweep: Python 3 is required 1>&2 From 787820d6cc1f8888ca21ee5d0e8fa7ad6082e7d3 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:46:13 +0930 Subject: [PATCH 78/82] Route GPU guide through clean BH2D launcher --- docs/GPU-RUNTIME.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/GPU-RUNTIME.md b/docs/GPU-RUNTIME.md index 603bc9a..e54f244 100644 --- a/docs/GPU-RUNTIME.md +++ b/docs/GPU-RUNTIME.md @@ -280,7 +280,7 @@ Tree-build stage families are batched into command buffers before synchronizatio For repeatable real-hardware scaling evidence, use the fail-closed sweep runner: ```bash -python3 -I scripts/bench-bh2d-hardware.py \ +sh scripts/bench-bh2d-hardware-launch.sh \ --output runs/bh2d-hardware-sweep \ --adapter 0 ``` From 4a9ec4ab8eff42c0231d7cf50f6f5d49c43c5fb3 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:46:25 +0930 Subject: [PATCH 79/82] Validate frozen BH2D tree declarations --- scripts/bench-bh2d-hardware.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/bench-bh2d-hardware.py b/scripts/bench-bh2d-hardware.py index 889e16a..f0b0253 100644 --- a/scripts/bench-bh2d-hardware.py +++ b/scripts/bench-bh2d-hardware.py @@ -50,6 +50,10 @@ TREE_META_BYTES = 32 BOUNDS_RECORD_BYTES = 16 RADIX_DIGITS = 16 +TREE_ORDERING = ( + "stable-lsd-radix-4bit-morton-code; resident body index retained for equal Morton keys" +) +TREE_LAYOUT = "sparse-level-order-slot=depth*N+group_start" CLEAN_LAUNCH_ENV = "GALAXY_BH2D_CLEAN_LAUNCH" BUILD_ENV_EXACT = { "LD_PRELOAD", "LD_AUDIT", "LD_LIBRARY_PATH", "LIBRARY_PATH", "COMPILER_PATH", @@ -1084,6 +1088,14 @@ def validate_receipt( identity = adapter_identity(root.get("gpu"), adapter_selector) tree = require_object(root.get("tree"), "receipt.tree") + require( + tree.get("ordering") == TREE_ORDERING, + "receipt.tree.ordering does not match the frozen stable Morton ordering", + ) + require( + tree.get("layout") == TREE_LAYOUT, + "receipt.tree.layout does not match the frozen sparse level-order layout", + ) require(tree.get("repeat_rebuild_matches") is True, "same-state repeat tree checksum changed") require_checksum( tree.get("initial_checksum_fnv_mix64"), From 5aba347d58af8c822afd2e64353fa7cc93b9cf8e Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:46:39 +0930 Subject: [PATCH 80/82] Cover frozen BH2D tree declarations --- tests/test_bh2d_hardware_sweep.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_bh2d_hardware_sweep.py b/tests/test_bh2d_hardware_sweep.py index dfe1b85..67f2bf3 100644 --- a/tests/test_bh2d_hardware_sweep.py +++ b/tests/test_bh2d_hardware_sweep.py @@ -134,6 +134,8 @@ def receipt_for( "evolution_tree_builds": steps + 1, "parallel_tree_buffer_bytes": sweep.expected_parallel_tree_buffer_bytes(particles), "tree": { + "ordering": sweep.TREE_ORDERING, + "layout": sweep.TREE_LAYOUT, "initial_checksum_fnv_mix64": "1111111111111111", "final_checksum_fnv_mix64": "2222222222222222", "repeat_checksum_fnv_mix64": "2222222222222222", @@ -1259,6 +1261,17 @@ def test_execution_counters_require_integer_types(self): with self.assertRaisesRegex(sweep.SweepError, field): sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_tree_representation_declarations_are_frozen(self): + for field, value, message in ( + ("ordering", "not-stable-or-morton", "stable Morton ordering"), + ("layout", "arbitrary-layout", "sparse level-order layout"), + ): + with self.subTest(field=field): + receipt = receipt_for(512) + receipt["tree"][field] = value + with self.assertRaisesRegex(sweep.SweepError, message): + sweep.validate_receipt(receipt, **validation_kwargs(512)) + def test_repeat_tree_mismatch_is_rejected(self): receipt = receipt_for(512) receipt["tree"]["repeat_rebuild_matches"] = False From 4ae0f05827cf32a2af1da5dd70391380c4993dc0 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:46:49 +0930 Subject: [PATCH 81/82] Test Windows BH2D launcher exit status --- .github/workflows/native-gpu.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/native-gpu.yml b/.github/workflows/native-gpu.yml index 8f719fa..5a6b521 100644 --- a/.github/workflows/native-gpu.yml +++ b/.github/workflows/native-gpu.yml @@ -25,6 +25,11 @@ jobs: python-version: '3.12' - name: Check SSH runner and result extraction on Windows run: python runtime/tests/test_runner.py + - name: Check BH2D Windows launcher propagates Python failure + shell: cmd + run: | + call scripts\bench-bh2d-hardware-launch.cmd --particles 512 --dry-run + if %ERRORLEVEL% EQU 0 exit /b 1 runner-python: runs-on: ubuntu-24.04 From da9bf28d6c631116b8b9ce8c9ea320c7e7a0aed1 Mon Sep 17 00:00:00 2001 From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:47:55 +0930 Subject: [PATCH 82/82] Make Windows launcher regression self-checking --- .github/workflows/native-gpu.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/native-gpu.yml b/.github/workflows/native-gpu.yml index 5a6b521..2180f43 100644 --- a/.github/workflows/native-gpu.yml +++ b/.github/workflows/native-gpu.yml @@ -30,6 +30,7 @@ jobs: run: | call scripts\bench-bh2d-hardware-launch.cmd --particles 512 --dry-run if %ERRORLEVEL% EQU 0 exit /b 1 + exit /b 0 runner-python: runs-on: ubuntu-24.04