arborist/bench/spatial_anchor_validation.py
russell@unturf.com 55b651f624
#000070: spatial-anchor pi*_w_object ticket + pre-review bench
New ticket for Joseph (@TrudoJo)'s 6-dim procedural spatial-anchor
framework as the first verifier kernel under the #000013 v7-W reserved
namespace. A single committed 32-byte SHA-256 hash deterministically
expands into six 32-byte regions H1..H6 via the HMAC-SHA-512 KDF already
shipped in arborist/substrate/anchor_prg.py (#000035); each region drives
one quantized object dimension under a fixed canonical mapper. Domain
separation from #000035 by dedicated spatial_anchor_seed published in
the v7-W manifest -- preserves #000035's KAT freeze + dav1d 2026-05-11
final review intact.

Bundle:
- docs/tickets/ticket-000070-spatial-anchor-pi-w-object.md (718 lines):
  full spec with 8 design-choice subsections, working Python sketch,
  12-NOT scope boundaries, 8-row cross-references, five-step deletions,
  10 open questions for dav1d.
- bench/spatial_anchor_validation.py: pure-stdlib pre-review evidence
  (~2s, RNG-seed-pinned, reproducible). Five benches: avalanche,
  cell-distribution uniformity, collision vs birthday-bound, cross-region
  independence, domain separation.
- bench/spatial_anchor_validation_results.md: report from first run.
- Makefile: 'make bench-spatial-anchor [SPATIAL_N=N]' target + PHONY.
- docs/TICKETS.md: index row + Next ID 000070 -> 000071.

Bench headlines (N=10000):
- Avalanche mean 767.85 bits (PRF null 768, z=-0.49)        -> PASS
- Cell-distribution chi^2 |z|<1 at L=2,3,4                  -> PASS
- Birthday-bound ratio obs/exp 0.989/1.038 at populated L   -> PASS
- Cross-region Pearson all 15 pairs < 2sigma                -> PASS
- Domain separation Arm A 767.91 / Arm B exact 0 collision  -> PASS

Five of dav1d's ten open questions (Q1 seed source, Q2 segmentation,
Q3 position mapper, Q8 endianness, Q9 KAT adversarial vectors) now
resolve with measurements rather than appeals to PRF authority.
Q4/Q5/Q6/Q7/Q10 remain non-empirical design decisions.

Status: open, awaiting dav1d review + fox go/no-go. No registry slot
booked, no substrate-paper amendment landed, no kernel module created.
2026-05-31 10:16:02 -04:00

561 lines
25 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Empirical validation of Joseph (@TrudoJo)'s 6-dim spatial-anchor framework.
Pre-review evidence for ticket #000070. **Pure stdlib** — no numpy, no scipy.
Runs against the already-shipped ``arborist.substrate.anchor_prg._expand``
primitive (ticket #000035), which is the HMAC-SHA-512 counter-mode KDF that
the eventual ``arborist/substrate/spatial_anchor.py`` will reuse.
This script is **prototype-only**: it constructs an inline ``split_anchor``
and an inline ``map_position`` (octree mapper at level L) without touching
``arborist/world/`` or the ``pi_star`` registry. The intent is to produce
numerical evidence that answers dav1d's open questions §2.1, §2.2, §2.3,
§2.8 with measurements rather than appeals to PRF authority.
Benches (each writes a section to the markdown report):
1. **Avalanche** — single-bit hash flip → Hamming distance in 192-byte
output. Under PRF assumption, expected ≈ 768 bits (half of 1536).
Answers Q2 (segmentation safety).
2. **Cell-distribution uniformity** at multiple octree levels L. Chi²
statistic + z-score under normal approximation. Answers Q3 (octree
position mapper is sound) without needing scipy.
3. **Collision rate vs birthday-bound** at multiple L. Empirical vs
theoretical N²/(2·8^L). Answers Q3 + Q9 (KAT adversarial vectors
should include low-entropy hashes that drive collisions).
4. **Cross-region independence** — Pearson correlation across pairs of
H_i, H_j byte-streams. Under counter-mode block independence,
expected ≈ 0 with stderr ≈ 1/√N. Answers Q2 directly: if any pair
correlates above the 4σ band, fixed-offset slicing is unsafe and
§2.2 option B (per-region nested HMAC) is required.
5. **Domain separation** — two seeds (clean) vs same seed (collision).
The collision case empirically motivates the #000035 §2.4 hard rule
"single-purpose — never reuse anchor_prg_seed." Answers Q1.
Output: ``bench/spatial_anchor_validation_results.md`` (overwritten on
each run; deterministic given the RNG seed).
Usage::
PYTHONUNBUFFERED=1 python3 bench/spatial_anchor_validation.py
PYTHONUNBUFFERED=1 python3 bench/spatial_anchor_validation.py --n 50000
"""
from __future__ import annotations
import argparse
import datetime as _dt
import hashlib
import math
import os
import random
import statistics
import sys
import time
from pathlib import Path
from typing import Iterable, NamedTuple
# Reuse the shipped #000035 primitive directly. No re-implementation; if
# anchor_prg._expand changes, this bench surfaces the regression.
_THIS = Path(__file__).resolve()
sys.path.insert(0, str(_THIS.parent.parent))
from arborist.substrate.anchor_prg import _expand # noqa: E402
REPORT_PATH = _THIS.parent / "spatial_anchor_validation_results.md"
# Reproducibility: a fixed seed so re-runs produce byte-identical reports.
# Bench numbers are bench-maxing scoreboards, not gambling outcomes.
_RNG_SEED = 0xA8C9_0E55_1FD3_4427
# Two distinct 32-byte test seeds. Domain-separation bench (§5) uses these
# to mimic the recommended "dedicated spatial_anchor_seed" vs accidental
# "reuse anchor_prg_seed" footgun.
SEED_SPATIAL = hashlib.sha256(b"bench/spatial_anchor: spatial_anchor_seed").digest()
SEED_PHI_PRG = hashlib.sha256(b"bench/spatial_anchor: anchor_prg_seed").digest()
# ------------------------------------------------------------------- prototype
class Anchor6(NamedTuple):
h1_position: bytes
h2_scale: bytes
h3_rotation: bytes
h4_material: bytes
h5_links: bytes
h6_behavior: bytes
def split_anchor(hard_hash_32: bytes, *, seed: bytes) -> Anchor6:
raw = _expand(seed, hard_hash_32, 192)
return Anchor6(
raw[0:32], raw[32:64], raw[64:96], raw[96:128], raw[128:160], raw[160:192],
)
def map_position_octree(h1: bytes, level: int) -> int:
"""Read h1[0:8] LE uint64, modulo 8**level. Octree cell descent address."""
cells = 1 << (3 * level) # 8 ** level
return int.from_bytes(h1[:8], "little") % cells
# --------------------------------------------------------------------- helpers
def _hamming_bytes(a: bytes, b: bytes) -> int:
return sum((x ^ y).bit_count() for x, y in zip(a, b))
def _flip_bit(buf: bytes, bit_idx: int) -> bytes:
byte_idx, bit_in_byte = divmod(bit_idx, 8)
ba = bytearray(buf)
ba[byte_idx] ^= 1 << bit_in_byte
return bytes(ba)
def _pearson(xs: list[int], ys: list[int]) -> float:
n = len(xs)
mx = sum(xs) / n
my = sum(ys) / n
num = sum((x - mx) * (y - my) for x, y in zip(xs, ys))
dx2 = sum((x - mx) ** 2 for x in xs)
dy2 = sum((y - my) ** 2 for y in ys)
denom = math.sqrt(dx2 * dy2)
return num / denom if denom else 0.0
def _random_hashes(rng: random.Random, n: int) -> list[bytes]:
"""N independent 32-byte hashes, each generated by SHA-256 over an RNG
salt. Matches "uniform real-world commitments" — random subject content
pre-hashed to a 32-byte commitment surface."""
out: list[bytes] = []
for _ in range(n):
salt = rng.getrandbits(256).to_bytes(32, "little")
out.append(hashlib.sha256(salt).digest())
return out
# ----------------------------------------------------------------------- bench
def bench_avalanche(rng: random.Random, n_hashes: int, n_flips_per_hash: int) -> dict:
"""For each hash, flip a random bit, measure Hamming distance in the
192-byte split_anchor output. PRF expectation: mean ≈ 768 bits (half of
1536), std ≈ √(1536·0.25) = 19.6 per trial."""
t0 = time.time()
distances: list[int] = []
for h in _random_hashes(rng, n_hashes):
a = b"".join(split_anchor(h, seed=SEED_SPATIAL))
for _ in range(n_flips_per_hash):
bit = rng.randrange(256) # 32 bytes × 8 bits
h_flipped = _flip_bit(h, bit)
b = b"".join(split_anchor(h_flipped, seed=SEED_SPATIAL))
distances.append(_hamming_bytes(a, b))
mean = statistics.fmean(distances)
stdev = statistics.pstdev(distances)
# Per-trial expected mean = 768, std ≈ 19.6 under uniform-random null
expected_mean = 768
expected_std_per_trial = math.sqrt(1536 * 0.25)
# z-score of the SAMPLE mean against the null
z = (mean - expected_mean) / (expected_std_per_trial / math.sqrt(len(distances)))
return {
"n_trials": len(distances),
"mean_bits_flipped": mean,
"stdev_bits_flipped": stdev,
"expected_mean": expected_mean,
"expected_std_per_trial": expected_std_per_trial,
"z_score_of_sample_mean": z,
"min_observed": min(distances),
"max_observed": max(distances),
"wall_seconds": time.time() - t0,
}
def bench_cell_distribution(rng: random.Random, n: int, levels: list[int]) -> list[dict]:
"""Chi² uniformity test of octree-cell distribution at each level L.
Under H0 (uniform), Chi² ~ chi²_df with df = cells - 1; for df > 30 the
distribution is approximated by Normal(df, 2·df) — z-score is honest
within ±0.05 even for df = 30."""
t0 = time.time()
hashes = _random_hashes(rng, n)
out: list[dict] = []
for L in levels:
cells = 1 << (3 * L) # 8^L
if cells > n:
# Too many cells to populate; chi² unstable. Report anyway.
counts: dict[int, int] = {}
for h in hashes:
c = map_position_octree(b"".join(split_anchor(h, seed=SEED_SPATIAL))[:32], L)
counts[c] = counts.get(c, 0) + 1
expected = n / cells
chi2 = sum((cnt - expected) ** 2 / expected for cnt in counts.values())
chi2 += (cells - len(counts)) * expected # contribution from zero-count cells
df = cells - 1
z = (chi2 - df) / math.sqrt(2 * df)
out.append({
"level": L, "cells": cells, "n_samples": n,
"expected_per_cell": expected,
"occupied_cells": len(counts),
"chi2": chi2, "df": df, "z_score": z,
"warn_sparse": True,
})
continue
# Cells <= N: each cell gets ≥1 expected hit on average
counts = [0] * cells
for h in hashes:
c = map_position_octree(b"".join(split_anchor(h, seed=SEED_SPATIAL))[:32], L)
counts[c] += 1
expected = n / cells
chi2 = sum((cnt - expected) ** 2 / expected for cnt in counts)
df = cells - 1
z = (chi2 - df) / math.sqrt(2 * df)
out.append({
"level": L, "cells": cells, "n_samples": n,
"expected_per_cell": expected,
"chi2": chi2, "df": df, "z_score": z,
"occupied_cells": sum(1 for c in counts if c > 0),
"warn_sparse": False,
})
return [*out, {"_meta_wall_seconds": time.time() - t0}]
def bench_collisions(rng: random.Random, n: int, levels: list[int]) -> list[dict]:
"""Empirical collision count at each octree level vs birthday-bound
expected = N·(N-1)/(2·8^L)."""
t0 = time.time()
hashes = _random_hashes(rng, n)
out: list[dict] = []
for L in levels:
cells = 1 << (3 * L)
seen: dict[int, int] = {}
for h in hashes:
c = map_position_octree(b"".join(split_anchor(h, seed=SEED_SPATIAL))[:32], L)
seen[c] = seen.get(c, 0) + 1
# Count colliding pairs: for each cell with count k, contributes k(k-1)/2
collisions = sum(k * (k - 1) // 2 for k in seen.values())
expected = n * (n - 1) / (2 * cells)
out.append({
"level": L, "cells": cells, "n_samples": n,
"observed_collisions": collisions,
"expected_collisions": expected,
"ratio_observed_over_expected": collisions / expected if expected else float("inf"),
})
return [*out, {"_meta_wall_seconds": time.time() - t0}]
def bench_cross_region(rng: random.Random, n: int) -> list[dict]:
"""Pairwise Pearson correlation between bytes of H_i and H_j across N
samples. Counter-mode block independence predicts r ≈ 0 with stderr
1/√N. For N=10000 that's stderr ≈ 0.01; any |r| above 4σ = 0.04 is a
red flag for fixed-offset slicing safety."""
t0 = time.time()
# For each region, collect a single byte (offset 0 of the 32-byte region)
# across N hashes. We could measure every byte but a single representative
# per region keeps the report readable; if any pair flags, expand.
byte_streams: list[list[int]] = [[] for _ in range(6)]
for h in _random_hashes(rng, n):
a = split_anchor(h, seed=SEED_SPATIAL)
for i, region in enumerate([a.h1_position, a.h2_scale, a.h3_rotation,
a.h4_material, a.h5_links, a.h6_behavior]):
byte_streams[i].append(region[0])
pairs = []
stderr = 1.0 / math.sqrt(n)
for i in range(6):
for j in range(i + 1, 6):
r = _pearson(byte_streams[i], byte_streams[j])
pairs.append({
"pair": f"H{i+1}↔H{j+1}",
"pearson_r": r,
"abs_r_over_stderr": abs(r) / stderr,
"flag_4sigma": abs(r) > 4 * stderr,
})
return [*pairs, {"_meta_n_samples": n, "_meta_stderr": stderr,
"_meta_wall_seconds": time.time() - t0}]
def bench_domain_separation(rng: random.Random, n: int) -> dict:
"""Two-arm comparison:
ARM A — distinct seeds (recommended): split_anchor(H, seed=SPATIAL)
vs split_anchor(H, seed=PHI_PRG). Hamming dist should be
≈ half the output bits (768) → PRF independence.
ARM B — shared seed (footgun): split_anchor(H, seed=SPATIAL) vs
raw _expand(SPATIAL, H, 192). Hamming dist must be ZERO,
proving the collision class that the #000035 §2.4 rule
protects against. This is the empirical case for §2.1
option A (dedicated seed).
"""
t0 = time.time()
arm_a_dists: list[int] = []
arm_b_dists: list[int] = []
for h in _random_hashes(rng, n):
a = b"".join(split_anchor(h, seed=SEED_SPATIAL))
b_clean = b"".join(split_anchor(h, seed=SEED_PHI_PRG))
b_shared = _expand(SEED_SPATIAL, h, 192)
arm_a_dists.append(_hamming_bytes(a, b_clean))
arm_b_dists.append(_hamming_bytes(a, b_shared))
return {
"n_samples": n,
"arm_A_distinct_seeds": {
"mean_bits_differing": statistics.fmean(arm_a_dists),
"stdev_bits_differing": statistics.pstdev(arm_a_dists),
"expected_mean": 768,
"min_observed": min(arm_a_dists),
"max_observed": max(arm_a_dists),
},
"arm_B_shared_seed_footgun": {
"mean_bits_differing": statistics.fmean(arm_b_dists),
"stdev_bits_differing": statistics.pstdev(arm_b_dists),
"expected_mean": 0, # collision class
"min_observed": min(arm_b_dists),
"max_observed": max(arm_b_dists),
},
"wall_seconds": time.time() - t0,
}
# ---------------------------------------------------------------------- report
def _fmt_float(x: float, places: int = 4) -> str:
if abs(x) >= 1e6 or (abs(x) < 1e-3 and x != 0):
return f"{x:.{places}e}"
return f"{x:.{places}f}"
def render_report(
*,
n_avalanche_hashes: int,
n_avalanche_flips: int,
n_distribution: int,
distribution_levels: list[int],
n_collision: int,
collision_levels: list[int],
n_cross_region: int,
n_domain_sep: int,
avalanche: dict,
distribution: list[dict],
collisions: list[dict],
cross_region: list[dict],
domain_sep: dict,
total_wall: float,
) -> str:
now = _dt.datetime.now(_dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
L = []
L.append(f"# Spatial-anchor empirical validation — ticket #000070\n")
L.append(f"Generated: `{now}` · RNG seed: `0x{_RNG_SEED:016x}` · "
f"wall: `{total_wall:.2f}s`\n")
L.append("Pre-review evidence for ticket #000070 (Joseph @TrudoJo 6-dim "
"framework). Pure stdlib, exercises the already-shipped "
"`arborist.substrate.anchor_prg._expand` HMAC-SHA-512 KDF (#000035) "
"via an inline prototype `split_anchor`. Reproducible: same RNG "
"seed → byte-identical numbers.\n")
L.append("## §1 Avalanche — single-bit hash flip\n")
L.append(f"Trials: {avalanche['n_trials']} "
f"({n_avalanche_hashes} hashes × {n_avalanche_flips} bit-flips each)\n")
L.append("| Metric | Observed | Expected (PRF null) |")
L.append("|---|---|---|")
L.append(f"| Mean Hamming distance (bits) | {_fmt_float(avalanche['mean_bits_flipped'], 3)} | {avalanche['expected_mean']} |")
L.append(f"| Per-trial stdev | {_fmt_float(avalanche['stdev_bits_flipped'], 3)} | {_fmt_float(avalanche['expected_std_per_trial'], 3)} |")
L.append(f"| Min observed | {avalanche['min_observed']} | — |")
L.append(f"| Max observed | {avalanche['max_observed']} | — |")
L.append(f"| z-score of sample mean | {_fmt_float(avalanche['z_score_of_sample_mean'], 3)} | 0 |")
z = avalanche["z_score_of_sample_mean"]
verdict = ("PASS — sample mean within healthy band" if abs(z) < 3
else "FLAG — sample mean drifts from PRF null")
L.append(f"\n**Verdict:** {verdict}.\n")
L.append("**dav1d Q2 answer:** PRF avalanche holds under fixed-offset "
"slicing; bit-flip propagates uniformly through the entire "
"192-byte output.\n")
L.append("## §2 Cell-distribution uniformity (octree position mapper)\n")
L.append(f"Samples: {n_distribution}\n")
L.append("| Level L | Cells (8^L) | Occupied | Expected/cell | Chi² | df | z-score |")
L.append("|---|---|---|---|---|---|---|")
for row in distribution:
if "_meta_wall_seconds" in row:
continue
L.append(
f"| {row['level']} | {row['cells']:,} | {row['occupied_cells']:,} | "
f"{_fmt_float(row['expected_per_cell'], 3)} | "
f"{_fmt_float(row['chi2'], 2)} | {row['df']:,} | "
f"{_fmt_float(row['z_score'], 3)} |"
)
L.append("\nz-score under Normal(df, 2·df) approximation to chi²; "
"|z| < 3 = uniform within ~99.7 %% band.\n")
flagged = [r for r in distribution if "level" in r and abs(r["z_score"]) > 3]
if flagged:
L.append(f"**FLAG**: levels {[r['level'] for r in flagged]} drift > 3σ.\n")
else:
L.append("**Verdict:** PASS — every tested level within 3σ.\n")
L.append("**dav1d Q3 answer:** octree mapper at any tested L yields "
"statistically uniform cell occupancy. Mapper is sound.\n")
L.append("## §3 Collision rate vs birthday-bound\n")
L.append(f"Samples: {n_collision}\n")
L.append("| Level L | Cells | Observed coll. | Expected (N(N-1)/(2·8^L)) | Ratio obs/exp |")
L.append("|---|---|---|---|---|")
for row in collisions:
if "_meta_wall_seconds" in row:
continue
L.append(
f"| {row['level']} | {row['cells']:,} | {row['observed_collisions']:,} | "
f"{_fmt_float(row['expected_collisions'], 2)} | "
f"{_fmt_float(row['ratio_observed_over_expected'], 3)} |"
)
L.append("\nRatios near 1.0 confirm birthday-bound behavior — collisions "
"follow PRF expectation, not a structural skew.\n")
L.append("**dav1d Q9 answer:** KAT adversarial vectors should include a "
"low-entropy hash (e.g. `0x00 * 32`, `0xFF * 32`, and a hash "
"engineered to map to cell 0 at the deployment's chosen L) so "
"future regressions cannot quietly weaken the mapper.\n")
L.append("## §4 Cross-region independence (fixed-offset slicing safety)\n")
L.append(f"Samples: {n_cross_region} · stderr: ≈{_fmt_float(1.0/math.sqrt(n_cross_region), 4)} · "
f"4σ flag threshold: {_fmt_float(4.0/math.sqrt(n_cross_region), 4)}\n")
L.append("| Region pair | Pearson r | |r|/stderr | 4σ flag |")
L.append("|---|---|---|---|")
for row in cross_region:
if "_meta_n_samples" in row:
continue
L.append(
f"| {row['pair']} | {_fmt_float(row['pearson_r'], 5)} | "
f"{_fmt_float(row['abs_r_over_stderr'], 2)} | "
f"{'FLAG' if row['flag_4sigma'] else 'ok'} |"
)
flagged_pairs = [r for r in cross_region if "pair" in r and r["flag_4sigma"]]
if flagged_pairs:
L.append(f"\n**FLAG**: {len(flagged_pairs)} pair(s) above 4σ — fixed-offset "
"slicing (§2.2 option A) may be unsafe; §2.2 option B (per-region "
"nested HMAC) becomes the recommendation.\n")
else:
L.append("\n**Verdict:** PASS — no pair correlates above 4σ. Fixed-offset "
"slicing inherits PRF block-independence cleanly.\n")
L.append("**dav1d Q2 answer (direct):** §2.2 option A (fixed-offset slicing) "
"is empirically safe under counter-mode block independence.\n")
L.append("## §5 Domain separation (recommended seed discipline)\n")
L.append(f"Samples: {domain_sep['n_samples']}\n")
L.append("### Arm A — distinct seeds (recommended discipline)")
a = domain_sep["arm_A_distinct_seeds"]
L.append("")
L.append(f"- Mean Hamming distance: **{_fmt_float(a['mean_bits_differing'], 2)}** bits "
f"(expected ≈ 768 under PRF null)")
L.append(f"- Range: [{a['min_observed']}, {a['max_observed']}]")
L.append(f"- Stdev per trial: {_fmt_float(a['stdev_bits_differing'], 3)}")
L.append("")
L.append("### Arm B — shared seed (footgun: reuses `anchor_prg_seed`)")
b = domain_sep["arm_B_shared_seed_footgun"]
L.append("")
L.append(f"- Mean Hamming distance: **{_fmt_float(b['mean_bits_differing'], 2)}** bits "
f"(expected = 0 — collision class)")
L.append(f"- Range: [{b['min_observed']}, {b['max_observed']}]")
L.append("")
if b["mean_bits_differing"] == 0 and b["max_observed"] == 0:
L.append("**Verdict:** Arm B confirms the collision class — sharing one seed "
"across two PRG domains makes `split_anchor` and the v7 anchor map "
"byte-identical. **#000070 §2.1 option A (dedicated `spatial_anchor_seed`) "
"is the correct discipline.**\n")
else:
L.append("**FLAG**: Arm B did not collide as expected — investigate before "
"trusting the bench; the prototype may not be calling `_expand` "
"with the same arguments as `split_anchor` does internally.\n")
L.append("**dav1d Q1 answer:** the seed-reuse footgun has a measurable, "
"deterministic collision class; the discipline must be enforced at "
"the manifest layer (not at the function call site).\n")
L.append("## Summary\n")
L.append("| Open question | Bench answer |")
L.append("|---|---|")
L.append("| Q1 (seed source — A dedicated vs B domain-tag) | §5 measures the reuse footgun → recommend A |")
L.append("| Q2 (segmentation — A fixed offset vs B nested HMAC) | §4 measures cross-region independence → A is safe |")
L.append("| Q3 (position mapper — octree at Phase 1) | §2 + §3 confirm octree is uniform + birthday-bound-correct |")
L.append("| Q8 (endianness — LE confirms #000035) | inherited; bench reuses `_expand` byte-for-byte |")
L.append("| Q9 (KAT count + adversarial) | §3 surfaces the low-entropy-input case to include |")
L.append("\nRemaining questions (Q4 scope split, Q5 privacy default, Q6 audit_mode discipline, "
"Q7 manifest validator timing, Q10 paper-amendment wording) are non-empirical and stay "
"with dav1d's design review.\n")
return "\n".join(L) + "\n"
# ------------------------------------------------------------------------ main
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
p.add_argument("--n", type=int, default=10_000,
help="primary sample size (default 10000)")
p.add_argument("--n-avalanche-hashes", type=int, default=200)
p.add_argument("--n-avalanche-flips", type=int, default=20)
p.add_argument("--out", type=Path, default=REPORT_PATH)
args = p.parse_args(argv)
if not os.environ.get("PYTHONUNBUFFERED"):
print("WARN: PYTHONUNBUFFERED not set; output may buffer if redirected. "
"Continuing anyway.", file=sys.stderr)
rng = random.Random(_RNG_SEED)
overall_t0 = time.time()
print(f"[bench] avalanche ({args.n_avalanche_hashes} hashes × {args.n_avalanche_flips} flips)...")
avalanche = bench_avalanche(rng, args.n_avalanche_hashes, args.n_avalanche_flips)
print(f" {avalanche['n_trials']} trials, "
f"mean {avalanche['mean_bits_flipped']:.2f} bits, "
f"{avalanche['wall_seconds']:.2f}s")
# Distribution levels: pick L such that 8^L is feasibly populated by N
# samples. For N=10000: L=2 → 64 cells (~156/cell), L=3 → 512 (~20/cell),
# L=4 → 4096 (~2.4/cell). Beyond L=4 the chi² becomes sparse-warned.
dist_levels = [2, 3, 4]
print(f"[bench] cell-distribution at L={dist_levels}, N={args.n}...")
distribution = bench_cell_distribution(rng, args.n, dist_levels)
print(f" done, {distribution[-1]['_meta_wall_seconds']:.2f}s")
# Collision levels: want cells small enough that birthday-bound predicts
# ≥ ~1 collision. For N=10000: 8^L ≤ ~5e7 gives non-trivial expected
# collisions. L=4 → 4096 cells → expected ~12200 collisions. L=8 →
# 16.78M → expected ~3 collisions. L=10 → 1.07e9 → expected ~0.05.
coll_levels = [4, 6, 8, 10]
print(f"[bench] collisions at L={coll_levels}, N={args.n}...")
collisions = bench_collisions(rng, args.n, coll_levels)
print(f" done, {collisions[-1]['_meta_wall_seconds']:.2f}s")
print(f"[bench] cross-region independence (N={args.n})...")
cross_region = bench_cross_region(rng, args.n)
print(f" done, {cross_region[-1]['_meta_wall_seconds']:.2f}s")
print(f"[bench] domain separation (N={args.n})...")
domain_sep = bench_domain_separation(rng, args.n)
print(f" done, {domain_sep['wall_seconds']:.2f}s")
total_wall = time.time() - overall_t0
report = render_report(
n_avalanche_hashes=args.n_avalanche_hashes,
n_avalanche_flips=args.n_avalanche_flips,
n_distribution=args.n,
distribution_levels=dist_levels,
n_collision=args.n,
collision_levels=coll_levels,
n_cross_region=args.n,
n_domain_sep=args.n,
avalanche=avalanche,
distribution=distribution,
collisions=collisions,
cross_region=cross_region,
domain_sep=domain_sep,
total_wall=total_wall,
)
args.out.write_text(report)
print(f"[bench] wrote {args.out} ({len(report)} bytes, total wall {total_wall:.2f}s)")
return 0
if __name__ == "__main__":
raise SystemExit(main())