From 28069f0d98d532125d234b7f65c577f9f0ac1782 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Wed, 21 Oct 2026 14:13:18 +0200 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX --- .../include/VoxelForgeCore/VoxelForgeCore.h | 6 +- Standalone/Core/src/ResolvedLayoutInternal.h | 5 +- Standalone/Core/src/ShapeWorldInternal.h | 3 +- .../Linux31/AggregateCostL31.py | 52 ++++ .../Linux31/CompareDeterminismL31.py | 26 ++ .../Linux31/EncodeCapturesL31.py | 34 +++ .../Linux31/MeasureNormalsL31.py | 78 ++++++ .../Linux31/MeasureRingL31.py | 241 ++++++++++++++++++ .../WorldAssemblerM3/Linux31/REPORT.md | 107 ++++++++ .../WorldAssemblerM3/Linux31/RunAuditsL31.py | 120 +++++++++ .../WorldAssemblerM3/Linux31/RunCostL31.py | 103 ++++++++ .../Linux31/RunDeterminismL31.py | 106 ++++++++ .../Linux31/RunInvariantAuditsL31.py | 81 ++++++ .../Linux31/SummarizePassageAuditsL31.py | 97 +++++++ .../Linux31/captures/fi-column-base-close.jpg | Bin 0 -> 60491 bytes .../Linux31/captures/fi-column-base-wide.jpg | Bin 0 -> 61543 bytes .../Linux31/captures/fi-column-top-close.jpg | Bin 0 -> 85696 bytes .../Linux31/captures/fi-column-top-wide.jpg | Bin 0 -> 67043 bytes .../Linux31/captures/fi-floor-tint-off.jpg | Bin 0 -> 43405 bytes .../Linux31/captures/fi-floor-tint-on.jpg | Bin 0 -> 43405 bytes Standalone/Source/LinuxOffscreenCapture.cpp | 32 ++- Standalone/Source/StreamBenchmarkHeadless.cpp | 29 +++ Standalone/Source/StreamBenchmarkPipeline.h | 134 +++++++++- 23 files changed, 1235 insertions(+), 19 deletions(-) create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/AggregateCostL31.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/CompareDeterminismL31.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/EncodeCapturesL31.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/MeasureNormalsL31.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/MeasureRingL31.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/RunAuditsL31.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/RunCostL31.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/RunDeterminismL31.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/RunInvariantAuditsL31.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/SummarizePassageAuditsL31.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-column-base-close.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-column-base-wide.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-column-top-close.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-column-top-wide.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-floor-tint-off.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-floor-tint-on.jpg diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index f928630..5dd3949 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -249,8 +249,10 @@ namespace VoxelForgeCore // L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles. inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256; inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2; - // Version 14 adds shared passage columns; version 16 smooths owner Gradient faces. - inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 16; + // Version 14 adds shared passage columns; version 16 smooths owner Gradient faces; + // version 18 limits FloatingIslands Gradient face displacement to one voxel; + // version 19 reduces it to one quarter voxel to remove the residual floor contour. + inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 19; enum class FieldChannel : std::uint32_t { diff --git a/Standalone/Core/src/ResolvedLayoutInternal.h b/Standalone/Core/src/ResolvedLayoutInternal.h index bcdb368..e0844e7 100644 --- a/Standalone/Core/src/ResolvedLayoutInternal.h +++ b/Standalone/Core/src/ResolvedLayoutInternal.h @@ -359,7 +359,10 @@ ResolvedLayoutTransitionBandsV1 EvaluateResolvedLayoutTransitionBandsV1( return result; } - const std::int64_t displacement = (noise * (width / 8)) / TransitionQ16; + // FloatingIslands floor faces retain the same deterministic Gradient field but + // cap displacement at one quarter voxel so the transition does not read as a contour. + const std::int64_t gradientDivisor=upper.generatorKind=="FloatingIslands"?256:8; + const std::int64_t displacement = (noise * (width / gradientDivisor)) / TransitionQ16; const std::int64_t interfaceZ = seam + displacement; result.upperBedrockBottomMicrometres = interfaceZ; result.upperBedrockTopMicrometres = interfaceZ + upperThickness; diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 7536a32..8c58763 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -269,7 +269,8 @@ public: const VoxelForgeCore::ResolvedLayoutStratumV2& Lower) const { const std::int64_t width=std::max(0,Upper.transitionWidthMicrometres); - if(Upper.transitionMode=="Gradient")return width/8; + if(Upper.transitionMode=="Gradient") + return width/(Upper.generatorKind=="FloatingIslands"?256:8); if(Upper.transitionMode!="Interleaved")return 0; const std::int64_t upperThickness=std::max(0, Upper.bedrock.topMicrometres-Upper.bedrock.bottomMicrometres); diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/AggregateCostL31.py b/Standalone/Research/WorldAssemblerM3/Linux31/AggregateCostL31.py new file mode 100644 index 0000000..683397d --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux31/AggregateCostL31.py @@ -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) diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/CompareDeterminismL31.py b/Standalone/Research/WorldAssemblerM3/Linux31/CompareDeterminismL31.py new file mode 100644 index 0000000..b53f378 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux31/CompareDeterminismL31.py @@ -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) diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/EncodeCapturesL31.py b/Standalone/Research/WorldAssemblerM3/Linux31/EncodeCapturesL31.py new file mode 100644 index 0000000..fe1fd61 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux31/EncodeCapturesL31.py @@ -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) diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/MeasureNormalsL31.py b/Standalone/Research/WorldAssemblerM3/Linux31/MeasureNormalsL31.py new file mode 100644 index 0000000..230cde4 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux31/MeasureNormalsL31.py @@ -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() diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/MeasureRingL31.py b/Standalone/Research/WorldAssemblerM3/Linux31/MeasureRingL31.py new file mode 100644 index 0000000..4f5ae00 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux31/MeasureRingL31.py @@ -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() diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux31/REPORT.md new file mode 100644 index 0000000..a9b60e7 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux31/REPORT.md @@ -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. diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/RunAuditsL31.py b/Standalone/Research/WorldAssemblerM3/Linux31/RunAuditsL31.py new file mode 100644 index 0000000..e97c41a --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux31/RunAuditsL31.py @@ -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) diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/RunCostL31.py b/Standalone/Research/WorldAssemblerM3/Linux31/RunCostL31.py new file mode 100644 index 0000000..aae6725 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux31/RunCostL31.py @@ -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) diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/RunDeterminismL31.py b/Standalone/Research/WorldAssemblerM3/Linux31/RunDeterminismL31.py new file mode 100644 index 0000000..d29b6c5 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux31/RunDeterminismL31.py @@ -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) diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/RunInvariantAuditsL31.py b/Standalone/Research/WorldAssemblerM3/Linux31/RunInvariantAuditsL31.py new file mode 100644 index 0000000..4399f29 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux31/RunInvariantAuditsL31.py @@ -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) diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/SummarizePassageAuditsL31.py b/Standalone/Research/WorldAssemblerM3/Linux31/SummarizePassageAuditsL31.py new file mode 100644 index 0000000..7a35a3d --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux31/SummarizePassageAuditsL31.py @@ -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}") diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-column-base-close.jpg b/Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-column-base-close.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e1c87690f51e63111b48206df305a790bb5572f6 GIT binary patch literal 60491 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 I^}pi(1E_(3cK`qY literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-column-base-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-column-base-wide.jpg new file mode 100644 index 0000000000000000000000000000000000000000..297f90d53a15a6dd11fd8895536f2e5b8324eee1 GIT binary patch literal 61543 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 literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-column-top-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-column-top-wide.jpg new file mode 100644 index 0000000000000000000000000000000000000000..93bac42f27849f948aa36a264449ca041ed81972 GIT binary patch literal 67043 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 V!5k%w{qZN0eV+f97h>O9{s(xABJ%(M literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-floor-tint-off.jpg b/Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-floor-tint-off.jpg new file mode 100644 index 0000000000000000000000000000000000000000..52f713575086f544c9a43ba1e6a36c9f57d8178e GIT binary patch literal 43405 zcmeIuJx;?w6ot{}I8q!DTOP+!QiLWQO@tdHDhgUy1fpOE)L9ZspkNXF?1jOiL57;{ z++w8pidUTcnm?nxxqH077!JebVK^Tmf5dfERkf^^C*P}A_0O87NhytQT{lhJrqr#w zF{Jgn@B8g`v)S!V_xrQ_61PzlG2OfvH%;Zd{Z%5;pOT*=Rg2@|SQcH(x+uHI@3AV2 zn58nsRXjcP)4P5I2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfWZF?6#4B7%expG literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-floor-tint-on.jpg b/Standalone/Research/WorldAssemblerM3/Linux31/captures/fi-floor-tint-on.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1a6fd1d64c2f544cdf7d551e9e21e4a25f39587d GIT binary patch literal 43405 zcmeIuJx;?w6ot{}xQHWS%i~x|iXc!rng};YR1~zZ2*e(!vkZDRLct>V*$ab3gA6s@ zxy4BH6|Xq?HGf8XbN6t6G3z_4ElTsSrx^9}bO{rUT zV@Ru2-}jr%dcEBq?{;VTC2pfAV!E;zH%&#^{wfjaPsz`bs`+7lD2py;U6ftq_gI!i z%u<=+DjuIsr+57b5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+0D=D(ILU8c&Gr~& literal 0 HcmV?d00001 diff --git a/Standalone/Source/LinuxOffscreenCapture.cpp b/Standalone/Source/LinuxOffscreenCapture.cpp index 5208f2c..1a1ca34 100644 --- a/Standalone/Source/LinuxOffscreenCapture.cpp +++ b/Standalone/Source/LinuxOffscreenCapture.cpp @@ -290,6 +290,7 @@ in vec4 vTileData; out vec4 outColor; uniform int uReadableMode; uniform int uTileEdges; +uniform int uHeightTint; uniform float uHeightCenter; uniform float uHeightRange; uniform float uTileEdgeWidth; @@ -312,7 +313,10 @@ void main() else { float heightT=clamp(0.5+(vHeight-uHeightCenter)/max(uHeightRange,0.001),0.0,1.0); - vec3 heightTint=mix(vec3(0.80,0.91,1.0),vec3(1.0,0.91,0.77),heightT); + float smoothHeightT=heightT*heightT*(3.0-2.0*heightT); + vec3 heightTint=uHeightTint!=0 + ?mix(vec3(0.80,0.91,1.0),vec3(1.0,0.91,0.77),smoothHeightT) + :vec3(1.0); float diffuse=max(dot(n,normalize(uLightDirection)),0.0); float illumination=uHeadlamp!=0?0.78+0.34*diffuse:0.62+0.38*diffuse; color=vec3(0.64,0.62,0.56)*heightTint*illumination; @@ -376,7 +380,7 @@ void main() } bool Render(FScene& Scene,const std::filesystem::path& Output,int Width,int Height, - double HorizontalFovDegrees,bool ReadableMode,bool Headlamp,bool TileEdges, + double HorizontalFovDegrees,bool ReadableMode,bool HeightTint,bool Headlamp,bool TileEdges, double TileEdgeWidthMeters,bool ClipPlaneEnabled, const std::array& ClipPlane) { @@ -472,6 +476,7 @@ void main() glUniform1f(glGetUniformLocation(Program,"uFar"),static_cast(Far)); glUniform1i(glGetUniformLocation(Program,"uReadableMode"),ReadableMode?1:0); glUniform1i(glGetUniformLocation(Program,"uTileEdges"),TileEdges?1:0); + glUniform1i(glGetUniformLocation(Program,"uHeightTint"),HeightTint?1:0); glUniform1f(glGetUniformLocation(Program,"uHeightCenter"), static_cast(Scene.CameraMeters[2])); glUniform1f(glGetUniformLocation(Program,"uHeightRange"),64.0f); @@ -499,13 +504,14 @@ void main() const auto Bytes=std::filesystem::file_size(Output,SizeError); std::printf("[OffscreenCapture] source_triangles=%llu drawn_triangles=%llu tiles=%u " "skirts=%llu visible_skirt_triangles=%llu hidden_same_lod_skirt_triangles=%llu " - "shading=%s tile_edges=%s edge_width_m=%.3f png_bytes=%llu status=PASS path=%s\n", + "shading=%s height_tint=%s tile_edges=%s edge_width_m=%.3f png_bytes=%llu status=PASS path=%s\n", static_cast(Scene.Triangles), static_cast(Scene.DrawnTriangles),Scene.TileCount, static_cast(Scene.SkirtTriangles), static_cast(Scene.VisibleSkirtTriangles), static_cast(Scene.HiddenSameLodSkirtTriangles), - ReadableMode?"readable":"normal",TileEdges?"on":"off",TileEdgeWidthMeters, + ReadableMode?"readable":"normal",HeightTint?"on":"off", + TileEdges?"on":"off",TileEdgeWidthMeters, static_cast(SizeError?0:Bytes),Output.string().c_str()); } glDeleteProgram(Program);glDeleteBuffers(1,&Ebo);glDeleteBuffers(1,&Vbo); @@ -521,13 +527,13 @@ int main(int argc,char** argv) { if(argc<3) { - std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N] [--shade=readable|normal] [--headlamp=on|off] [--tile-edges=on|off] [--tile-edge-width-m=N] [--clip-plane=NX,NY,NZ,D]\n",argv[0]); + std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N] [--shade=readable|normal] [--height-tint=on|off] [--headlamp=on|off] [--tile-edges=on|off] [--tile-edge-width-m=N] [--clip-plane=NX,NY,NZ,D]\n",argv[0]); return 2; } int Width=2560,Height=1440;double Fov=90.0; bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0; bool HasCamera=false;std::array Camera{}; - bool ReadableMode=true,TileEdges=false,Headlamp=false;double TileEdgeWidthMeters=0.15; + bool ReadableMode=true,HeightTint=true,TileEdges=false,Headlamp=false;double TileEdgeWidthMeters=0.15; bool ClipPlaneEnabled=false;std::array ClipPlane{}; for(int I=3;I(Scene.Triangles)); - return Render(Scene,argv[2],Width,Height,Fov,ReadableMode,Headlamp,TileEdges, + return Render(Scene,argv[2],Width,Height,Fov,ReadableMode,HeightTint,Headlamp,TileEdges, TileEdgeWidthMeters,ClipPlaneEnabled,ClipPlane)?0:3; } diff --git a/Standalone/Source/StreamBenchmarkHeadless.cpp b/Standalone/Source/StreamBenchmarkHeadless.cpp index 39547cf..b210ff5 100644 --- a/Standalone/Source/StreamBenchmarkHeadless.cpp +++ b/Standalone/Source/StreamBenchmarkHeadless.cpp @@ -423,6 +423,35 @@ int main(int argc,char** argv) VoxelForgeCore::ResolvedLayoutValidationModeV3::strict,strictIssues,strictError); std::printf("[ResolvedLayoutSnapshot] schema=%u stable_identity=%s strata=%zu bytes=%zu round_trip=PASS\n", resolvedLayout.schemaVersion,resolvedLayout.stableIdentity.c_str(),resolvedLayout.strates.size(),layoutJson.size()); + const auto requestedRecipe=options.FloatingIslandsRecipe + ?std::string_view("floating-islands"):std::string_view("surface-world"); + const auto topStratum=std::max_element(resolvedLayout.strates.begin(),resolvedLayout.strates.end(), + [](const auto& a,const auto& b) + {return a.interiorTopMicrometresgeneratorKind.c_str(); + std::printf("[WorldAssemblerRecipeAudit] requested=%.*s actual=%.*s top_generator=%s surface_world_plan=%s floating_islands_stratum=%s status=%s\n", + static_cast(requestedRecipe.size()),requestedRecipe.data(), + static_cast(actualRecipe.size()),actualRecipe.data(), + topGenerator,hasSurfaceWorldPlan?"present":"absent",hasFloatingIslands?"present":"absent", + actualRecipe==requestedRecipe?"PASS":"FAIL"); + if(actualRecipe!=requestedRecipe) + {std::fprintf(stderr,"Requested recipe %.*s resolved to %.*s\n", + static_cast(requestedRecipe.size()),requestedRecipe.data(), + static_cast(actualRecipe.size()),actualRecipe.data());return 2;} std::printf("[ResolvedLayoutCompatibility] accepted=%s issues=%zu error=%s\n", compatibilityAccepted?"true":"false",compatibilityIssues.size(),compatibilityError.c_str()); for(const auto& issue:compatibilityIssues) diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index 8a2a223..389b8b7 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -1084,6 +1084,8 @@ namespace StandaloneStreamBenchmark double surfaceRadiusReliefMetres=0.0; double longestShortSpanMetres=0.0; bool passed=true; + bool disconnectedEndControlApplicable=false; + bool disconnectedEndControlDetected=false; }; const auto auditPassageColumns=[&](const VoxelForgeCore::World& Field, const VoxelForgeCore::World& Native, @@ -1095,7 +1097,7 @@ namespace StandaloneStreamBenchmark /VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; constexpr std::array,5> supportOffsets{{ {{0.0,0.0}},{{0.45,0.0}},{{-0.45,0.0}},{{0.0,0.45}},{{0.0,-0.45}}}}; - const auto endpointConnected=[&](const std::array& point) + const auto endpointSupportCount=[&](const std::array& point) { const double x=point[0]*qMetres,y=point[1]*qMetres; const double floor=point[2]*qMetres-point[3]*qMetres+0.13; @@ -1109,7 +1111,58 @@ namespace StandaloneStreamBenchmark {solid=true;break;} supported+=solid; } - return supported>=3; + return supported; + }; + const auto endpointConnected=[&](const std::array& point) + {return endpointSupportCount(point)>=3;}; + struct FColumnCapProbe + { + std::uint64_t columnSamples=0,nativeOverlapSamples=0; + double firstOverlapDepthMetres=std::numeric_limits::infinity(); + double minimumNativeDensity=std::numeric_limits::infinity(); + }; + const auto probeColumnCap=[&](const VoxelForgeCore::PassageVoidCrossingAuditV1& crossing, + std::size_t segment,double capZMetres,bool upperCap,double depthLimitMetres) + { + FColumnCapProbe probe; + constexpr double DepthStepMetres=0.25; + constexpr double RadialStepMetres=0.50; + constexpr std::size_t DirectionCount=16; + const double axisX=qToMetres(crossing.columnAxisSubvoxels[0]); + const double axisY=qToMetres(crossing.columnAxisSubvoxels[1]); + for(double depth=DepthStepMetres;depth<=depthLimitMetres+1.0e-6; + depth+=DepthStepMetres) + { + const double z=capZMetres+(upperCap?-depth:depth); + const double maxRadius=qToMetres(crossing.columnMaximumRadiusSubvoxels); + const std::size_t radialSteps=static_cast( + std::ceil(maxRadius/RadialStepMetres)); + for(std::size_t radialStep=0;radialStep<=radialSteps;++radialStep) + { + const double radius=std::min(maxRadius, + radialStep*RadialStepMetres); + for(std::size_t direction=0;direction::infinity(); + bool disconnectedEndControlApplicable=false; + bool disconnectedEndControlDetected=false; for(const auto& crossing:crossings) { if(crossing.passageIndex!=passageIndex)continue; @@ -1157,8 +1213,63 @@ namespace StandaloneStreamBenchmark } if(crossing.lastSegment+1>=passage.controlPoints.size()) {audit.passed=false;continue;} + const double capTopMetres=qToMetres(crossing.columnTopZSubvoxels); + const double capBottomMetres=qToMetres(crossing.columnBottomZSubvoxels); + const auto topCap=probeColumnCap(crossing,crossing.firstSegment, + capTopMetres,true,6.0); + const auto bottomCap=probeColumnCap(crossing,crossing.lastSegment, + capBottomMetres,false,6.0); + const bool topCapConnected=topCap.nativeOverlapSamples>0; + const bool bottomCapConnected=bottomCap.nativeOverlapSamples>0; + const std::uint32_t routeStartSupport=endpointSupportCount( + crossing.startPointSubvoxels); + const std::uint32_t routeEndSupport=endpointSupportCount( + crossing.endPointSubvoxels); + std::printf("[PassageColumnRouteEndpointSupport] id=%s start=%u/5 start_z_m=%.3f end=%u/5 end_z_m=%.3f threshold=3/5\n", + passage.stableId.c_str(),routeStartSupport, + qToMetres(crossing.startPointSubvoxels[2]),routeEndSupport, + qToMetres(crossing.endPointSubvoxels[2])); + const auto printCap=[&](const char* endName,const char* side,double capZ, + const FColumnCapProbe& probe,bool connected) + { + std::printf("[PassageColumnCapAudit] id=%s end=%s side=%s cap_z_m=%.3f column_samples=%llu native_overlap_samples=%llu first_overlap_depth_m=%.3f minimum_native_density=%.6f search_depth_m=6.000 status=%s\n", + passage.stableId.c_str(),endName,side,capZ, + static_cast(probe.columnSamples), + static_cast(probe.nativeOverlapSamples), + probe.firstOverlapDepthMetres,probe.minimumNativeDensity, + connected?"PASS":"FAIL"); + }; + printCap("top","upper",capTopMetres,topCap,topCapConnected); + printCap("bottom","lower",capBottomMetres,bottomCap,bottomCapConnected); + const auto upper=std::find_if(ResolvedLayout->strates.begin(), + ResolvedLayout->strates.end(),[&](const auto& value) + {return value.strateIndex==passage.upperStrateIndex;}); + const double startRouteZ=qToMetres(crossing.startPointSubvoxels[2]); + const double endRouteZ=qToMetres(crossing.endPointSubvoxels[2]); + const double lowerRouteZ=std::min(startRouteZ,endRouteZ); + if(upper!=ResolvedLayout->strates.end() + &&upper->generatorKind=="FloatingIslands" + &&lowerRouteZ0; + disconnectedEndControlDetected=disconnectedEndControlApplicable + &&disconnectedControl.nativeOverlapSamples==0; + std::printf("[PassageColumnDisconnectedEndControl] source=air_gap_truncated_lower_cap cap_z_m=%.3f column_samples=%llu native_overlap_samples=%llu minimum_native_density=%.6f detected=%s\n", + hypotheticalBottomZ, + static_cast(disconnectedControl.columnSamples), + static_cast(disconnectedControl.nativeOverlapSamples), + disconnectedControl.minimumNativeDensity, + disconnectedEndControlDetected?"true":"false"); + } ++audit.columns; - audit.connectedEnds+=connectedStart+connectedEnd; + audit.connectedEnds+=topCapConnected+bottomCapConnected; const double axisX=qToMetres(crossing.columnAxisSubvoxels[0]); const double axisY=qToMetres(crossing.columnAxisSubvoxels[1]); const double minimumRadius=qToMetres(crossing.columnMinimumRadiusSubvoxels); @@ -1262,7 +1373,7 @@ namespace StandaloneStreamBenchmark crossingSurfaceRelief); const double measuredWall=crossingMinimumSurfaceRadius -crossing.maximumRouteOffsetMetres-crossing.maximumBoreRadiusMetres; - const bool columnPass=connectedStart&&connectedEnd + const bool columnPass=topCapConnected&&bottomCapConnected &&minimumWall>=1.50&&crossingRouteFailures==0 &&crossingWallFailures==0&&std::isfinite(measuredWall) &&measuredWall>=1.50&&crossingSurfaceRelief>=0.20; @@ -1270,13 +1381,17 @@ namespace StandaloneStreamBenchmark std::printf("[PassageRockColumnAudit] id=%s segments=%zu..%zu span_m=%.3f axis_m=(%.3f,%.3f) connected=%d/2 route_support=%llu/%llu wall_margin_m=%.3f threshold_m=1.500 radii_m=%.3f..%.3f relief_p2p_m=%.3f nominal_radius_m=%.3f status=%s\n", passage.stableId.c_str(),crossing.firstSegment,crossing.lastSegment, crossing.unsupportedLengthMetres,axisX,axisY, - static_cast(connectedStart)+static_cast(connectedEnd), + static_cast(topCapConnected)+static_cast(bottomCapConnected), static_cast(probeCount-crossingRouteFailures), static_cast(probeCount),measuredWall, crossingMinimumSurfaceRadius,crossingMaximumSurfaceRadius, crossingSurfaceRelief,qToMetres(crossing.columnNominalRadiusSubvoxels), columnPass?"PASS":"FAIL"); } + audit.disconnectedEndControlApplicable|=disconnectedEndControlApplicable; + audit.disconnectedEndControlDetected|=disconnectedEndControlDetected; + if(disconnectedEndControlApplicable&&!disconnectedEndControlDetected) + audit.passed=false; return audit; }; if(Assembly) @@ -1384,6 +1499,8 @@ namespace StandaloneStreamBenchmark double longestUncolumnedSpanMetres=0.0,longestShortSpanMetres=0.0; double l26LegacyBridgeSpanMetres=0.0; bool columnAuditsPassed=true,noLongBridgeAuditPassed=true; + bool columnDisconnectedEndControlApplicable=false; + bool columnDisconnectedEndControlDetected=false; bool l26NegativeControlApplicable=false; const auto plannedVoidCrossings=Shape.passageVoidCrossings(); for(const auto& crossing:plannedVoidCrossings) @@ -1439,6 +1556,8 @@ namespace StandaloneStreamBenchmark longestShortSpanMetres=std::max(longestShortSpanMetres, columnAudit.longestShortSpanMetres); columnAuditsPassed&=columnAudit.passed; + columnDisconnectedEndControlApplicable|=columnAudit.disconnectedEndControlApplicable; + columnDisconnectedEndControlDetected|=columnAudit.disconnectedEndControlDetected; ++openAirAudits; const auto radialSquared=[](const std::array& p) {return p[0]*p[0]+p[1]*p[1];}; @@ -1753,12 +1872,15 @@ namespace StandaloneStreamBenchmark longestShortSpanMetres,l26NegativeControlApplicable ?(l26NoLongBridgeControlDetected?"DETECTED":"MISSED"):"NOT_APPLICABLE", noLongBridgeAuditPassed?"PASS":"FAIL"); - std::printf("[PassageRockColumnAuditSummary] columns=%llu connected_ends=%llu/%llu short_spans=%llu wall_or_support_failures=%llu status=%s\n", + std::printf("[PassageRockColumnAuditSummary] columns=%llu connected_ends=%llu/%llu short_spans=%llu wall_or_support_failures=%llu disconnected_end_control=%s status=%s\n", static_cast(columnCrossings), static_cast(columnConnectedEnds), static_cast(columnCrossings*2), static_cast(shortGroundedSpans), static_cast(columnRouteFailures+columnWallFailures), + columnDisconnectedEndControlApplicable + ?(columnDisconnectedEndControlDetected?"DETECTED":"MISSED") + :"NOT_APPLICABLE", columnAuditsPassed?"PASS":"FAIL"); const bool l23SmoothWallDetected=!looksNatural(0.0,0.0); std::printf("[PassageNaturalnessNegativeControl] source=L23_smooth_unrelieved_wall relief_p2p_m=0.000 threshold_m=%.3f detected=%s\n",