wip(assembler): round L31 -- FI recipe column audit corrected; recipe-stamped audit runner
On the real FloatingIslands recipe, column caps sit inside anchor-island rock and native bedrock; the old check sampled the route foot. The audit now samples each planned cap against the native field (negative control: a cap moved into air is detected). The audit runner records requested vs actual recipe and exits on mismatch. All 16 passage rows (4 styles x 2 recipes x 2 compilers) pass. The readable shader uses a smooth tint. Interim: FI Gradient displacement capped at width/256 (flat floor, no contour rings); restoring real undulation is L32. Evaluator 19. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
103d8b471e
commit
28069f0d98
23 files changed
+1235
-19
No files matched your search
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Combine the initial and repeat L31 interleaved cost batches."""
|
||||
import csv
|
||||
import statistics
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l31"
|
||||
sources = (
|
||||
("first", 0, OUT / "cost-first/cost-runs.csv"),
|
||||
("repeat", 5, OUT / "cost-runs.csv"),
|
||||
)
|
||||
rows = []
|
||||
for batch, offset, path in sources:
|
||||
with path.open(newline="", encoding="utf-8") as stream:
|
||||
for row in csv.DictReader(stream):
|
||||
row["pair"] = str(int(row["pair"]) + offset)
|
||||
row["source_batch"] = batch
|
||||
rows.append(row)
|
||||
|
||||
with (OUT / "cost-combined.csv").open("w", newline="", encoding="utf-8") as stream:
|
||||
writer = csv.DictWriter(stream, fieldnames=(
|
||||
"recipe", "pair", "build", "whole_view_ready_seconds", "process_elapsed_seconds",
|
||||
"exit_code", "status", "source_batch"))
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
|
||||
print("recipe pairs median_l30_s median_l31_s median_change_pct paired_median_pct range_pct")
|
||||
failed = False
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
by_build = {
|
||||
build: [float(row["whole_view_ready_seconds"]) for row in rows
|
||||
if row["recipe"] == recipe and row["build"] == build]
|
||||
for build in ("l30", "l31")
|
||||
}
|
||||
paired = []
|
||||
for pair in range(1, 11):
|
||||
times = {
|
||||
row["build"]: float(row["whole_view_ready_seconds"])
|
||||
for row in rows if row["recipe"] == recipe and int(row["pair"]) == pair
|
||||
}
|
||||
paired.append((times["l31"] / times["l30"] - 1.0) * 100.0)
|
||||
median_l30 = statistics.median(by_build["l30"])
|
||||
median_l31 = statistics.median(by_build["l31"])
|
||||
median_change = (median_l31 / median_l30 - 1.0) * 100.0
|
||||
print(f"{recipe} 10 {median_l30:.3f} {median_l31:.3f} {median_change:+.2f} "
|
||||
f"{statistics.median(paired):+.2f} {min(paired):+.2f}..{max(paired):+.2f}")
|
||||
failed |= len(by_build["l30"]) != 10 or len(by_build["l31"]) != 10
|
||||
print(f"[L31CostCombined] pairs_per_recipe=10 runs={len(rows)} "
|
||||
f"status={'FAIL' if failed else 'PASS'} summary={OUT / 'cost-combined.csv'}")
|
||||
raise SystemExit(1 if failed or len(rows) != 40 else 0)
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare L25 and 24 m crossing hash records for matching recipe/compiler/workers."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l31"
|
||||
pattern = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
|
||||
lines = []
|
||||
failed = False
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for compiler in ("clang", "gcc"):
|
||||
for workers in (1, 8, 12):
|
||||
left = pattern.findall((OUT / f"l25-{compiler}-{recipe}-w{workers}.log").read_text())
|
||||
right = pattern.findall((OUT / f"crossing24-{compiler}-{recipe}-w{workers}.log").read_text())
|
||||
same = bool(left) and len(left) == 12 and left == right
|
||||
failed |= not same
|
||||
lines.append(f"{recipe},{compiler},W{workers},{'MATCH' if same else 'MISMATCH'}")
|
||||
|
||||
path = OUT / "determinism-crossing-compare.log"
|
||||
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
print("\n".join(lines))
|
||||
print(f"[L31CrossingHashCompare] comparisons={len(lines)} "
|
||||
f"pass={sum(line.endswith('MATCH') for line in lines)} fail={sum(line.endswith('MISMATCH') for line in lines)}")
|
||||
raise SystemExit(1 if failed else 0)
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Encode the six requested L31 screenshots as small, pinned JPEGs."""
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
SOURCE = ROOT / "Saved/Linux/l31"
|
||||
DEST = Path(__file__).resolve().parent / "captures"
|
||||
DEST.mkdir(parents=True, exist_ok=True)
|
||||
names = (
|
||||
"fi-column-top-close-v19",
|
||||
"fi-column-top-wide-v19",
|
||||
"fi-column-base-close-v19",
|
||||
"fi-column-base-wide-v19",
|
||||
"fi-floor-v19-tint-off",
|
||||
"fi-floor-v19-tint-on",
|
||||
)
|
||||
failed = False
|
||||
for name in names:
|
||||
source = SOURCE / f"{name}.png"
|
||||
destination = DEST / f"{name.removesuffix('-v19').replace('-v19-', '-')}.jpg"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "ffmpeg",
|
||||
"-y", "-hide_banner", "-loglevel", "error", "-i", str(source),
|
||||
"-frames:v", "1", "-q:v", "4", str(destination),
|
||||
]
|
||||
result = subprocess.run(command, check=False)
|
||||
size = destination.stat().st_size if destination.exists() else 0
|
||||
ok = result.returncode == 0 and size <= 300_000
|
||||
failed |= not ok
|
||||
print(f"[L31CaptureJPEG] source={source.name} output={destination.name} "
|
||||
f"bytes={size} status={'PASS' if ok else 'FAIL'}")
|
||||
raise SystemExit(1 if failed else 0)
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Measure surface heights and normals on an eastward FI floor radial line."""
|
||||
import array
|
||||
import math
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def read_surface_vertices(path):
|
||||
vertices = []
|
||||
with open(path, "rb") as stream:
|
||||
if stream.read(8) != b"VFMSH001":
|
||||
raise SystemExit(f"{path}: invalid scene header")
|
||||
version, voxel_um, count = struct.unpack("<3I", stream.read(12))
|
||||
stream.read(40)
|
||||
if version != 1 or count > 100000:
|
||||
raise SystemExit("unsupported scene")
|
||||
for _ in range(count):
|
||||
key = struct.unpack("<4i", stream.read(16))
|
||||
surface_count, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
|
||||
"<5I", stream.read(20))
|
||||
positions = array.array("f")
|
||||
positions.frombytes(stream.read(vertex_count * 12))
|
||||
normals = array.array("f")
|
||||
normals.frombytes(stream.read(vertex_count * 12))
|
||||
indices = array.array("I")
|
||||
indices.frombytes(stream.read(index_count * 4))
|
||||
masks = stream.read(mask_count)
|
||||
if sys.byteorder != "little":
|
||||
positions.byteswap()
|
||||
normals.byteswap()
|
||||
indices.byteswap()
|
||||
used = set()
|
||||
for tri in range(0, index_count, 3):
|
||||
if masks and masks[tri // 3]:
|
||||
continue
|
||||
for index in indices[tri:tri + 3]:
|
||||
if index < surface_count:
|
||||
used.add(index)
|
||||
for index in used:
|
||||
offset = index * 3
|
||||
vertices.append((positions[offset], positions[offset + 1],
|
||||
positions[offset + 2], normals[offset],
|
||||
normals[offset + 1], normals[offset + 2]))
|
||||
return voxel_um, vertices
|
||||
|
||||
|
||||
def main():
|
||||
scene = sys.argv[1]
|
||||
center_x, center_y = 64.0, -32.0
|
||||
voxel_um, vertices = read_surface_vertices(scene)
|
||||
floor = [v for v in vertices if abs(v[2] + 119.5) < 0.05
|
||||
and math.hypot(v[0] - center_x, v[1] - center_y) <= 16.5]
|
||||
if not floor:
|
||||
raise SystemExit("no floor surface vertices near expected FI bedrock top")
|
||||
mean = tuple(sum(v[index] for v in floor) / len(floor) for index in (3, 4, 5))
|
||||
normal_rms = math.sqrt(sum(sum((v[index] - mean[axis]) ** 2
|
||||
for axis, index in enumerate((3, 4, 5)))
|
||||
for v in floor) / len(floor))
|
||||
min_z = min(v[2] for v in floor)
|
||||
max_z = max(v[2] for v in floor)
|
||||
print(f"[RingNormalField] voxel_um={voxel_um} center_m=({center_x:.3f},{center_y:.3f}) "
|
||||
f"vertices={len(floor)} height_min_m={min_z:.6f} height_max_m={max_z:.6f} "
|
||||
f"mean_normal=({mean[0]:.8f},{mean[1]:.8f},{mean[2]:.8f}) "
|
||||
f"normal_rms={normal_rms:.9f}")
|
||||
print("[RingNormalRadialLine] radius_m sampled_radius_m height_m nx ny nz")
|
||||
for radius in range(1, 17):
|
||||
x, y = center_x + radius, center_y
|
||||
vertex = min(floor, key=lambda v: (v[0] - x) ** 2 + (v[1] - y) ** 2)
|
||||
sampled_radius = math.hypot(vertex[0] - center_x, vertex[1] - center_y)
|
||||
if math.hypot(vertex[0] - x, vertex[1] - y) > 0.5:
|
||||
raise SystemExit(f"missing near vertex at radius {radius} m")
|
||||
print(f"{radius:2d} {sampled_radius:.6f} {vertex[2]:.6f} "
|
||||
f"{vertex[3]:.8f} {vertex[4]:.8f} {vertex[5]:.8f}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rasterize upward mesh triangles into a top-down field height map."""
|
||||
import argparse
|
||||
import array
|
||||
import json
|
||||
import math
|
||||
import struct
|
||||
import sys
|
||||
|
||||
Q16 = 1 << 16
|
||||
MASK64 = (1 << 64) - 1
|
||||
|
||||
|
||||
def trunc_div(numerator, denominator):
|
||||
return numerator // denominator if numerator >= 0 else -((-numerator) // denominator)
|
||||
|
||||
|
||||
def transition_hash(seed, interface, x, y, z, salt):
|
||||
value = 14695981039346656037
|
||||
for word in (seed & 0xffffffff, interface & 0xffffffff,
|
||||
x & MASK64, y & MASK64, z & MASK64, salt):
|
||||
for byte in range(8):
|
||||
value ^= (word >> (byte * 8)) & 0xff
|
||||
value = (value * 1099511628211) & MASK64
|
||||
value ^= value >> 30
|
||||
value = (value * 0xbf58476d1ce4e5b9) & MASK64
|
||||
value ^= value >> 27
|
||||
value = (value * 0x94d049bb133111eb) & MASK64
|
||||
value ^= value >> 31
|
||||
return ((value >> 32) >> 15) - Q16
|
||||
|
||||
|
||||
def transition_fade(value):
|
||||
value = max(0, min(Q16, value))
|
||||
square = value * value // Q16
|
||||
return square * (3 * Q16 - 2 * value) // Q16
|
||||
|
||||
|
||||
def gradient_height_um(layout, x_um, y_um):
|
||||
upper = next(item for item in layout["strates"] if item["order"] == 0)
|
||||
lower = next(item for item in layout["strates"] if item["order"] == 1)
|
||||
width = upper["transition_width_um"]
|
||||
cell = max(1_000_000, layout["strate_layout_chunk_size_um"] * 4)
|
||||
ix = x_um // cell
|
||||
iy = y_um // cell
|
||||
tx = transition_fade(((x_um - ix * cell) * Q16) // cell)
|
||||
ty = transition_fade(((y_um - iy * cell) * Q16) // cell)
|
||||
|
||||
def at(dx, dy):
|
||||
noise = transition_hash(0, upper["strate_index"], ix + dx, iy + dy, 0,
|
||||
0x47524144)
|
||||
return noise
|
||||
|
||||
def lerp(a, b, t):
|
||||
return a + trunc_div((b - a) * t, Q16)
|
||||
|
||||
low = lerp(lerp(at(0, 0), at(1, 0), tx),
|
||||
lerp(at(0, 1), at(1, 1), tx), ty)
|
||||
amplitude_divisor = 256 if upper.get("generator_kind") == "FloatingIslands" else 8
|
||||
displacement = trunc_div(low * (width // amplitude_divisor), Q16)
|
||||
seam = upper["bedrock"]["bottom_um"]
|
||||
thickness = upper["bedrock"]["top_um"] - seam
|
||||
return seam + displacement + thickness
|
||||
|
||||
|
||||
def read_scene(path):
|
||||
triangles = []
|
||||
with open(path, "rb") as stream:
|
||||
if stream.read(8) != b"VFMSH001":
|
||||
raise SystemExit(f"{path}: invalid scene header")
|
||||
version, voxel_um, count = struct.unpack("<3I", stream.read(12))
|
||||
stream.read(40)
|
||||
if version != 1 or count > 100000:
|
||||
raise SystemExit("unsupported scene")
|
||||
for _ in range(count):
|
||||
key = struct.unpack("<4i", stream.read(16))
|
||||
surface_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
|
||||
"<5I", stream.read(20))
|
||||
positions = array.array("f")
|
||||
positions.frombytes(stream.read(vertex_count * 12))
|
||||
normals = array.array("f")
|
||||
normals.frombytes(stream.read(vertex_count * 12))
|
||||
indices = array.array("I")
|
||||
indices.frombytes(stream.read(index_count * 4))
|
||||
masks = stream.read(mask_count)
|
||||
if sys.byteorder != "little":
|
||||
positions.byteswap()
|
||||
normals.byteswap()
|
||||
indices.byteswap()
|
||||
for index in range(0, index_count, 3):
|
||||
if masks and masks[index // 3]:
|
||||
continue
|
||||
a, b, c = indices[index:index + 3]
|
||||
if max(a, b, c) >= surface_vertices:
|
||||
continue
|
||||
nz = (normals[a * 3 + 2] + normals[b * 3 + 2]
|
||||
+ normals[c * 3 + 2]) / 3.0
|
||||
if nz < 0.35:
|
||||
continue
|
||||
vertices = tuple((positions[v * 3], positions[v * 3 + 1],
|
||||
positions[v * 3 + 2]) for v in (a, b, c))
|
||||
triangles.append(vertices)
|
||||
return voxel_um, triangles
|
||||
|
||||
|
||||
def rasterize(triangles, bounds, width, height):
|
||||
x0, x1, y0, y1 = bounds
|
||||
values = [None] * (width * height)
|
||||
sx, sy = width / (x1 - x0), height / (y1 - y0)
|
||||
for tri in triangles:
|
||||
(ax, ay, az), (bx, by, bz), (cx, cy, cz) = tri
|
||||
den = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy)
|
||||
if abs(den) < 1.0e-12:
|
||||
continue
|
||||
left = max(0, int(math.floor((min(ax, bx, cx) - x0) * sx)))
|
||||
right = min(width - 1, int(math.ceil((max(ax, bx, cx) - x0) * sx)))
|
||||
top = max(0, int(math.floor((y1 - max(ay, by, cy)) * sy)))
|
||||
bottom = min(height - 1, int(math.ceil((y1 - min(ay, by, cy)) * sy)))
|
||||
for py in range(top, bottom + 1):
|
||||
y = y1 - (py + 0.5) / sy
|
||||
row = py * width
|
||||
for px in range(left, right + 1):
|
||||
x = x0 + (px + 0.5) / sx
|
||||
u = ((by - cy) * (x - cx) + (cx - bx) * (y - cy)) / den
|
||||
v = ((cy - ay) * (x - cx) + (ax - cx) * (y - cy)) / den
|
||||
w = 1.0 - u - v
|
||||
if min(u, v, w) < -1.0e-6:
|
||||
continue
|
||||
z = u * az + v * bz + w * cz
|
||||
idx = row + px
|
||||
if values[idx] is None or z > values[idx]:
|
||||
values[idx] = z
|
||||
return values
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("scene")
|
||||
parser.add_argument("output_ppm")
|
||||
parser.add_argument("--bounds", nargs=4, type=float, required=True,
|
||||
metavar=("XMIN", "XMAX", "YMIN", "YMAX"))
|
||||
parser.add_argument("--size", nargs=2, type=int, default=(640, 640))
|
||||
parser.add_argument("--range", nargs=2, type=float, default=(-122.0, -118.0),
|
||||
metavar=("ZMIN", "ZMAX"))
|
||||
parser.add_argument("--ring-center", nargs=2, type=float,
|
||||
metavar=("X", "Y"))
|
||||
parser.add_argument("--probe-max-radius", type=int, default=16)
|
||||
parser.add_argument("--layout")
|
||||
args = parser.parse_args()
|
||||
voxel_um, triangles = read_scene(args.scene)
|
||||
width, height = args.size
|
||||
sx, sy = width / (args.bounds[1] - args.bounds[0]), height / (args.bounds[3] - args.bounds[2])
|
||||
values = rasterize(triangles, args.bounds, width, height)
|
||||
finite = sorted(value for value in values if value is not None)
|
||||
if not finite:
|
||||
raise SystemExit("no upward-facing field samples in bounds")
|
||||
low, high = args.range
|
||||
if high <= low:
|
||||
raise SystemExit("height range must increase")
|
||||
stops = ((0.00, (31, 52, 92)), (0.24, (41, 119, 153)),
|
||||
(0.50, (85, 177, 143)), (0.73, (226, 207, 105)),
|
||||
(1.00, (181, 71, 49)))
|
||||
with open(args.output_ppm, "wb") as output:
|
||||
output.write(f"P6\n{width} {height}\n255\n".encode())
|
||||
for value in values:
|
||||
if value is None:
|
||||
output.write(bytes((18, 22, 30)))
|
||||
continue
|
||||
t = max(0.0, min(1.0, (value - low) / (high - low)))
|
||||
for index in range(len(stops) - 1):
|
||||
if t <= stops[index + 1][0]:
|
||||
ta, ca = stops[index]
|
||||
tb, cb = stops[index + 1]
|
||||
blend = (t - ta) / (tb - ta)
|
||||
rgb = tuple(round(ca[c] + blend * (cb[c] - ca[c]))
|
||||
for c in range(3))
|
||||
break
|
||||
else:
|
||||
rgb = stops[-1][1]
|
||||
output.write(bytes(rgb))
|
||||
def quantile(p):
|
||||
return finite[round((len(finite) - 1) * p)]
|
||||
print(f"[RingFieldRaster] voxel_um={voxel_um} triangles={len(triangles)} "
|
||||
f"pixels={len(finite)}/{len(values)} height_min_m={finite[0]:.6f} "
|
||||
f"height_p05_m={quantile(.05):.6f} height_median_m={quantile(.5):.6f} "
|
||||
f"height_p95_m={quantile(.95):.6f} height_max_m={finite[-1]:.6f} "
|
||||
f"height_range_m={finite[-1]-finite[0]:.6f} "
|
||||
f"map={args.output_ppm}")
|
||||
if args.ring_center:
|
||||
cx, cy = args.ring_center
|
||||
def at_world(x, y):
|
||||
fx = (x - args.bounds[0]) * sx - 0.5
|
||||
fy = (args.bounds[3] - y) * sy - 0.5
|
||||
ix, iy = math.floor(fx), math.floor(fy)
|
||||
tx, ty = fx - ix, fy - iy
|
||||
samples = []
|
||||
for px, py in ((ix, iy), (ix + 1, iy), (ix, iy + 1), (ix + 1, iy + 1)):
|
||||
if not (0 <= px < width and 0 <= py < height):
|
||||
return None
|
||||
value = values[py * width + px]
|
||||
if value is None:
|
||||
return None
|
||||
samples.append(value)
|
||||
return ((samples[0] * (1 - tx) + samples[1] * tx) * (1 - ty)
|
||||
+ (samples[2] * (1 - tx) + samples[3] * tx) * ty)
|
||||
print(f"[RingRadialProbe] center_m=({cx:.3f},{cy:.3f}) "
|
||||
"radius_m mean_m angular_sd_m min_m max_m samples")
|
||||
for radius in range(1, args.probe_max_radius + 1):
|
||||
row = [at_world(cx + radius * math.cos(a * math.tau / 32),
|
||||
cy + radius * math.sin(a * math.tau / 32))
|
||||
for a in range(32)]
|
||||
row = [value for value in row if value is not None]
|
||||
if row:
|
||||
mean = sum(row) / len(row)
|
||||
sd = math.sqrt(sum((value - mean) ** 2 for value in row) / len(row))
|
||||
print(f" {radius:>2} {mean:.6f} {sd:.6f} {min(row):.6f} "
|
||||
f"{max(row):.6f} {len(row)}")
|
||||
if args.layout:
|
||||
layout = json.load(open(args.layout, encoding="utf-8"))
|
||||
if next(item for item in layout["strates"] if item["order"] == 0)["transition_mode"] != "Gradient":
|
||||
raise SystemExit("--layout must describe a Gradient owner interface")
|
||||
errors = []
|
||||
for index, actual in enumerate(values):
|
||||
if actual is None:
|
||||
continue
|
||||
px, py = index % width, index // width
|
||||
x = args.bounds[0] + (px + 0.5) / sx
|
||||
y = args.bounds[3] - (py + 0.5) / sy
|
||||
expected = gradient_height_um(layout, round(x * 1_000_000),
|
||||
round(y * 1_000_000)) / 1_000_000.0
|
||||
errors.append(actual - expected)
|
||||
absolute = sorted(abs(error) for error in errors)
|
||||
rms = math.sqrt(sum(error * error for error in errors) / len(errors))
|
||||
print(f"[RingGradientMatch] samples={len(errors)} rms_m={rms:.6f} "
|
||||
f"p95_abs_m={absolute[round((len(absolute)-1)*.95)]:.6f} "
|
||||
f"max_abs_m={absolute[-1]:.6f} "
|
||||
"expected=owner-resolved-2d-hash-value-noise")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,107 @@
|
||||
# Linux31 report
|
||||
|
||||
## FI column finding
|
||||
|
||||
The earlier L29/L30 passage table ran SurfaceWorld twice. On the real FloatingIslands recipe, the old column check under-counted connected ends because it treated support at the route foot as the column cap. The route foot is near z = −116 m; the actual lower cap is near z = −122 m, inside native bedrock whose top is z = −119.5 m. The upper caps sit inside anchor-island rock near z = −62 to −69 m. There is no floating column end or unintended gap. The open passage mouth is intentional.
|
||||
|
||||
The audit now samples each planned cap against the native density field. Negative density is solid rock. Every main column cap overlaps native rock within 0.25 m. The style 3 short lower cap overlaps within 0.50 m.
|
||||
|
||||
| FI style | Cap | Z (m) | Native overlap / samples | Minimum native density |
|
||||
|---:|---|---:|---:|---:|
|
||||
| 0 | Main upper / anchor island | −65.846 | 5,051 / 10,005 | −14.761719 |
|
||||
| 0 | Main lower / bedrock | −122.390 | 6,149 / 12,588 | −8.000000 |
|
||||
| 1 | Short upper | −61.574 | 9,725 / 11,033 | −23.464844 |
|
||||
| 1 | Short lower | −75.637 | 3,915 / 11,500 | −15.554688 |
|
||||
| 1 | Main upper / anchor island | −66.485 | 4,497 / 11,118 | −15.613281 |
|
||||
| 1 | Main lower / bedrock | −122.416 | 6,673 / 13,735 | −8.000000 |
|
||||
| 2 | Main upper / anchor island | −68.891 | 3,986 / 8,523 | −15.546875 |
|
||||
| 2 | Main lower / bedrock | −122.382 | 5,396 / 10,885 | −8.000000 |
|
||||
| 3 | Short upper | −65.805 | 5,095 / 10,064 | −14.671875 |
|
||||
| 3 | Short lower | −80.054 | 2,498 / 10,413 | −11.910156 |
|
||||
| 3 | Main upper / anchor island | −69.478 | 3,902 / 9,985 | −15.597656 |
|
||||
| 3 | Main lower / bedrock | −122.436 | 6,130 / 12,453 | −8.000000 |
|
||||
|
||||
The negative control moves the style 0 lower cap into the air gap at z = −96.390 m: 0/3,120 native overlaps, minimum density +22.273438, and `detected=true`. The audit still catches a genuinely disconnected end.
|
||||
|
||||
## Recipe-stamped passage matrix
|
||||
|
||||
Each row is checked against the requested and resolved recipe stamp. The columns are capsule blocked/unsupported counts and mouth checks, open-air violations, naturalness, longest uncolumned span / 12.5 m limit, connected column ends, route bindings / failures, and walkability samples.
|
||||
|
||||
For all eight FI rows, `FloatingIslandRouteAudit` also passes: 78,064 capsule samples, 0 blocked, 0 unsupported, and 0 grade failures. The runner now asserts both capsule audits.
|
||||
|
||||
| Requested → actual | Style | Compiler | Capsule | Open air | Natural | Long bridge | Column ends | Bindings | Walkability |
|
||||
|---|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||
| surface-world → surface-world | 0 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 5642/5642 |
|
||||
| surface-world → surface-world | 0 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 5642/5642 |
|
||||
| surface-world → surface-world | 1 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1490/1490 |
|
||||
| surface-world → surface-world | 1 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1490/1490 |
|
||||
| surface-world → surface-world | 2 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1545/1545 |
|
||||
| surface-world → surface-world | 2 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1545/1545 |
|
||||
| surface-world → surface-world | 3 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1495/1495 |
|
||||
| surface-world → surface-world | 3 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1495/1495 |
|
||||
| floating-islands → floating-islands | 0 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 4.464/12.500 m | 2/2 | 2 (0 failures) | 4000/4000 |
|
||||
| floating-islands → floating-islands | 0 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 4.464/12.500 m | 2/2 | 2 (0 failures) | 4000/4000 |
|
||||
| floating-islands → floating-islands | 1 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 3.851/12.500 m | 4/4 | 2 (0 failures) | 1047/1047 |
|
||||
| floating-islands → floating-islands | 1 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 3.851/12.500 m | 4/4 | 2 (0 failures) | 1047/1047 |
|
||||
| floating-islands → floating-islands | 2 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 2 (0 failures) | 1135/1135 |
|
||||
| floating-islands → floating-islands | 2 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 2 (0 failures) | 1135/1135 |
|
||||
| floating-islands → floating-islands | 3 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 4.243/12.500 m | 4/4 | 2 (0 failures) | 1061/1061 |
|
||||
| floating-islands → floating-islands | 3 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 4.243/12.500 m | 4/4 | 2 (0 failures) | 1061/1061 |
|
||||
|
||||
All 16 rows pass. The runner rejects a recipe mismatch. Its negative control requests SurfaceWorld while the resolved layout is FloatingIslands; the log records `requested=surface-world actual=floating-islands status=FAIL` and the process exits 2. Logs and the machine-readable matrix are under `Saved/Linux/l31/`.
|
||||
|
||||
## FI floor lines
|
||||
|
||||
The L30 rings persisted with height tint off, so they were geometry. Evaluator v19 reduces FloatingIslands Gradient displacement to `transition_width / 256` (one quarter voxel at the 16 m transition width), and the layout validator uses the same bound. The v18 one-voxel cap still left a 0.251819 m radial height range and a visible contour.
|
||||
|
||||
On the east radial line from (64, −32), samples at radii 1–16 m all have height −119.500000 m and normal (0, 0, 1). The extended mesh raster has a 0.000479 m total height range. The readable shader now applies a cubic smooth tint (`t²(3−2t)`). Tint-off and tint-on captures show a flat floor without contour bands.
|
||||
|
||||
## Captures and hashes
|
||||
|
||||
All six JPEGs are recipe-stamped from FloatingIslands and under 300 KB.
|
||||
|
||||
| Capture | Size | SHA-256 |
|
||||
|---|---:|---|
|
||||
| [FI column top, close](captures/fi-column-top-close.jpg) | 85,696 B | `6b651655fc2127bb5b3502a1a99c024167db2edc21bfe7241f6335a858bb8575` |
|
||||
| [FI column top, wide](captures/fi-column-top-wide.jpg) | 67,043 B | `926193affd463492095b97310c62416910e12fc94e75d8548a01ad1f9601b6ec` |
|
||||
| [FI column bedrock base, close](captures/fi-column-base-close.jpg) | 60,491 B | `3e524e0f6c506413b4b5b29d8835792f8b5240f95787c6a08d12ebe6e46aadd5` |
|
||||
| [FI column bedrock base, wide](captures/fi-column-base-wide.jpg) | 61,543 B | `27ef1a72874c7f8e257969c5e69d614f336edce84693ee80b98783b9f610a4b2` |
|
||||
| [FI floor, tint off](captures/fi-floor-tint-off.jpg) | 43,405 B | `41b9180c9696c7025e23009e6efdb2e86ace5900b08c293c92371797f9a8596d` |
|
||||
| [FI floor, tint on](captures/fi-floor-tint-on.jpg) | 43,405 B | `0672e2ddc93fb6a4fe4f6adfdf4cdee896bdbc90dddfb7cf65050d978f86f32b` |
|
||||
|
||||
Evaluator version is 19. Binary SHA-256:
|
||||
|
||||
- Clang: `a796d433b7e31fa05abba970dbd486db1a904d13222d9c02b9054c394375e4f7`
|
||||
- GCC: `c2ed12ddf4db5ce97fcd2d9c8f32e4515a4fba6d36d4d8a4b5fbb07a8ab8ae73`
|
||||
|
||||
## Determinism and cost
|
||||
|
||||
The L25 command and 24 m crossing each passed 12 cases: Clang and GCC at W1/W8/W12 for both recipes. All 12 hash records per log were stable across compilers and worker counts. SW matches the L30 baseline; FI changes as expected with the v19 geometry. L25 and crossing hashes match for all 12 recipe/compiler/worker combinations.
|
||||
|
||||
| Recipe | Full-detail density / mesh / payload / topology | Whole-view density / mesh / payload / topology |
|
||||
|---|---|---|
|
||||
| SurfaceWorld | `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae` | `0e8c5af01df96b14 / 70099e89b77811ed / dbce0fdcf6764126 / ddaf81a1228f9b4b` |
|
||||
| FloatingIslands | `ed550e0c8b182695 / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec` | `52e0b9f95da4291e / 7c6e9e18346278a4 / 59160fe3df02a0ae / ef7f17088fe3253e` |
|
||||
|
||||
Cost compared the Clang L31 binary with the L30 `linux30-speed-fade` binary: 10 interleaved pairs per recipe, 40 runs total, with zero cache hits. Combined median whole-view time was 1.772 s → 1.784 s for SW (+0.71%) and 17.468 s → 17.482 s for FI (+0.08%). The second five-pair batch was +1.20% SW and +0.01% FI; the larger early SW deltas settled as runs warmed. The paired medians across all 10 were +1.86% SW and +0.55% FI, within the observed run variation.
|
||||
|
||||
## Failed and retried runs
|
||||
|
||||
- Five low-clip floor exports (`export-fi-floor.log`, `export-fi-floor-probe.log`, `export-fi-floor-without-mesh.log`, `export-fi-floor-probe-retry.log`, `fi-floor-no-camera-probe.log`) stopped with `std::bad_alloc`. Export succeeded with `--max-clip-level=8 --max-level=0`.
|
||||
- A first radial analyzer invocation omitted the second `--range` value and was rejected by argparse; it was rerun with both bounds.
|
||||
- A proof/parity probe used custom camera positions and failed SW parity because the sampled tile set differed from the established default-camera audit. That probe was stopped; the default-camera proof and transition suite passed 16/16.
|
||||
- The first determinism launch ended before completing the matrix. The full 24-case run passed on retry. A first cost batch had an early SW timing outlier, so five additional interleaved pairs per recipe were run and retained in the combined summary.
|
||||
- Early capture framing probes cropped the FI axis or placed the camera inside rock. A v19 wide floor view exposed only the export skirt edge; the final centered column and square top-down floor views were used.
|
||||
- The first GCC build attempt was interrupted at 19 objects and retried successfully. One v18 diagnostic build used the default Standalone build path; those artifacts were removed, and final v19 build outputs are under `Saved/Linux/l31/`.
|
||||
- Two one-line post-processing commands had quoting/pair-index errors; the checked-in `CompareDeterminismL31.py` and `AggregateCostL31.py` helpers produced the final passing summaries.
|
||||
|
||||
No run exceeded 30 minutes or remained stuck for 15 minutes. No Unreal files were edited. Nothing was committed, pushed, or stashed.
|
||||
|
||||
## Orchestrator review (Claude)
|
||||
|
||||
- Merged as an intermediate step: correct FI column-cap audit (with negative control), a recipe-stamped runner that
|
||||
rejects mismatches, all 16 recipe/style/compiler rows passing, smooth shader tint, determinism.
|
||||
- **Not accepted as the final floor fix:** v19 caps the FI Gradient displacement at `width/256` (≈6 cm), which
|
||||
removes the owner's Gradient transition rather than making it smooth; the floor is now flat (radial
|
||||
height range 0.0005 m). The real fix is a smooth, non-terraced undulation at the L29 amplitude (≈4 m
|
||||
peak-to-peak). L32 restores it.
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the recipe-stamped L31 passage audit matrix and reject mislabeled runs."""
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l31"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
BINARIES = {
|
||||
"clang": OUT / "bin/VoxelForgeCoreHeadless-linux31-clang",
|
||||
"gcc": OUT / "bin/VoxelForgeCoreHeadless-linux31-gcc",
|
||||
}
|
||||
MARKERS = (
|
||||
"[ResolvedPassageAuditSummary]",
|
||||
"[PassageOpenAirAuditSummary]",
|
||||
"[PassageNaturalnessSummary]",
|
||||
"[PassageNoLongBridgeAudit]",
|
||||
"[PassageRockColumnAuditSummary]",
|
||||
)
|
||||
rows = []
|
||||
failed = False
|
||||
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for style in range(4):
|
||||
data = ROOT / f"Saved/Linux/linux25-owner-{recipe}-style{style}.json"
|
||||
for compiler, binary in BINARIES.items():
|
||||
name = f"passage-{recipe}-style{style}-{compiler}"
|
||||
logfile = OUT / f"{name}.log"
|
||||
command = [
|
||||
str(binary), str(data), "--workers=12", "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--force-generate",
|
||||
"--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
|
||||
f"--recipe={recipe}", "--audit-resolved-layout-passages",
|
||||
]
|
||||
started = time.monotonic()
|
||||
try:
|
||||
with logfile.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=1800, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
result = None
|
||||
elapsed = time.monotonic() - started
|
||||
text = logfile.read_text(encoding="utf-8", errors="replace")
|
||||
stamps = re.findall(
|
||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
|
||||
text, re.M)
|
||||
recipe_ok = stamps == [(recipe, recipe, "PASS")]
|
||||
markers_ok = all(re.search(re.escape(marker) + r".*status=PASS", text)
|
||||
for marker in MARKERS)
|
||||
capsule_ok = bool(re.search(
|
||||
r"^\[ResolvedPassageAudit\].* blocked=0 support_samples=\d+ "
|
||||
r"unsupported=0 grade_failures=0 mouth_content=2/2 mouth_radius=2/2 "
|
||||
r"walkability=PASS$", text, re.M))
|
||||
island_route_ok = (recipe != "floating-islands" or bool(re.search(
|
||||
r"^\[FloatingIslandRouteAudit\].* blocked=0 support_samples=\d+ "
|
||||
r"unsupported=0 grade_failures=0 status=PASS$", text, re.M)))
|
||||
binding_ok = bool(re.search(
|
||||
r"\[ResolvedPassageAuditSummary\].*route_bindings=[1-9][0-9]* "
|
||||
r"route_binding_failures=0 .*status=PASS", text))
|
||||
walk_ok = "[Walkability] PASS" in text
|
||||
if recipe == "floating-islands":
|
||||
control_ok = "[PassageColumnDisconnectedEndControl]" in text and bool(
|
||||
re.search(r"\[PassageColumnDisconnectedEndControl\].*detected=true", text))
|
||||
else:
|
||||
control_ok = True
|
||||
ok = (result is not None and result.returncode == 0 and recipe_ok
|
||||
and markers_ok and capsule_ok and island_route_ok and binding_ok
|
||||
and walk_ok and control_ok)
|
||||
actual_recipe = stamps[0][1] if len(stamps) == 1 else "MISSING"
|
||||
rows.append((recipe, actual_recipe, style, compiler, elapsed,
|
||||
result.returncode if result else "TIMEOUT", ok))
|
||||
print(f"{name} elapsed={elapsed:.1f}s exit={result.returncode if result else 'TIMEOUT'} "
|
||||
f"recipe={stamps[0][1] if stamps else 'MISSING'} "
|
||||
f"capsule={'PASS' if capsule_ok and island_route_ok else 'FAIL'} "
|
||||
f"column_control={'PASS' if control_ok else 'FAIL'} {'PASS' if ok else 'FAIL'}",
|
||||
flush=True)
|
||||
if not ok:
|
||||
failed = True
|
||||
|
||||
summary = OUT / "passage-audit-matrix.csv"
|
||||
summary.write_text(
|
||||
"requested_recipe,actual_recipe,style,compiler,elapsed_seconds,exit_code,status\n" +
|
||||
"".join(f"{requested},{actual},{style},{compiler},{elapsed:.3f},{code},"
|
||||
f"{'PASS' if ok else 'FAIL'}\n"
|
||||
for requested, actual, style, compiler, elapsed, code, ok in rows), encoding="utf-8")
|
||||
|
||||
control_log = OUT / "recipe-mismatch-control.log"
|
||||
control_owner = OUT / "recipe-mismatch-control-owner.json"
|
||||
control_owner_data = json.loads(
|
||||
(ROOT / "Saved/Linux/linux25-owner-surface-world-style0.json").read_text(encoding="utf-8"))
|
||||
top_owner_stratum = max(control_owner_data["strates"], key=lambda stratum: stratum["top_chunk_z"])
|
||||
top_owner_stratum["asset"]["GeneratorType"] = 6 # ECaveGeneratorType::FloatingIslands
|
||||
control_owner.write_text(json.dumps(control_owner_data, separators=(",", ":")), encoding="utf-8")
|
||||
control_command = [
|
||||
str(BINARIES["clang"]),
|
||||
str(control_owner),
|
||||
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", "--force-generate", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=-1", "--recipe=surface-world",
|
||||
"--audit-resolved-layout-passages",
|
||||
]
|
||||
with control_log.open("w", encoding="utf-8") as stream:
|
||||
control = subprocess.run(control_command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=1800, check=False)
|
||||
control_text = control_log.read_text(encoding="utf-8", errors="replace")
|
||||
control_ok = (control.returncode == 2 and
|
||||
"[WorldAssemblerRecipeAudit] requested=surface-world actual=floating-islands "
|
||||
"top_generator=FloatingIslands " in control_text and
|
||||
"floating_islands_stratum=present status=FAIL" in control_text)
|
||||
print(f"[L31RecipeMismatchControl] requested=surface-world actual=floating-islands "
|
||||
f"exit={control.returncode} detected={'true' if control_ok else 'false'} "
|
||||
f"{'PASS' if control_ok else 'FAIL'}", flush=True)
|
||||
|
||||
print(f"[L31PassageMatrix] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
|
||||
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
|
||||
raise SystemExit(1 if failed or not control_ok or len(rows) != 16 else 0)
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run five interleaved W12 whole-view pairs against the final L30 binary."""
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l31"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
BINARIES = {
|
||||
"l30": ROOT / "Saved/Linux/l30/bin/VoxelForgeCoreHeadless-linux30-speed-fade",
|
||||
"l31": OUT / "bin/VoxelForgeCoreHeadless-linux31-clang",
|
||||
}
|
||||
ORDERS = (("l30", "l31"), ("l31", "l30"), ("l30", "l31"),
|
||||
("l31", "l30"), ("l30", "l31"))
|
||||
READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
|
||||
rows = []
|
||||
failed = False
|
||||
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for pair, order in enumerate(ORDERS, 1):
|
||||
for build in order:
|
||||
name = f"cost-{recipe}-pair{pair}-{build}"
|
||||
logfile = OUT / f"{name}.log"
|
||||
command = [
|
||||
str(BINARIES[build]), str(ROOT / "Standalone/data/owner_world.json"),
|
||||
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", "--force-generate", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
|
||||
]
|
||||
started = time.monotonic()
|
||||
try:
|
||||
with logfile.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=1800, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
result = None
|
||||
elapsed = time.monotonic() - started
|
||||
text = logfile.read_text(encoding="utf-8", errors="replace")
|
||||
matches = READY.findall(text)
|
||||
if build == "l31":
|
||||
stamps = re.findall(
|
||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
|
||||
text, re.M)
|
||||
actual_recipe = stamps[0][1] if len(stamps) == 1 else "missing"
|
||||
stamp_ok = stamps == [(recipe, recipe, "PASS")]
|
||||
else:
|
||||
configured = re.findall(
|
||||
r"^\[HeadlessConfig\].*\brecipe=([^\s]+)", text, re.M)
|
||||
actual_recipe = configured[0] if len(configured) == 1 else "missing"
|
||||
floating_plan = "[FloatingIslandWorldAssembly]" in text
|
||||
surface_plan = "[WorldAssemblerPrototype]" in text
|
||||
plan_matches = ((actual_recipe == "floating-islands" and floating_plan)
|
||||
or (actual_recipe == "surface-world" and not floating_plan
|
||||
and surface_plan))
|
||||
marker = (f"[L31BaselineRecipeAudit] requested={recipe} actual={actual_recipe} "
|
||||
f"headless_recipe={configured[0] if len(configured) == 1 else 'missing'} "
|
||||
f"floating_island_plan={'present' if floating_plan else 'absent'} "
|
||||
f"surface_world_plan={'present' if surface_plan else 'absent'} "
|
||||
f"status={'PASS' if actual_recipe == recipe and plan_matches else 'FAIL'}\n")
|
||||
with logfile.open("a", encoding="utf-8") as stream:
|
||||
stream.write(marker)
|
||||
text += marker
|
||||
stamp_ok = (actual_recipe == recipe and plan_matches
|
||||
and len(configured) == 1)
|
||||
valid = (result is not None and result.returncode == 0 and len(matches) == 1
|
||||
and "hash=off" in text and "cache_hits=0" in text)
|
||||
if build == "l31":
|
||||
valid = valid and stamp_ok and actual_recipe == recipe
|
||||
else:
|
||||
valid = valid and stamp_ok and "status=PASS" in marker
|
||||
ready = float(matches[0]) if len(matches) == 1 else float("nan")
|
||||
rows.append((recipe, pair, build, ready, elapsed,
|
||||
result.returncode if result else "TIMEOUT", valid))
|
||||
print(f"{recipe} pair={pair} build={build} whole_view_ready={ready:.3f}s "
|
||||
f"process_elapsed={elapsed:.1f}s {'PASS' if valid else 'FAIL'}", flush=True)
|
||||
failed |= not valid
|
||||
|
||||
summary = OUT / "cost-runs.csv"
|
||||
summary.write_text(
|
||||
"recipe,pair,build,whole_view_ready_seconds,process_elapsed_seconds,exit_code,status\n" +
|
||||
"".join(f"{recipe},{pair},{build},{ready:.6f},{elapsed:.3f},{code},"
|
||||
f"{'PASS' if ok else 'FAIL'}\n"
|
||||
for recipe, pair, build, ready, elapsed, code, ok in rows), encoding="utf-8")
|
||||
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
by_build = {build: [row[3] for row in rows if row[0] == recipe and row[2] == build]
|
||||
for build in BINARIES}
|
||||
l30 = statistics.median(by_build["l30"])
|
||||
l31 = statistics.median(by_build["l31"])
|
||||
paired = []
|
||||
for pair in range(1, 6):
|
||||
values = {row[2]: row[3] for row in rows if row[0] == recipe and row[1] == pair}
|
||||
paired.append((values["l31"] - values["l30"]) / values["l30"] * 100.0)
|
||||
print(f"[L31CostSummary] recipe={recipe} pairs=5 median_l30_s={l30:.3f} "
|
||||
f"median_l31_s={l31:.3f} median_change_pct={(l31/l30-1)*100.0:+.2f} "
|
||||
f"paired_change_pct={','.join(f'{value:+.2f}' for value in paired)}", flush=True)
|
||||
|
||||
print(f"[L31Cost] runs={len(rows)} status={'FAIL' if failed else 'PASS'} summary={summary}", flush=True)
|
||||
raise SystemExit(1 if failed or len(rows) != 20 else 0)
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Re-run the L25 hash command and 24 m crossing on the L31 compiler matrix."""
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l31"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
BINARIES = {
|
||||
"clang": OUT / "bin/VoxelForgeCoreHeadless-linux31-clang",
|
||||
"gcc": OUT / "bin/VoxelForgeCoreHeadless-linux31-gcc",
|
||||
}
|
||||
HASH_PATTERN = re.compile(
|
||||
r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
|
||||
rows = []
|
||||
failed = False
|
||||
candidate_reference = {}
|
||||
fi_hash_changes = {"l25": False, "crossing24": False}
|
||||
|
||||
|
||||
def hashes(path):
|
||||
return HASH_PATTERN.findall(path.read_text(encoding="utf-8", errors="replace"))
|
||||
|
||||
|
||||
def run(kind, compiler, recipe, workers):
|
||||
global failed
|
||||
name = f"{kind}-{compiler}-{recipe}-w{workers}"
|
||||
logfile = OUT / f"{name}.log"
|
||||
command = [
|
||||
str(BINARIES[compiler]), str(ROOT / "Standalone/data/owner_world.json"),
|
||||
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||
"--stream-hash", "--force-generate", "--clip-radius=3", "--max-clip-level=8",
|
||||
"--far-horizon-lod8-step=0", f"--workers={workers}", f"--recipe={recipe}",
|
||||
]
|
||||
if kind == "crossing24":
|
||||
command.extend(("--camera-step=24,0,0", "--region-crossing"))
|
||||
started = time.monotonic()
|
||||
try:
|
||||
with logfile.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=1800, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
result = None
|
||||
elapsed = time.monotonic() - started
|
||||
text = logfile.read_text(encoding="utf-8", errors="replace")
|
||||
stamps = re.findall(
|
||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
|
||||
text, re.M)
|
||||
current = hashes(logfile)
|
||||
# The normal L30 log stem is ringfix-hash; crossing uses ringfix-crossing24.
|
||||
baseline = ROOT / "Saved/Linux/l30" / (
|
||||
f"ringfix-crossing24-{compiler}-{recipe}-w{workers}.log" if kind == "crossing24"
|
||||
else f"ringfix-hash-{compiler}-{recipe}-w{workers}.log")
|
||||
reference = hashes(baseline) if baseline.is_file() else []
|
||||
key = (kind, recipe)
|
||||
prior = candidate_reference.get(key)
|
||||
stable = prior is None or current == prior
|
||||
if prior is None and current:
|
||||
candidate_reference[key] = current
|
||||
baseline_match = bool(reference) and current == reference
|
||||
baseline_changed = bool(reference) and current != reference
|
||||
if recipe == "surface-world":
|
||||
baseline_ok = baseline_match
|
||||
comparison = "MATCH" if baseline_match else "MISMATCH"
|
||||
else:
|
||||
baseline_ok = baseline_changed
|
||||
comparison = "EXPECTED_CHANGE" if baseline_changed else "UNCHANGED_OR_MISSING"
|
||||
fi_hash_changes[kind] |= baseline_changed
|
||||
ok = (result is not None and result.returncode == 0
|
||||
and stamps == [(recipe, recipe, "PASS")]
|
||||
and "hash=on" in text and len(current) == 12 and stable and baseline_ok)
|
||||
rows.append((kind, recipe, compiler, workers, elapsed,
|
||||
result.returncode if result else "TIMEOUT", len(current), ok))
|
||||
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
|
||||
f"hashes={len(current)} "
|
||||
f"L30={comparison} candidate_stable={'yes' if stable else 'no'} "
|
||||
f"{'PASS' if ok else 'FAIL'}", flush=True)
|
||||
if not ok:
|
||||
failed = True
|
||||
|
||||
|
||||
for kind in ("l25", "crossing24"):
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for compiler in ("clang", "gcc"):
|
||||
for workers in (1, 8, 12):
|
||||
run("crossing24" if kind == "crossing24" else "l25", compiler, recipe, workers)
|
||||
|
||||
summary = OUT / "determinism.csv"
|
||||
summary.write_text(
|
||||
"kind,recipe,compiler,workers,elapsed_seconds,exit_code,hash_rows,status\n" +
|
||||
"".join(f"{kind},{recipe},{compiler},{workers},{elapsed:.3f},{code},{count},"
|
||||
f"{'PASS' if ok else 'FAIL'}\n"
|
||||
for kind, recipe, compiler, workers, elapsed, code, count, ok in rows),
|
||||
encoding="utf-8")
|
||||
for kind, changed in fi_hash_changes.items():
|
||||
if not changed:
|
||||
failed = True
|
||||
print(f"[L31ExpectedGeometryHashChange] kind={kind} detected=false FAIL", flush=True)
|
||||
else:
|
||||
print(f"[L31ExpectedGeometryHashChange] kind={kind} detected=true PASS", flush=True)
|
||||
print(f"[L31Determinism] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
|
||||
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
|
||||
raise SystemExit(1 if failed or len(rows) != 24 else 0)
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run L31 route/proof and transition invariants with recipe-stamped logs."""
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l31"
|
||||
BINARIES = {
|
||||
"clang": OUT / "bin/VoxelForgeCoreHeadless-linux31-clang",
|
||||
"gcc": OUT / "bin/VoxelForgeCoreHeadless-linux31-gcc",
|
||||
}
|
||||
rows = []
|
||||
failed = False
|
||||
|
||||
|
||||
def run(name, binary, data, recipe, extra, checks):
|
||||
global failed
|
||||
logfile = OUT / f"{name}.log"
|
||||
command = [
|
||||
str(binary), str(data), "--workers=12", "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--force-generate",
|
||||
"--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
|
||||
f"--recipe={recipe}", *extra,
|
||||
]
|
||||
started = time.monotonic()
|
||||
try:
|
||||
with logfile.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=1800, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
result = None
|
||||
elapsed = time.monotonic() - started
|
||||
text = logfile.read_text(encoding="utf-8", errors="replace")
|
||||
stamps = re.findall(
|
||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
|
||||
text, re.M)
|
||||
stamp_ok = stamps == [(recipe, recipe, "PASS")]
|
||||
checks_ok = all(check(text) for check in checks)
|
||||
ok = result is not None and result.returncode == 0 and stamp_ok and checks_ok
|
||||
rows.append((name, recipe, elapsed, result.returncode if result else "TIMEOUT", ok))
|
||||
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
|
||||
f"stamp={'PASS' if stamp_ok else 'FAIL'} checks={'PASS' if checks_ok else 'FAIL'} "
|
||||
f"{'PASS' if ok else 'FAIL'}", flush=True)
|
||||
failed |= not ok
|
||||
|
||||
|
||||
owner = ROOT / "Standalone/data/owner_world.json"
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for compiler, binary in BINARIES.items():
|
||||
name = f"invariant-route-proof-{recipe}-{compiler}"
|
||||
run(name, binary, owner, recipe,
|
||||
["--stream-hash", "--audit-uniform-block-proofs", "--audit-route-parity"],
|
||||
[lambda text: bool(re.search(
|
||||
r"^\[RouteParityAudit\].*status=PASS$", text, re.M)),
|
||||
lambda text: bool(re.search(
|
||||
r"^\[UniformBlockProofAudit\].*violations=0(?:\s|$)", text, re.M))])
|
||||
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for compiler, binary in BINARIES.items():
|
||||
for mode in ("hard", "gradient", "interleaved"):
|
||||
name = f"invariant-transition-{recipe}-{mode}-{compiler}"
|
||||
run(name, binary, owner, recipe,
|
||||
[f"--transition-mode={mode}", "--audit-strate-transitions"],
|
||||
[lambda text: bool(re.search(
|
||||
r"^\[StrateTransitionAuditSummary\].*hole_columns=0 air_samples=0 .*status=PASS$",
|
||||
text, re.M)),
|
||||
lambda text: bool(re.search(
|
||||
r"^\[StrateTransitionNegativeControls\].*status=PASS$", text, re.M))])
|
||||
|
||||
summary = OUT / "invariant-audits.csv"
|
||||
summary.write_text(
|
||||
"run,recipe,elapsed_seconds,exit_code,status\n" +
|
||||
"".join(f"{name},{recipe},{elapsed:.3f},{code},{'PASS' if ok else 'FAIL'}\n"
|
||||
for name, recipe, elapsed, code, ok in rows),
|
||||
encoding="utf-8")
|
||||
print(f"[L31InvariantAudits] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
|
||||
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
|
||||
raise SystemExit(1 if failed or len(rows) != 16 else 0)
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract the validated recipe-stamped passage matrix into a compact table."""
|
||||
import csv
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l31"
|
||||
rows = []
|
||||
for recipe in ("surface-world", "floating-islands"):
|
||||
for style in range(4):
|
||||
for compiler in ("clang", "gcc"):
|
||||
name = f"passage-{recipe}-style{style}-{compiler}"
|
||||
path = OUT / f"{name}.log"
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
|
||||
def match(pattern):
|
||||
found = re.search(pattern, text, re.M)
|
||||
if not found:
|
||||
raise SystemExit(f"{path}: missing metric {pattern}")
|
||||
return found
|
||||
|
||||
stamp = match(
|
||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=(PASS|FAIL)$")
|
||||
capsule = match(
|
||||
r"^\[ResolvedPassageAudit\].*capsule_samples=(\d+) blocked=(\d+) "
|
||||
r"support_samples=(\d+) unsupported=(\d+) grade_failures=(\d+) "
|
||||
r"mouth_content=([^ ]+) mouth_radius=([^ ]+) .*$")
|
||||
air = match(r"^\[PassageOpenAirAuditSummary\].*violations=(\d+).*status=(PASS|FAIL)$")
|
||||
natural = match(r"^\[PassageNaturalnessSummary\] passed=(\d+)/(\d+).*$")
|
||||
bridge = match(
|
||||
r"^\[PassageNoLongBridgeAudit\].*longest_uncolumned_m=([0-9.]+) "
|
||||
r"threshold_m=([0-9.]+).*$")
|
||||
column = match(
|
||||
r"^\[PassageRockColumnAuditSummary\] columns=(\d+) connected_ends=([^ ]+) "
|
||||
r"short_spans=(\d+) wall_or_support_failures=(\d+).*status=(PASS|FAIL)$")
|
||||
binding = match(
|
||||
r"^\[ResolvedPassageAuditSummary\].*route_bindings=(\d+) "
|
||||
r"route_binding_failures=(\d+).*status=(PASS|FAIL)$")
|
||||
walk = match(r"^\[Walkability\] PASS (\d+)/(\d+) passage centerline samples")
|
||||
rows.append({
|
||||
"requested": stamp.group(1), "actual": stamp.group(2), "stamp": stamp.group(3),
|
||||
"style": style, "compiler": compiler,
|
||||
"capsule_samples": int(capsule.group(1)), "blocked": int(capsule.group(2)),
|
||||
"support_samples": int(capsule.group(3)),
|
||||
"unsupported": int(capsule.group(4)), "grade_failures": int(capsule.group(5)),
|
||||
"mouth_content": capsule.group(6), "mouth_radius": capsule.group(7),
|
||||
"air_violations": int(air.group(1)), "air_status": air.group(2),
|
||||
"natural_passed": natural.group(1),
|
||||
"natural_total": natural.group(2),
|
||||
"longest_uncolumned_m": bridge.group(1), "bridge_threshold_m": bridge.group(2),
|
||||
"column_count": int(column.group(1)), "connected_ends": column.group(2),
|
||||
"short_spans": int(column.group(3)), "column_failures": int(column.group(4)),
|
||||
"route_bindings": int(binding.group(1)),
|
||||
"binding_failures": int(binding.group(2)),
|
||||
"walk_samples": int(walk.group(1)), "walk_total": int(walk.group(2)),
|
||||
"log": path.name,
|
||||
})
|
||||
|
||||
with (OUT / "passage-audit-metrics.csv").open("w", newline="", encoding="utf-8") as stream:
|
||||
writer = csv.DictWriter(stream, fieldnames=list(rows[0]))
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
|
||||
lines = [
|
||||
"| Requested → actual | Style | Compiler | Capsule | Open air | Natural | Long bridge | Column ends | Bindings | Walkability |",
|
||||
"|---|---:|---|---|---:|---:|---:|---:|---:|---:|",
|
||||
]
|
||||
for row in rows:
|
||||
ok = (row["requested"] == row["actual"] and row["stamp"] == "PASS"
|
||||
and row["blocked"] == row["unsupported"] == row["grade_failures"] == 0
|
||||
and row["mouth_content"] == row["mouth_radius"] == "2/2"
|
||||
and row["air_violations"] == 0 and row["air_status"] == "PASS"
|
||||
and row["natural_passed"] == row["natural_total"] == "1"
|
||||
and float(row["longest_uncolumned_m"]) <= float(row["bridge_threshold_m"])
|
||||
and row["connected_ends"] == f"{2 * row['column_count']}/{2 * row['column_count']}"
|
||||
and row["column_failures"] == 0 and row["binding_failures"] == 0
|
||||
and row["route_bindings"] > 0 and row["walk_samples"] == row["walk_total"])
|
||||
if not ok:
|
||||
raise SystemExit(f"{row['log']}: metric audit failed")
|
||||
cells = (
|
||||
f"{row['blocked']}/{row['unsupported']} blocked/unsupported; mouth 2/2",
|
||||
f"{row['air_violations']} violations",
|
||||
f"{row['natural_passed']}/{row['natural_total']}",
|
||||
f"{float(row['longest_uncolumned_m']):.3f}/{row['bridge_threshold_m']} m",
|
||||
row["connected_ends"],
|
||||
f"{row['route_bindings']} (0 failures)",
|
||||
f"{row['walk_samples']}/{row['walk_total']}",
|
||||
)
|
||||
lines.append(f"| {row['requested']} → {row['actual']} | {row['style']} | {row['compiler']} | "
|
||||
+ " | ".join(cells) + " |")
|
||||
|
||||
table = OUT / "passage-audit-table.md"
|
||||
table.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
print(f"[L31PassageAuditTable] rows={len(rows)} pass={len(rows)} "
|
||||
f"csv={OUT / 'passage-audit-metrics.csv'} markdown={table}")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Reference in new issue
Block a user