fix(core): smooth tunnel entrance floor; verifier keeps baselines out of /tmp (L47); research R14

The demo tunnel's hard floor cap used a vertical Z - floorZ on a sloped Bezier floor, which is not a signed
distance, so marching cubes produced a staircase through the first 7 m (40-51 mm ripple, 82 mm steps). The
cap now uses the grade-normalised signed distance with a floor-adjacent bore re-application fading over the
entrance; mesh tile Z bounds include cavern bounds. New three-rail entrance audit: L46 fails, L47 passes.
verify: the cost baseline exports only build inputs into Saved/Linux/<out>/baseline-<commit> and removes it;
/tmp unchanged after a full tier. Demo off: owner hashes identical to L46. fast/medium/full PASS.
Known: sawtooth at the tunnel wall foot (next round). Research R14: Windows/MSVC readiness review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-24 12:54:39 +02:00
1 parent e016f6a20f
commit c091e4434f
13 files changed
+839 -27

No files matched your search

+47 -9
View File
@@ -5973,6 +5973,10 @@ private:
} }
const float centerX=bezier(t,0),centerY=bezier(t,1); const float centerX=bezier(t,0),centerY=bezier(t,1);
const float floorZ=bezier(t,2); const float floorZ=bezier(t,2);
const float floorHorizontalSpeed=std::hypot(derivative(t,0),derivative(t,1));
const float floorGrade=floorHorizontalSpeed>1.0e-5f
?derivative(t,2)/floorHorizontalSpeed:0.0f;
const float floorDistanceScale=1.0f/std::sqrt(1.0f+floorGrade*floorGrade);
const float radial=std::hypot(X-centerX,Y-centerY); const float radial=std::hypot(X-centerX,Y-centerY);
const float radius=feature.boreRadiusVoxels; const float radius=feature.boreRadiusVoxels;
const float vertical=(Z-floorZ)/feature.boreHeightVoxels; const float vertical=(Z-floorZ)/feature.boreHeightVoxels;
@@ -6000,11 +6004,12 @@ private:
return; return;
} }
composed=SmoothMaxDistance(composed,-distance,feature.smoothWidthVoxels); composed=SmoothMaxDistance(composed,-distance,feature.smoothWidthVoxels);
// The hard profile cap is deliberately applied after the smooth union and // Apply a signed distance to the sloped floor plane. The old vertical
// relief. Below it, native rock remains untouched; the narrow support above // height field made Marching Cubes quantize the shallow ramp into terraces.
// fills only baseline air. // Below it, native rock remains untouched; the narrow support above fills
// only baseline air.
if(radial<=radius) if(radial<=radius)
composed=std::min(composed,Z-floorZ); composed=std::min(composed,(Z-floorZ)*floorDistanceScale);
}; };
if(scoped) if(scoped)
for(const auto index:tileQueries->cavernFeatureCandidates())apply(index); for(const auto index:tileQueries->cavernFeatureCandidates())apply(index);
@@ -6097,12 +6102,17 @@ private:
t=std::clamp(t-(dx*vx+dy*vy)/denominator,0.0f,1.0f); t=std::clamp(t-(dx*vx+dy*vy)/denominator,0.0f,1.0f);
} }
const float floorZ=bezier(t,2); const float floorZ=bezier(t,2);
const float floorHorizontalSpeed=std::hypot(derivative(t,0),derivative(t,1));
const float floorGrade=floorHorizontalSpeed>1.0e-5f
?derivative(t,2)/floorHorizontalSpeed:0.0f;
const float floorDistanceScale=1.0f/std::sqrt(1.0f+floorGrade*floorGrade);
const float radial=std::hypot(X-bezier(t,0),Y-bezier(t,1)); const float radial=std::hypot(X-bezier(t,0),Y-bezier(t,1));
if(Z<floorZ) const float floorDistance=(Z-floorZ)*floorDistanceScale;
const float floorSdfBand=std::max(1.0f,feature.floorSupportDepthVoxels);
if(Z<floorZ-floorSdfBand)
{ {
// Re-evaluate the narrow fallback after passages and stamps, since // Preserve the existing narrow support outside the signed-distance
// those later operations may open air beneath an otherwise native // band beneath the floor.
// floor. Negative native density is preserved by the min operation.
if(capped>0.0f&&feature.floorSupportDepthVoxels>0.0f if(capped>0.0f&&feature.floorSupportDepthVoxels>0.0f
&&Z>=floorZ-feature.floorSupportDepthVoxels &&Z>=floorZ-feature.floorSupportDepthVoxels
&&radial<feature.boreRadiusVoxels-0.12f) &&radial<feature.boreRadiusVoxels-0.12f)
@@ -6114,8 +6124,36 @@ private:
} }
return; return;
} }
if(radial<=feature.boreRadiusVoxels&&Z<=floorZ+0.8f)
{
const float vertical=(Z-floorZ)/feature.boreHeightVoxels;
const float normalized=std::sqrt((radial/feature.boreRadiusVoxels)
*(radial/feature.boreRadiusVoxels)+vertical*vertical);
float tunnelDistance=(normalized-1.0f)*std::min(
feature.boreRadiusVoxels,feature.boreHeightVoxels);
if(feature.irregularityAmplitudeVoxels>0.0f)
{
const float fade=SmoothUnit((Z-floorZ)/0.8f);
tunnelDistance+=StampValueNoise(
X-feature.controlPointsVoxels[0][0],
Y-feature.controlPointsVoxels[0][1],Z-floorZ,
feature.irregularityWavelengthVoxels,feature.irregularitySeed)
*feature.irregularityAmplitudeVoxels*fade;
}
// Reapply the tunnel SDF in the floor-adjacent strip. Keep a narrow
// correction along the ramp, with a wider fade only at the stamped mouth.
const float heightAboveFloor=Z-floorZ;
const float mouthFade=1.0f-SmoothUnit(t/0.10f);
const float recarveWeight=heightAboveFloor<=0.25f?1.0f:mouthFade;
if(recarveWeight>0.0f)
{
const float recarved=SmoothMaxDistance(capped,-tunnelDistance,
feature.smoothWidthVoxels);
capped=std::lerp(capped,recarved,recarveWeight);
}
}
if(radial<=feature.boreRadiusVoxels) if(radial<=feature.boreRadiusVoxels)
capped=std::min(capped,Z-floorZ); capped=std::min(capped,floorDistance);
}; };
if(scoped) if(scoped)
for(const auto index:tileQueries->cavernFeatureCandidates())apply(index); for(const auto index:tileQueries->cavernFeatureCandidates())apply(index);
+24
View File
@@ -2294,6 +2294,30 @@ namespace VoxelForgeCore
RelevantMinimum=std::min(RelevantMinimum,MinimumZ); RelevantMinimum=std::min(RelevantMinimum,MinimumZ);
RelevantMaximum=std::max(RelevantMaximum,MaximumZ); RelevantMaximum=std::max(RelevantMaximum,MaximumZ);
} }
const auto includeCavernBounds=[&](const auto& Feature)
{
const double MinimumX=Feature.boundsMinimumMicrometres[0]
*MicronsToVoxels;
const double MaximumX=Feature.boundsMaximumMicrometres[0]
*MicronsToVoxels;
const double MinimumY=Feature.boundsMinimumMicrometres[1]
*MicronsToVoxels;
const double MaximumY=Feature.boundsMaximumMicrometres[1]
*MicronsToVoxels;
if(MaximumX<TileMinimumX||MinimumX>TileMaximumX
||MaximumY<TileMinimumY||MinimumY>TileMaximumY)return;
const double MinimumZ=Feature.boundsMinimumMicrometres[2]
*MicronsToVoxels;
const double MaximumZ=Feature.boundsMaximumMicrometres[2]
*MicronsToVoxels;
if(MaximumZ<TileMinimumZ||MinimumZ>TileMaximumZ)return;
RelevantMinimum=std::min(RelevantMinimum,MinimumZ);
RelevantMaximum=std::max(RelevantMaximum,MaximumZ);
};
for(const auto& Room:impl_->resolvedLayout->cavernRooms)
includeCavernBounds(Room);
for(const auto& Edge:impl_->resolvedLayout->cavernEdges)
includeCavernBounds(Edge);
} }
if(std::isfinite(RelevantMinimum)&&std::isfinite(RelevantMaximum)) if(std::isfinite(RelevantMinimum)&&std::isfinite(RelevantMaximum))
{ {
@@ -0,0 +1,401 @@
#!/usr/bin/env python3
"""Capture L47 cavern entrance views and audit the exported floor profile."""
from __future__ import annotations
import argparse
import importlib.util
import json
import math
import os
from pathlib import Path
import subprocess
import sys
import time
ROOT = Path(__file__).resolve().parents[4]
VERIFY = ROOT / "Standalone/Tools/verify"
LINUX37 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37"
sys.path.insert(0, str(VERIFY))
from runner import bounded_run, current_stamp, first_stamp
def load_module(name: str, path: Path):
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise RuntimeError(f"cannot import {path}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
PROFILE = load_module("l45_ring_profile", ROOT /
"Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py")
SHAPE = load_module("l45_shape_audit", LINUX37 / "AuditMouthShapeL37.py")
PARSER = argparse.ArgumentParser(description=__doc__)
PARSER.add_argument("--layout", type=Path, required=True)
PARSER.add_argument("--artifact-dir", type=Path, required=True)
PARSER.add_argument("--capture-dir", type=Path, required=True)
PARSER.add_argument("--data", type=Path, default=ROOT / "Standalone/data/owner_world.json")
OPTIONS = PARSER.parse_args()
ARTIFACT_DIR = OPTIONS.artifact_dir.resolve()
CAPTURE_DIR = OPTIONS.capture_dir.resolve()
LAYOUT_PATH = OPTIONS.layout.resolve()
ARTIFACT_DIR.mkdir(parents=True, exist_ok=True)
CAPTURE_DIR.mkdir(parents=True, exist_ok=True)
PNG_DIR = ARTIFACT_DIR / "capture-png"
PNG_DIR.mkdir(parents=True, exist_ok=True)
HEADLESS = Path(os.environ["VOXELFORGE_L37_HEADLESS"])
RENDERER = Path(os.environ["VOXELFORGE_L37_RENDERER"])
EXPECTED_STAMP = current_stamp()
def run_logged(name: str, command: list[str], *, capture: bool = False) -> str:
log = ARTIFACT_DIR / f"{name}.log"
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = bounded_run(command, timeout=1800, stdout=stream)
elapsed = time.monotonic() - started
output = log.read_text(encoding="utf-8", errors="replace")
if result is None:
raise RuntimeError(f"{name} timed out; see {log}")
print(f"[L47CaptureRun] step={name} elapsed={elapsed:.1f}s "
f"exit={result.returncode} log={log}", flush=True)
if result.returncode != 0:
raise RuntimeError(f"{name} failed; see {log}")
if capture and (first_stamp(output) != EXPECTED_STAMP
or first_stamp(bounded_run(
[str(command[0]), "--print-source-stamp"], timeout=15).stdout or "")
!= EXPECTED_STAMP):
raise RuntimeError(f"{name} executable source stamp mismatch")
return output
def stamp_by_id(layout: dict, stable_id: str) -> dict:
for stamp in layout.get("stamps", []):
if stamp.get("stable_id") == stable_id:
return stamp
raise RuntimeError(f"missing stamp {stable_id}")
def metres(stamp: dict, key: str = "position_um") -> tuple[float, float, float]:
return tuple(value / 1_000_000.0 for value in stamp[key])
def camera_angles(camera, target):
dx, dy, dz = (target[index] - camera[index] for index in range(3))
return (math.degrees(math.atan2(dx, dy)),
math.degrees(math.atan2(dz, math.hypot(dx, dy))))
def fmt(values) -> str:
return ",".join(f"{value:.6f}" for value in values)
def camera_spec(camera, target):
yaw, pitch = camera_angles(camera, target)
return (*camera, yaw, pitch)
def edge_point(edge: dict, t: float) -> tuple[float, float, float]:
points = edge["control_points_um"]
one = 1.0 - t
weights = (one ** 3, 3 * one ** 2 * t, 3 * one * t ** 2, t ** 3)
return tuple(sum(weights[index] * points[index][axis]
for index in range(4)) / 1_000_000.0
for axis in range(3))
def cavern_features(layout: dict):
if (layout.get("cavern_graph_vocabulary_version") != 1
or len(layout.get("cavern_rooms", [])) != 1
or len(layout.get("cavern_edges", [])) != 1):
raise RuntimeError("L47 capture needs one resolved cavern room and edge")
room = layout["cavern_rooms"][0]
edge = layout["cavern_edges"][0]
return room, edge
layout = json.loads(LAYOUT_PATH.read_text(encoding="utf-8"))
room, edge = cavern_features(layout)
portal_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-mouth-approach-v1")
floor_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-v1-floor")
dais_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-v1-dais")
entrance = edge_point(edge, 0.0)
next_ramp = edge_point(edge, 0.08)
portal = metres(portal_stamp)
chamber = metres(room, "center_um")
floor = metres(floor_stamp)
floor_top = floor[2] + floor_stamp["half_extents_um"][2] / 1_000_000.0
dais = metres(dais_stamp)
dais_top = dais[2] + dais_stamp["half_extents_um"][2] / 1_000_000.0
ground_z = entrance[2]
direction_length = math.hypot(next_ramp[0] - entrance[0], next_ramp[1] - entrance[1])
direction = ((next_ramp[0] - entrance[0]) / direction_length,
(next_ramp[1] - entrance[1]) / direction_length)
outward_length = math.hypot(entrance[0] - chamber[0], entrance[1] - chamber[1])
outward = ((entrance[0] - chamber[0]) / outward_length,
(entrance[1] - chamber[1]) / outward_length)
mouth_normal = (-direction[1], direction[0])
mesh = ARTIFACT_DIR / "landmark-l47.vfmesh"
export_anchor = edge_point(edge, 0.25)
export_camera = (export_anchor[0], export_anchor[1], export_anchor[2] + 1.4)
export_target_floor = edge_point(edge, 0.42)
export_target = (export_target_floor[0], export_target_floor[1],
export_target_floor[2] + 1.2)
run_logged("landmark-export", [str(HEADLESS), str(OPTIONS.data), "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=4", "--max-clip-level=0", "--max-level=0",
"--render-distance-chunks=0", "--recipe=surface-world", "--landmark-chamber-demo",
f"--camera={fmt(camera_spec(export_camera, export_target))}",
f"--mesh-export={mesh}", f"--resolved-layout-output={LAYOUT_PATH}"], capture=True)
layout = json.loads(LAYOUT_PATH.read_text(encoding="utf-8"))
room, edge = cavern_features(layout)
portal_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-mouth-approach-v1")
floor_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-v1-floor")
dais_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-v1-dais")
entrance = edge_point(edge, 0.0)
next_ramp = edge_point(edge, 0.08)
portal = metres(portal_stamp)
chamber = metres(room, "center_um")
floor = metres(floor_stamp)
floor_top = floor[2] + floor_stamp["half_extents_um"][2] / 1_000_000.0
dais = metres(dais_stamp)
dais_top = dais[2] + dais_stamp["half_extents_um"][2] / 1_000_000.0
ground_z = entrance[2]
direction_length = math.hypot(next_ramp[0] - entrance[0], next_ramp[1] - entrance[1])
direction = ((next_ramp[0] - entrance[0]) / direction_length,
(next_ramp[1] - entrance[1]) / direction_length)
outward_length = math.hypot(entrance[0] - chamber[0], entrance[1] - chamber[1])
outward = ((entrance[0] - chamber[0]) / outward_length,
(entrance[1] - chamber[1]) / outward_length)
if not mesh.is_file() or mesh.stat().st_size == 0:
raise RuntimeError(f"missing mesh export {mesh}")
entrance_audit = ROOT / "Standalone/Research/WorldAssemblerM3/Linux47/MeasureEntranceL47.py"
run_logged("entrance-floor-control-l46", [sys.executable, str(entrance_audit),
"--mesh", str(ROOT / "Saved/Linux/l46/landmark-l46.vfmesh"),
"--layout", str(ROOT / "Saved/Linux/l46/layouts/landmark-reachability-clang.json"),
"--out", str(ARTIFACT_DIR / "entrance-profile-control-l46.json"), "--expect=fail"])
run_logged("entrance-floor-audit", [sys.executable, str(entrance_audit),
"--mesh", str(mesh), "--layout", str(LAYOUT_PATH),
"--out", str(ARTIFACT_DIR / "entrance-profile-l47.json"), "--expect=pass"])
voxel_um, triangles, tile_count = PROFILE.read_scene(str(mesh), 0,
floor_top - 8.0, entrance[2] + 9.0,
(min(entrance[0], chamber[0]) - 20.0, max(entrance[0], chamber[0]) + 20.0,
min(entrance[1], chamber[1]) - 20.0, max(entrance[1], chamber[1]) + 20.0))
bounds = (min(entrance[0], chamber[0]) - 20.0, max(entrance[0], chamber[0]) + 20.0,
min(entrance[1], chamber[1]) - 20.0, max(entrance[1], chamber[1]) + 20.0)
buckets = PROFILE.build_buckets(triangles, True, bounds, cell_size=1.0)
ground_candidates = []
for ix in range(-20, 21):
for iy in range(-20, 21):
x, y = portal[0] + ix * 0.5, portal[1] + iy * 0.5
dx, dy = x - portal[0], y - portal[1]
distance = math.hypot(dx, dy)
if not 6.0 <= distance <= 10.0:
continue
hit = PROFILE.sample_surface(buckets, x, y, True, 1.0,
preferred_height=ground_z)
if hit is None or abs(hit[0] - ground_z) > 3.0 or hit[1][2] < 0.85:
continue
alignment = max(-1.0, min(1.0,
(dx * mouth_normal[0] + dy * mouth_normal[1]) / distance))
angle = math.acos(alignment)
score = abs(distance - 7.0) + angle * 4.0
ground_candidates.append((score, x, y, hit[0]))
if not ground_candidates:
raise RuntimeError("no walkable mesh ground lies 6 to 10 m from the cave mouth")
_, ground_x, ground_y, outside_ground = min(ground_candidates)
inside_floor = PROFILE.sample_surface(buckets, chamber[0], chamber[1], True, 1.0,
preferred_height=floor_top)
if inside_floor is None or abs(inside_floor[0] - floor_top) > 1.5:
raise RuntimeError(f"chamber floor mesh hit {inside_floor} does not match designed floor {floor_top:.3f} m")
floor_top = inside_floor[0]
ground_camera = (ground_x, ground_y, outside_ground + 1.6)
entry_ground = PROFILE.sample_surface(buckets, entrance[0], entrance[1], True, 1.0,
preferred_height=ground_z)
if entry_ground is None:
raise RuntimeError("cave mouth has no walkable ground surface in the exported mesh")
entry_camera = (entrance[0] + mouth_normal[0] * 3.8,
entrance[1] + mouth_normal[1] * 3.8, ground_z + 1.6)
entry_target_floor = edge_point(edge, 0.30)
entry_target = (entry_target_floor[0], entry_target_floor[1], entry_target_floor[2] + 1.4)
low_camera = (entrance[0] + outward[0] * 1.0,
entrance[1] + outward[1] * 1.0, ground_z + 0.65)
low_target_floor = edge_point(edge, 0.24)
low_target = (low_target_floor[0], low_target_floor[1], low_target_floor[2] + 0.15)
to_dais = (dais[0] - chamber[0], dais[1] - chamber[1])
to_dais_length = math.hypot(*to_dais)
opposite_dais = (-to_dais[0] / to_dais_length, -to_dais[1] / to_dais_length)
inside_x = chamber[0]
inside_y = chamber[1]
inside_floor = PROFILE.sample_surface(buckets, inside_x, inside_y, True, 1.0,
preferred_height=floor_top)
inside_eye_z = inside_floor[0] + 1.6
chamber_camera = (chamber[0] + opposite_dais[0] * 5.5,
chamber[1] + opposite_dais[1] * 5.5, inside_eye_z)
chamber_target = (dais[0], dais[1], dais_top - 0.25)
overview_camera = (entrance[0] + outward[0] * 18.0,
entrance[1] + outward[1] * 18.0, outside_ground + 15.0)
overview_target = ((entrance[0] + chamber[0]) * 0.5,
(entrance[1] + chamber[1]) * 0.5,
(ground_z + floor_top) * 0.5)
inward = (-outward[0], -outward[1])
clip_point = (entrance[0] + outward[0] * 0.5,
entrance[1] + outward[1] * 0.5)
overview_clip = (inward[0], inward[1], 0.0,
-(inward[0] * clip_point[0] + inward[1] * clip_point[1]))
views = (
("ground-entrance", ground_camera, (portal[0], portal[1], portal[2]),
(portal[0], portal[1], ground_z, 20.0), -509.0),
("entrance-looking-in", entry_camera, entry_target, (entrance[0], entrance[1], entrance[2], 16.0), -507.0),
("entrance-floor-low", low_camera, low_target,
(entrance[0], entrance[1], entrance[2], 12.0), ground_z),
("inside-to-dais", chamber_camera, chamber_target,
(chamber[0], chamber[1], chamber[2], 15.0), chamber[2]),
("overview-entrance-and-chamber", overview_camera, overview_target,
((entrance[0] + chamber[0]) * 0.5,
(entrance[1] + chamber[1]) * 0.5,
(ground_z + floor_top) * 0.5, 34.0), -509.0),
)
for name, camera, target, focus, height_center in views:
png = PNG_DIR / f"{name}.png"
jpg = CAPTURE_DIR / f"{name}.jpg"
yaw, pitch = camera_angles(camera, target)
fov = (80.0 if name == "overview-entrance-and-chamber" else
70.0 if name in ("inside-to-dais", "entrance-looking-in") else
58.0 if name == "entrance-floor-low" else 65.0
if name == "ground-entrance" else 60.0)
if name == "ground-entrance":
fov = 55.0
print(f"[L47CaptureCamera] view={name} camera_m=({fmt(camera)}) "
f"target_m=({fmt(target)}) yaw_deg={yaw:.3f} pitch_deg={pitch:.3f} "
f"fov_deg={fov:.1f} entrance_distance_m={math.dist(camera, portal):.3f}", flush=True)
render = [str(RENDERER), str(mesh), str(png), "--size=1600x900", f"--fov-deg={fov:.1f}",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}",
"--shade=readable", "--headlamp=on", "--height-tint=on",
"--light-direction=0.3,-0.4,0.85",
f"--height-center-m={(floor_top if name == 'inside-to-dais' else height_center):.6f}",
f"--height-range-m={4 if name == 'inside-to-dais' else 6 if name == 'entrance-floor-low' else 16}",
"--tile-skirts=off", "--tile-edges=off",
f"--focus-region={fmt(focus)}"]
if name == "overview-entrance-and-chamber":
render.append(f"--clip-plane={fmt(overview_clip)}")
run_logged(f"{name}-render", render, capture=True)
for quality in (4, 6, 8):
run_logged(f"{name}-jpeg-q{quality}", ["ffmpeg", "-nostdin", "-hide_banner",
"-loglevel", "error", "-y", "-i", str(png), "-frames:v", "1",
"-q:v", str(quality), str(jpg)])
if jpg.is_file() and 0 < jpg.stat().st_size <= 300_000:
break
if not jpg.is_file() or not 0 < jpg.stat().st_size <= 300_000:
raise RuntimeError(f"{jpg.name} exceeds the 300 KB capture limit")
quality_log = run_logged(f"{name}-quality", [sys.executable,
str(VERIFY / "capture_quality.py"), str(jpg)])
print(f"[L47CaptureJPEG] file={jpg} bytes={jpg.stat().st_size} "
f"quality_log={quality_log.strip().replace(chr(10), ' ')} status=PASS", flush=True)
def region_triangles(center, radius_xy, z_min, z_max):
selected = []
for triangle in triangles:
points = tuple(vertex[0] for vertex in triangle[2])
centroid = tuple(sum(point[axis] for point in points) / 3.0 for axis in range(3))
if (math.hypot(centroid[0] - center[0], centroid[1] - center[1]) <= radius_xy
and z_min <= centroid[2] <= z_max):
selected.append(triangle)
return selected
def tunnel_wall_triangles(edge, t, height_above_floor):
center = edge_point(edge, t)
before = edge_point(edge, max(0.0, t - 0.01))
after = edge_point(edge, min(1.0, t + 0.01))
tx, ty = after[0] - before[0], after[1] - before[1]
tangent_length = math.hypot(tx, ty)
tx, ty = tx / tangent_length, ty / tangent_length
expected_radius = edge["bore_radius_um"] / 1_000_000.0 * math.sqrt(
1.0 - (height_above_floor / (edge["bore_height_um"] / 1_000_000.0)) ** 2)
selected = []
for triangle in triangles:
points = tuple(vertex[0] for vertex in triangle[2])
centroid = tuple(sum(point[axis] for point in points) / 3.0 for axis in range(3))
dx, dy = centroid[0] - center[0], centroid[1] - center[1]
longitudinal = dx * tx + dy * ty
lateral = abs(-dy * tx + dx * ty)
vertical = centroid[2] - center[2]
if (abs(longitudinal) <= 1.5
and abs(lateral - expected_radius) <= 0.7
and abs(vertical - height_above_floor) <= 0.75):
selected.append(triangle)
print(f"[L47TunnelWallWindow] t={t:.3f} center_m=({center[0]:.3f},"
f"{center[1]:.3f},{center[2]:.3f}) expected_radius_m={expected_radius:.3f} "
f"longitudinal_half_width_m=1.500 vertical_half_width_m=0.750 "
f"triangles={len(selected)}", flush=True)
return selected
def audit_shape(name, region, center):
metrics = SHAPE.analyze_triangles(region, center[:2], center[2],
crop_to_center=False, include_sides=True)
passed = (metrics["triangles"] > 0
and metrics["planar_area"] < SHAPE.PLANE_LIMIT_M2
and metrics["straight"] <= SHAPE.STRAIGHT_LIMIT_M
and metrics["corners"] == 0)
print(f"[LandmarkBuiltShape] region={name} triangles={metrics['triangles']} "
f"planar_patch_m2={metrics['planar_area']:.4f} "
f"straight_run_m={metrics['straight']:.4f} sharp_corners={metrics['corners']} "
f"corner_turn_max_deg={metrics['corner_turn_max']:.2f} "
f"thresholds=({SHAPE.PLANE_LIMIT_M2:.2f}m2,{SHAPE.STRAIGHT_LIMIT_M:.2f}m,"
f"{SHAPE.CORNER_TURN_LIMIT_DEG:.0f}deg) status={'PASS' if passed else 'FAIL'}",
flush=True)
if not passed:
raise RuntimeError(f"L37 built-shape thresholds failed in {name}")
return metrics
entrance_metrics = audit_shape("cave-mouth", region_triangles(
portal, 5.0, ground_z + 0.8, ground_z + 4.5),
(portal[0], portal[1], ground_z))
tunnel_mid = edge_point(edge, 0.64)
tunnel_metrics = audit_shape("tunnel-wall-edge", tunnel_wall_triangles(
edge, 0.64, 1.2),
(tunnel_mid[0], tunnel_mid[1], tunnel_mid[2] + 1.2))
dais_surface = PROFILE.sample_surface(buckets, dais[0], dais[1], True, 1.0,
preferred_height=dais_top - 0.4)
if dais_surface is None:
raise RuntimeError("dais stamp has no upward-facing surface in the exported mesh")
dais_rise = dais_surface[0] - floor_top
print(f"[LandmarkDaisSurface] center_m=({dais[0]:.3f},{dais[1]:.3f}) "
f"floor_z_m={floor_top:.3f} top_z_m={dais_surface[0]:.3f} "
f"rise_m={dais_rise:.3f} minimum_m=1.000 "
f"status={'PASS' if dais_rise >= 1.0 else 'FAIL'}", flush=True)
if dais_rise < 1.0:
raise RuntimeError(f"dais rises only {dais_rise:.3f} m above the chamber floor")
dais_metrics = audit_shape("dais-platform", region_triangles(
dais, 1.7, floor_top + 0.6, dais_top + 0.4),
(dais[0], dais[1], dais_surface[0]))
metrics_path = ARTIFACT_DIR / "landmark-shape-metrics.json"
metrics_path.write_text(json.dumps({
"graph_vocabulary_version": layout["cavern_graph_vocabulary_version"],
"entry_m": entrance, "portal_m": portal, "entry_ground_m": ground_z,
"chamber_center_m": chamber, "chamber_floor_m": floor_top,
"tunnel_midpoint_m": tunnel_mid,
"dais_center_m": dais, "dais_top_m": dais_top,
"dais_surface_m": dais_surface[0], "dais_rise_m": dais_rise,
"l37_thresholds": {"planar_patch_m2_lt": SHAPE.PLANE_LIMIT_M2,
"straight_run_m_lte": SHAPE.STRAIGHT_LIMIT_M,
"corner_turn_deg_lt": SHAPE.CORNER_TURN_LIMIT_DEG},
"entrance_arch": entrance_metrics, "tunnel_wall": tunnel_metrics,
"dais_platform": dais_metrics,
"mesh_tiles": tile_count, "mesh_voxel_um": voxel_um,
}, indent=2) + "\n", encoding="utf-8")
print(f"[L47CaptureShapeMetrics] file={metrics_path} status=PASS", flush=True)
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""Measure the L46 entrance floor from open terrain through its first ten metres."""
from __future__ import annotations
import importlib.util
import argparse
import json
import math
from pathlib import Path
import sys
ROOT = Path(__file__).resolve().parents[4]
PROFILE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
spec = importlib.util.spec_from_file_location("ring_profile", PROFILE_PATH)
if spec is None or spec.loader is None:
raise RuntimeError(f"cannot load {PROFILE_PATH}")
PROFILE = importlib.util.module_from_spec(spec)
spec.loader.exec_module(PROFILE)
ARTIFACTS = ROOT / "Saved/Linux/l47"
PARSER = argparse.ArgumentParser(description=__doc__)
PARSER.add_argument("--layout", type=Path,
default=ROOT / "Saved/Linux/l46/layouts/landmark-reachability-clang.json")
PARSER.add_argument("--mesh", type=Path,
default=ROOT / "Saved/Linux/l46/landmark-l46.vfmesh")
PARSER.add_argument("--out", type=Path, default=ARTIFACTS / "entrance-profile-before.json")
PARSER.add_argument("--expect", choices=("pass", "fail", "measure"), default="measure")
PARSER.add_argument("--lod", default="0", help="LOD level, or 'all' to include every exported level")
OPTIONS = PARSER.parse_args()
LAYOUT = OPTIONS.layout.resolve()
MESH = OPTIONS.mesh.resolve()
OUT = OPTIONS.out.resolve()
layout = json.loads(LAYOUT.read_text(encoding="utf-8"))
edge = layout["cavern_edges"][0]
controls = [[value / 1_000_000.0 for value in point]
for point in edge["control_points_um"]]
def bezier(t: float) -> tuple[float, float, float]:
u = 1.0 - t
weights = (u ** 3, 3.0 * u * u * t, 3.0 * u * t * t, t ** 3)
return tuple(sum(weights[i] * controls[i][axis] for i in range(4))
for axis in range(3))
route = [bezier(i / 4096.0) for i in range(4097)]
arc = [0.0]
for a, b in zip(route, route[1:]):
arc.append(arc[-1] + math.hypot(b[0] - a[0], b[1] - a[1]))
def route_at(distance: float) -> tuple[float, float, float]:
if distance < 0.0:
start = route[0]
dx = route[1][0] - start[0]
dy = route[1][1] - start[1]
dz = route[1][2] - start[2]
horizontal = math.hypot(dx, dy)
scale = distance / (arc[1] - arc[0])
return start[0] + dx * scale, start[1] + dy * scale, start[2] + dz * scale
index = min(len(arc) - 2, max(0, next(
(i for i, value in enumerate(arc) if value >= distance), len(arc) - 1) - 1))
span = arc[index + 1] - arc[index]
t = 0.0 if span <= 0.0 else (distance - arc[index]) / span
return tuple(route[index][axis] + (route[index + 1][axis] - route[index][axis]) * t
for axis in range(3))
entry = route[0]
tangent = route[1]
tx, ty = tangent[0] - entry[0], tangent[1] - entry[1]
length = math.hypot(tx, ty)
tx, ty = tx / length, ty / length
nx, ny = -ty, tx
requested_lod = None if OPTIONS.lod == "all" else int(OPTIONS.lod)
voxel_um, triangles, tile_count = PROFILE.read_scene(
str(MESH), requested_lod, entry[2] - 7.0, entry[2] + 5.0,
(entry[0] - 14.0, entry[0] + 14.0, entry[1] - 14.0, entry[1] + 14.0))
buckets = PROFILE.build_buckets(
triangles, True,
(entry[0] - 14.0, entry[0] + 14.0, entry[1] - 14.0, entry[1] + 14.0),
cell_size=1.0)
down_buckets = PROFILE.build_buckets(
triangles, False,
(entry[0] - 14.0, entry[0] + 14.0, entry[1] - 14.0, entry[1] + 14.0),
cell_size=1.0)
mesh_points = [vertex[0] for triangle in triangles for vertex in triangle[2]]
print(f"[EntranceProfileGeometry] entry_m=({entry[0]:.3f},{entry[1]:.3f},{entry[2]:.3f}) "
f"point10_m=({route_at(10.0)[0]:.3f},{route_at(10.0)[1]:.3f},"
f"{route_at(10.0)[2]:.3f}) mesh_xy=({min(p[0] for p in mesh_points):.3f},"
f"{max(p[0] for p in mesh_points):.3f},{min(p[1] for p in mesh_points):.3f},"
f"{max(p[1] for p in mesh_points):.3f}) mesh_z=({min(p[2] for p in mesh_points):.3f},"
f"{max(p[2] for p in mesh_points):.3f})")
for sample_distance in range(11):
sx, sy, expected = route_at(float(sample_distance))
bucket = buckets.get((math.floor(sx), math.floor(sy)), ())
hit = PROFILE.sample_surface(buckets, sx, sy, True, 1.0,
preferred_height=expected)
down_hit = PROFILE.sample_surface(down_buckets, sx, sy, False, 1.0,
preferred_height=expected)
print(f"[EntranceProfileBucket] distance_m={sample_distance:.1f} "
f"xy_m=({sx:.3f},{sy:.3f}) triangles={len(bucket)} "
f"hit_z_m={None if hit is None else round(hit[0], 4)} "
f"down_hit_z_m={None if down_hit is None else round(down_hit[0], 4)} "
f"expected_z_m={expected:.4f}")
rows = []
for rail, lateral in (("left", -1.5), ("center", 0.0), ("right", 1.5)):
distance = -5.0
while distance <= 10.000001:
x, y, expected = route_at(distance)
x += nx * lateral
y += ny * lateral
hit = PROFILE.sample_surface(buckets, x, y, True, 1.0,
preferred_height=expected)
rows.append({
"rail": rail,
"distance_m": round(distance, 3),
"x_m": x,
"y_m": y,
"expected_floor_m": expected,
"surface_z_m": None if hit is None else hit[0],
"normal_z": None if hit is None else hit[1][2],
"residual_m": None if hit is None else hit[0] - expected,
})
distance += 0.125
results = {}
for rail in ("left", "center", "right"):
profile = [row for row in rows if row["rail"] == rail and row["surface_z_m"] is not None
and 0.0 <= row["distance_m"] <= 10.0]
jumps = []
for previous, current in zip(profile, profile[1:]):
expected_delta = current["expected_floor_m"] - previous["expected_floor_m"]
residual_delta = current["residual_m"] - previous["residual_m"]
if abs(residual_delta) >= 0.025:
jumps.append({"from_m": previous["distance_m"], "to_m": current["distance_m"],
"delta_m": current["surface_z_m"] - previous["surface_z_m"],
"residual_delta_m": residual_delta,
"expected_delta_m": expected_delta})
residuals = [row["residual_m"] for row in profile]
ripple_residuals = []
for row in profile:
nearby = [other["residual_m"] for other in profile
if abs(other["distance_m"] - row["distance_m"]) <= 1.0]
ripple_residuals.append((row["distance_m"], row["residual_m"]
- sum(nearby) / max(1, len(nearby))))
amplitude, period = 0.0, 0.0
for candidate in (0.375 + index * 0.125 for index in range(30)):
cosine = sum(value * math.cos(2.0 * math.pi * distance / candidate)
for distance, value in ripple_residuals)
sine = sum(value * math.sin(2.0 * math.pi * distance / candidate)
for distance, value in ripple_residuals)
current_amplitude = 2.0 * math.hypot(cosine, sine) / max(1, len(ripple_residuals))
if current_amplitude > amplitude:
amplitude, period = current_amplitude, candidate
step_error = max((abs(item["residual_delta_m"]) for item in jumps), default=0.0)
coverage = len(profile) / 81.0
rail_passed = (coverage >= 0.85 and amplitude <= 0.015 and step_error <= 0.05)
results[rail] = {
"coverage": coverage,
"max_adjacent_step_m": max((abs(item["delta_m"]) for item in jumps), default=0.0),
"max_step_residual_m": step_error,
"ripple_amplitude_m": amplitude,
"ripple_period_m": period,
"passed": rail_passed,
"jumps_over_0_04m": jumps,
"residual_peak_to_peak_m": max(residuals) - min(residuals) if residuals else None,
}
payload = {
"layout": str(LAYOUT.relative_to(ROOT)),
"mesh": str(MESH.relative_to(ROOT)),
"voxel_um": voxel_um,
"selected_tiles": tile_count,
"sample_spacing_m": 0.125,
"distance_range_m": [-5.0, 10.0],
"lateral_offsets_m": {"left": -1.5, "center": 0.0, "right": 1.5},
"rails": results,
"samples": rows,
}
all_passed = all(result["passed"] for result in results.values())
expected_ok = (OPTIONS.expect == "measure"
or (OPTIONS.expect == "pass" and all_passed)
or (OPTIONS.expect == "fail" and not all_passed))
OUT.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
print(f"[EntranceProfileBefore] voxel_um={voxel_um} tiles={tile_count} "
f"lod={OPTIONS.lod} spacing_m=0.125 distance_m=-5.000..10.000 status=MEASURED")
for rail, summary in results.items():
print(f"[EntranceFloorRailAudit] rail={rail} coverage={summary['coverage']:.3f} "
f"ripple_amplitude_m={summary['ripple_amplitude_m']:.4f} "
f"ripple_period_m={summary['ripple_period_m']:.3f} "
f"max_step_residual_m={summary['max_step_residual_m']:.4f} "
f"status={'PASS' if summary['passed'] else 'FAIL'}")
for jump in summary["jumps_over_0_04m"]:
print(f"[EntranceProfileJump] rail={rail} from_m={jump['from_m']:.3f} "
f"to_m={jump['to_m']:.3f} delta_m={jump['delta_m']:+.4f} "
f"expected_delta_m={jump['expected_delta_m']:+.4f} "
f"residual_delta_m={jump['residual_delta_m']:+.4f}")
failed_rails = sum(not result["passed"] for result in results.values())
print(f"[EntranceFloorMeshAudit] samples_per_rail=81 distance_m=0.000..10.000 "
f"rails=3 failed_rails={failed_rails} observed={'PASS' if all_passed else 'FAIL'} "
f"expected={OPTIONS.expect.upper()} status={'PASS' if expected_ok else 'FAIL'}")
print(f"[EntranceProfileJSON] file={OUT} status={'PASS' if expected_ok else 'FAIL'}")
if not expected_ok:
raise SystemExit(1)
@@ -0,0 +1,38 @@
# Linux47 report
## Entrance floor
The L46 mesh has a repeating staircase through the first 7 m: 39.9–51.4 mm ripple and 81.8–82.9 mm largest residual step. The cause was the final hard floor cap, applied after the mouth and terrain stamps, using vertical `Z-floorZ` on a sloped Bezier floor. That field is not a signed distance to the ramp, so Marching Cubes interpolated a distorted gradient; stamp fill beside the cap made the ridges visible. Voxel height quantisation was not the main cause.
The cap now uses the grade-normalised signed distance. The tunnel SDF is reapplied in a floor-adjacent band, with the wider correction fading over the first 10% of the entrance; the narrow floor correction continues along the ramp. This removes the ridges without changing the tunnel wall profile. Mesh tile Z bounds now include resolved cavern bounds too; without that, the exported ramp lost triangles past about 6.6 m.
The mesh audit samples 0–10 m at 0.125 m on three rails (−1.5, 0, +1.5 m), requiring ≥85% coverage, ≤15 mm ripple and ≤50 mm adjacent residual step. L46 fails all rails: coverage 0.63–0.68, ripple 39.9–51.4 mm, step 81.8–82.9 mm. L47 passes all rails: 100% coverage, ripple 0.4–1.2 mm, no measured residual step. The Core audit also passes 243 samples with 0.3 mm maximum step and 0.0 mm reported ripple. Buried L43 and segmented-ramp L45 controls still fail as expected.
## Verifier, hashes and cost
The cost baseline now exports only Core, Source, Compat, and the build/helper scripts to `Saved/Linux/l47/baseline-e016f6a20fcfac785c9870b3d1133fd5df47a6d4`; an exit handler removes the export. It does not copy the repository to `/tmp`. `/tmp` was **28K before** and **28K after** the full tier; the saved top-level-entry comparison is identical and the baseline export was removed.
Source stamp / source manifest: `68328b0c2a154e261eba94a067eb7d879f37ef987a54758275b4d14e3e9746d3`. Demo-off owner hashes match L46 exactly for both recipes, with W1=W12. Whole-view hashes (density/mesh/payload/topology): floating-islands `598e9daaa1744ccb / 3114e0f4f6dbf734 / c49c4e48bff74089 / cadd4edd10d4d8ef`; surface-world `7dc75e2c09302258 / 0cb34c40ceec4ce3 / 9d23e8c46b515cf1 / 2962c9464991eedd`. Demo-on determinism passed 30/30 cells.
Five-pair median cost: floating-islands 16.390→16.435 s (+0.27%); surface-world 1.722→1.717 s (−0.29%). The baseline build took 40.2 s.
## Tiers, captures and failed runs
Fast **PASS** (195.9 s), medium **PASS** (567.8 s), full **PASS** (1606.5 s); zero failed checks. All used source stamp above. Final captures: [ground entrance](captures/ground-entrance.jpg), [looking in](captures/entrance-looking-in.jpg), [overview](captures/overview-entrance-and-chamber.jpg), [inside to dais](captures/inside-to-dais.jpg), and [low entrance floor](captures/entrance-floor-low.jpg).
Exploration failures: the first entrance gate caught a 171 mm step; stamp-only re-carving restored about 40 mm ripple and a 100 mm step; full-width re-carving failed the wall-shape check at t=0.58. The final mouth-local fade passed the wall check at t=0.64. The first fast invocation also stopped because the executable had not yet been built; the rebuilt fast tier passed. L46/L43/L45 negative controls are intentional failures and passed their expected-fail checks. Work stayed within the 15-trial budget.
## Orchestrator review (Claude)
Accepted, with one new defect logged. The stamp matches the final Clang and GCC builds (`68328b0c…`); demo-off hashes
are identical to L46; `/tmp` is unchanged after the full tier (1.8 MB used). The entrance floor is smooth now, and
the cause, a vertical cap distance on a sloped floor, was measured before the fix.
**New defect:** a row of **jagged sawtooth teeth along the wall foot,** where the floor-adjacent band correction meets
the bore wall. It shows in `entrance-looking-in.jpg` and along the edge in the overview. The three floor rails (−1.5,
0, +1.5 m) don't reach the 2.6 m wall foot. **This is the third time** a fix has moved an artifact to where the
audit isn't looking (L46 inside-only, L47 rails only). L48 adds a whole-surface artifact audit.
R14: no likely MSVC or UHT blocker. The Unreal adapter does not resolve stamps or cavern graphs, so the L43–L47 demos
are standalone-only until the adapter supplies those records. The legacy chunk callers agree with the micrometre
layout only at the default 8 m width.
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

@@ -0,0 +1,31 @@
# VoxelForge R14 — Windows build review
**Verdict:** I found no likely MSVC or UHT compile blocker in the requested diff. This is a source review, not a build result. Runtime behavior has one important condition: the legacy chunk callers align with the new physical layout only at the default 8 m width.
## Compile and UHT review
The diff adds no designated initializers, `__int128`, GNU builtins, `[[likely]]`, or `std::visit` use. The `std::variant` code has `<variant>` available; Core uses C++20, and the `std::llround` calls have `<cmath>` available. The new `FStrateSlot` metadata is plain non-`UPROPERTY` data.
| File:line | Issue | Surety | One-line fix |
|---|---|---|---|
| [CoreNamespacePreamble.h:60](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/CoreNamespacePreamble.h:60), [VoxelForgeCore.Build.cs:41](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForgeCore/VoxelForgeCore.Build.cs:41) | Toolchain check: the Core build uses C++20 and MSVC’s `fp_contract` pragma. Core pins C++20 and precise FP; confirm the owner’s UBT-selected MSVC version and flags. | check | Use the UE-supported VS2022 toolset and confirm `/std:c++20` and `/fp:precise` in the build log. |
Epic documents C++20 as UE’s default and minimum, and UBT’s module settings come from `.Build.cs` files ([C++ standard](https://dev.epicgames.com/documentation/unreal-engine/epic-cplusplus-coding-standard-for-unreal-engine), [module properties](https://dev.epicgames.com/documentation/unreal-engine/module-properties-in-unreal-engine)). Microsoft documents that `/fp:precise` disables contraction by default starting with Visual Studio 2022; earlier versions could contract, while the Core preamble explicitly requests `fp_contract(off)` ([`/fp`](https://learn.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior?view=msvc-170), [`fp_contract`](https://learn.microsoft.com/en-us/cpp/preprocessor/fp-contract?view=msvc-160)).
## Behaviour risks
- **Chunk coordinates versus micrometre bounds:** [VoxelStrateManager.cpp:1201](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelStrateManager.cpp:1201) resolves physical layout width in micrometres, but [GetStrateForChunk/GetStrateChunkZBounds](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelStrateManager.cpp:6298) and [IsGapChunk](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelStrateManager.cpp:6398) still treat `ChunkCoord.Z` as a layout-chunk index. Normal world chunk coordinates use `CHUNK_SIZE` voxels ([VoxelTypes.h:285](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Public/VoxelTypes.h:285)). Those units match at the default 8 m width with 25 cm voxels; the setting also allows 0.25–64 m. At other widths, generator selection, gap detection, and transition distances can disagree with the physical slot bounds. Enforce the matching width or convert these chunk APIs using the actual streaming-chunk width.
- **Stamp and cavern graph records:** The Unreal adapter builds strata and passages, then finalizes its layout JSON ([VoxelForgeCoreAdapter.cpp:1078](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1078), [1260](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1260)); it does not call either stamp or graph resolver. The Core world is active in Unreal, but the adapter currently supplies it with no L43–L46 stamp or graph records. The current headless demo calls those resolvers directly ([StreamBenchmarkHeadless.cpp:933](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StreamBenchmarkHeadless.cpp:933)). To get that geometry in Unreal, the adapter needs to build and serialize those records.
- **Bezier FP contraction:** The new Newton solve is in [ShapeWorldInternal.h:6090](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:6090). Core’s compile preamble disables contraction for MSVC, and `World::compile` runs a contraction self-check before compiling the shape. The solve itself has no graph input from the current Unreal adapter, so it should not execute in the current plugin path. Confirm the owner’s MSVC toolset; Microsoft documents version-dependent `/fp:precise` defaults as above.
## First-build checklist
1. Confirm the UE minor version and UBT-selected VS2022/MSVC toolset.
2. Build the Win64 Development Editor target through UBT so it runs UHT and compiles both plugin modules; UBT takes module settings from `.Build.cs`.
3. Check the first UHT or MSVC diagnostic, then confirm the Core compile flags include C++20 and precise FP.
4. Launch the editor with the default 8 m layout and confirm Core world initialization succeeds, including its FP self-check.
5. If using a non-default layout width, resolve the chunk-coordinate mismatch before relying on streamed generator and gap behavior. Do not expect the stamp/graph demo geometry in Unreal until the adapter supplies those records.
I left the tree untouched. `git status` showed `Standalone/Research/WorldAssemblerM3/Linux18/exports/` as pre-existing untracked content.
+63 -7
View File
@@ -504,8 +504,12 @@ namespace StandaloneStreamBenchmark
struct FLandmarkTunnelRippleResult struct FLandmarkTunnelRippleResult
{ {
std::size_t floorSamples=0,wallSamples=0; std::size_t floorSamples=0,entranceFloorSamples=0,wallSamples=0;
double floorPeakPeriodMetres=0.0,floorPeakAmplitudeMetres=0.0; double floorPeakPeriodMetres=0.0,floorPeakAmplitudeMetres=0.0;
double entranceFloorMaxStepMetres=0.0;
double entranceFloorWorstDistanceMetres=0.0;
double entranceFloorPriorResidualMetres=0.0,entranceFloorCurrentResidualMetres=0.0;
std::size_t entranceFloorWorstTrack=0;
double wallPeakPeriodMetres=0.0,wallPeakAmplitudeMetres=0.0; double wallPeakPeriodMetres=0.0,wallPeakAmplitudeMetres=0.0;
bool valid=false,passed=false; bool valid=false,passed=false;
}; };
@@ -601,7 +605,11 @@ namespace StandaloneStreamBenchmark
return closest; return closest;
}; };
std::vector<std::pair<double,double>> floorResiduals; std::vector<std::pair<double,double>> floorResiduals;
for(double distance=0.75;distance<=length-0.75;distance+=0.125) std::array<std::vector<std::pair<double,double>>,3> floorTrackResiduals;
std::array<std::optional<double>,3> previousFloorResiduals;
std::array<double,3> previousFloorDistances{};
constexpr std::array<double,3> FloorTrackOffsets={-1.5,0.0,1.5};
for(double distance=0.0;distance<=length-0.75;distance+=0.125)
{ {
const auto point=atDistance(distance); const auto point=atDistance(distance);
const auto prior=atDistance(std::max(0.0,distance-0.1)); const auto prior=atDistance(std::max(0.0,distance-0.1));
@@ -611,16 +619,40 @@ namespace StandaloneStreamBenchmark
if(tangentLength<1.0e-6)continue; if(tangentLength<1.0e-6)continue;
const double normalX=-ty/tangentLength,normalY=tx/tangentLength; const double normalX=-ty/tangentLength,normalY=tx/tangentLength;
double offsetSum=0.0;std::size_t offsetCount=0; double offsetSum=0.0;std::size_t offsetCount=0;
for(const double lateral:{-0.35,0.0,0.35}) for(std::size_t track=0;track<FloorTrackOffsets.size();++track)
{ {
const double lateral=FloorTrackOffsets[track];
const auto root=floorRoot(point.x+normalX*lateral, const auto root=floorRoot(point.x+normalX*lateral,
point.y+normalY*lateral,point.floor); point.y+normalY*lateral,point.floor);
if(root){offsetSum+=*root-point.floor;++offsetCount;} if(!root)continue;
const double residual=*root-point.floor;
floorTrackResiduals[track].push_back({distance,residual});
offsetSum+=residual;++offsetCount;
if(distance<=10.0+1.0e-9)
{
++result.entranceFloorSamples;
if(previousFloorResiduals[track]
&&distance-previousFloorDistances[track]<=0.2)
{
const double step=std::abs(residual-*previousFloorResiduals[track]);
if(step>result.entranceFloorMaxStepMetres)
{
result.entranceFloorMaxStepMetres=step;
result.entranceFloorWorstDistanceMetres=distance;
result.entranceFloorPriorResidualMetres=*previousFloorResiduals[track];
result.entranceFloorCurrentResidualMetres=residual;
result.entranceFloorWorstTrack=track;
}
}
}
previousFloorResiduals[track]=residual;
previousFloorDistances[track]=distance;
} }
if(offsetCount) if(offsetCount)
{floorResiduals.push_back({distance,offsetSum/offsetCount});} {floorResiduals.push_back({distance,offsetSum/offsetCount});}
} }
result.floorSamples=floorResiduals.size(); result.floorSamples=floorTrackResiduals[0].size()
+floorTrackResiduals[1].size()+floorTrackResiduals[2].size();
const auto peakPeriod=[&](const std::vector<std::pair<double,double>>& samples, const auto peakPeriod=[&](const std::vector<std::pair<double,double>>& samples,
double minimumPeriod,double maximumPeriod, double minimumPeriod,double maximumPeriod,
double& outPeriod) double& outPeriod)
@@ -642,6 +674,17 @@ namespace StandaloneStreamBenchmark
}; };
result.floorPeakAmplitudeMetres=peakPeriod(floorResiduals,0.375, result.floorPeakAmplitudeMetres=peakPeriod(floorResiduals,0.375,
std::min(8.0,length/2.0),result.floorPeakPeriodMetres); std::min(8.0,length/2.0),result.floorPeakPeriodMetres);
for(const auto& track:floorTrackResiduals)
{
double trackPeriod=0.0;
const double trackAmplitude=peakPeriod(track,0.375,
std::min(8.0,length/2.0),trackPeriod);
if(trackAmplitude>result.floorPeakAmplitudeMetres)
{
result.floorPeakAmplitudeMetres=trackAmplitude;
result.floorPeakPeriodMetres=trackPeriod;
}
}
std::vector<std::pair<double,double>> wallResiduals; std::vector<std::pair<double,double>> wallResiduals;
const double expectedWallRadius=edge!=layout.cavernEdges.end() const double expectedWallRadius=edge!=layout.cavernEdges.end()
@@ -702,8 +745,10 @@ namespace StandaloneStreamBenchmark
} }
result.wallPeakAmplitudeMetres=peakPeriod(wallRippleResiduals,0.25, result.wallPeakAmplitudeMetres=peakPeriod(wallRippleResiduals,0.25,
std::min(1.0,length/2.0),result.wallPeakPeriodMetres); std::min(1.0,length/2.0),result.wallPeakPeriodMetres);
result.valid=result.floorSamples>=16&&result.wallSamples>=16; result.valid=result.floorSamples>=16&&result.entranceFloorSamples>=150
&&result.wallSamples>=16;
result.passed=result.valid&&result.floorPeakAmplitudeMetres<=0.015 result.passed=result.valid&&result.floorPeakAmplitudeMetres<=0.015
&&result.entranceFloorMaxStepMetres<=0.05
&&result.wallPeakAmplitudeMetres<=0.035; &&result.wallPeakAmplitudeMetres<=0.035;
return result; return result;
} }
@@ -1259,6 +1304,16 @@ namespace StandaloneStreamBenchmark
{std::fprintf(stderr,"[LandmarkReachabilityAudit] missing cavern room node\n");return 2;} {std::fprintf(stderr,"[LandmarkReachabilityAudit] missing cavern room node\n");return 2;}
const auto ripples=AuditLandmarkTunnelRipples(Shape,*ResolvedLayout, const auto ripples=AuditLandmarkTunnelRipples(Shape,*ResolvedLayout,
World.CoreVoxelSizeMicrons); World.CoreVoxelSizeMicrons);
const bool entranceFloorPassed=ripples.entranceFloorSamples>=150
&&ripples.entranceFloorMaxStepMetres<=0.05
&&ripples.floorPeakAmplitudeMetres<=0.015;
std::printf("[CavernEntranceFloorAudit] layout=L46 samples=%zu distance_m=0.000..10.000 rails=3 max_step_m=%.4f step_limit_m=0.050 ripple_peak_m=%.4f ripple_limit_m=0.015 status=%s\n",
ripples.entranceFloorSamples,ripples.entranceFloorMaxStepMetres,
ripples.floorPeakAmplitudeMetres,entranceFloorPassed?"PASS":"FAIL");
std::printf("[CavernEntranceFloorStepDetail] rail=%zu distance_m=%.3f residual_m=%.4f->%.4f\n",
ripples.entranceFloorWorstTrack,ripples.entranceFloorWorstDistanceMetres,
ripples.entranceFloorPriorResidualMetres,
ripples.entranceFloorCurrentResidualMetres);
std::printf("[CavernTunnelRipple] layout=L46 floor_samples=%zu floor_peak_period_m=%.3f floor_peak_amplitude_m=%.4f floor_limit_m=0.015 wall_samples=%zu wall_peak_period_m=%.3f wall_peak_amplitude_m=%.4f wall_period_band_m=0.250..1.000 wall_detrend_window_m=2.000 wall_limit_m=0.035 status=%s\n", std::printf("[CavernTunnelRipple] layout=L46 floor_samples=%zu floor_peak_period_m=%.3f floor_peak_amplitude_m=%.4f floor_limit_m=0.015 wall_samples=%zu wall_peak_period_m=%.3f wall_peak_amplitude_m=%.4f wall_period_band_m=0.250..1.000 wall_detrend_window_m=2.000 wall_limit_m=0.035 status=%s\n",
ripples.floorSamples,ripples.floorPeakPeriodMetres, ripples.floorSamples,ripples.floorPeakPeriodMetres,
ripples.floorPeakAmplitudeMetres,ripples.wallSamples, ripples.floorPeakAmplitudeMetres,ripples.wallSamples,
@@ -1318,7 +1373,8 @@ namespace StandaloneStreamBenchmark
static_cast<unsigned long long>(buried.unsupportedSamples), static_cast<unsigned long long>(buried.unsupportedSamples),
static_cast<unsigned long long>(buried.gradeFailures), static_cast<unsigned long long>(buried.gradeFailures),
buried.passed?"PASS":"FAIL",buried.passed?"FAIL":"PASS"); buried.passed?"PASS":"FAIL",buried.passed?"FAIL":"PASS");
const bool passed=ripples.passed&&candidate.passed&&!buried.passed; const bool passed=ripples.passed&&entranceFloorPassed
&&candidate.passed&&!buried.passed;
std::printf("[LandmarkReachabilityAudit] positive=PASS control=%s status=%s\n", std::printf("[LandmarkReachabilityAudit] positive=PASS control=%s status=%s\n",
buried.passed?"FAILED":"EXPECTED_FAIL",passed?"PASS":"FAIL"); buried.passed?"FAILED":"EXPECTED_FAIL",passed?"PASS":"FAIL");
return passed?0:13; return passed?0:13;
+27 -11
View File
@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import atexit
import hashlib import hashlib
import json import json
import math import math
@@ -22,6 +23,7 @@ import zlib
ROOT = Path(__file__).resolve().parents[3] ROOT = Path(__file__).resolve().parents[3]
VERIFY_DIR = ROOT / "Standalone/Tools/verify" VERIFY_DIR = ROOT / "Standalone/Tools/verify"
LINUX37 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37" LINUX37 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37"
LINUX47 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux47"
OWNER = ROOT / "Standalone/data/owner_world.json" OWNER = ROOT / "Standalone/data/owner_world.json"
OUT_DEFAULT = ROOT / "Saved/Linux/l42" OUT_DEFAULT = ROOT / "Saved/Linux/l42"
@@ -221,6 +223,16 @@ def run_landmark_reachability(runner: VerificationRunner,
runner.require(f"{name}:continuous-tunnel-ripple", runner.require(f"{name}:continuous-tunnel-ripple",
all_match(output, r"^\[CavernTunnelRipple\] layout=L46 .*floor_limit_m=0\.015 .*wall_limit_m=0\.035 status=PASS$"), all_match(output, r"^\[CavernTunnelRipple\] layout=L46 .*floor_limit_m=0\.015 .*wall_limit_m=0\.035 status=PASS$"),
"floor and wall ripple remain below L36/L35 limits") "floor and wall ripple remain below L36/L35 limits")
entrance_floor = re.search(
r"^\[CavernEntranceFloorAudit\] layout=L46 samples=(\d+) "
r"distance_m=0\.000\.\.10\.000 rails=3 max_step_m=([0-9.]+) "
r"step_limit_m=0\.050 ripple_peak_m=([0-9.]+) "
r"ripple_limit_m=0\.015 status=PASS$", output, re.M)
runner.require(f"{name}:entrance-floor-ripple-and-steps",
bool(entrance_floor and int(entrance_floor.group(1)) >= 150
and float(entrance_floor.group(2)) <= 0.05
and float(entrance_floor.group(3)) <= 0.015),
"three floor rails cover the first 10 m from the entrance; step<=0.05 m and ripple<=0.015 m")
runner.require(f"{name}:buried-l43-control", runner.require(f"{name}:buried-l43-control",
all_match(output, r"^\[LandmarkReachabilityControl\] layout=L43-buried .*" all_match(output, r"^\[LandmarkReachabilityControl\] layout=L43-buried .*"
r"result=FAIL expected=FAIL status=PASS$"), r"result=FAIL expected=FAIL status=PASS$"),
@@ -356,13 +368,13 @@ def run_hash_pairs(runner: VerificationRunner,
runner.require(f"{name}:hash-count", len(values) == 12, runner.require(f"{name}:hash-count", len(values) == 12,
f"hashes={len(values)}/12") f"hashes={len(values)}/12")
same = hashes[1] == hashes[12] same = hashes[1] == hashes[12]
baseline = ROOT / "Saved/Linux/l37" / f"determinism-l25-clang-{recipe}-w12.log" baseline = ROOT / "Saved/Linux/l46" / f"owner-hash-{recipe}-style{style}-w12.log"
old_hashes = tuple(HASH_RE.findall(baseline.read_text(encoding="utf-8", errors="replace"))) old_hashes = tuple(HASH_RE.findall(baseline.read_text(encoding="utf-8", errors="replace")))
same_l37 = hashes[12] == old_hashes and len(old_hashes) == 12 same_l46 = hashes[12] == old_hashes and len(old_hashes) == 12
runner.require(f"owner-hash-{recipe}-w1-w12", same, runner.require(f"owner-hash-{recipe}-w1-w12", same,
"W1 and W12 each produced identical 12-hash signatures") "W1 and W12 each produced identical 12-hash signatures")
runner.require(f"owner-hash-{recipe}-matches-l37", same_l37, runner.require(f"owner-hash-{recipe}-matches-l46", same_l46,
f"current owner hashes match {baseline.name}; hashes={len(old_hashes)}") f"demo-off owner hashes match L46 {baseline.name}; hashes={len(old_hashes)}")
class _SplitMix64: class _SplitMix64:
@@ -695,15 +707,16 @@ def run_captures_and_mesh_audits(runner: VerificationRunner) -> None:
script_inputs=[overview_script, VERIFY_DIR / "capture_quality.py", script_inputs=[overview_script, VERIFY_DIR / "capture_quality.py",
OUT / "owner-fi-ground-mouth.vfmesh"]) OUT / "owner-fi-ground-mouth.vfmesh"])
landmark_script = ROOT / "Standalone/Research/WorldAssemblerM3/Linux46/CaptureLandmarkL46.py" landmark_script = LINUX47 / "CaptureLandmarkL47.py"
landmark_layout = OUT / "layouts" / "landmark-reachability-clang.json" landmark_layout = OUT / "layouts" / "landmark-reachability-clang.json"
runner.run_stage("capture-landmark-l46", landmark_script, runner.run_stage("capture-landmark-l47", landmark_script,
["--layout", str(landmark_layout), "--artifact-dir", str(OUT), ["--layout", str(landmark_layout), "--artifact-dir", str(OUT),
"--capture-dir", str(ROOT / "Standalone/Research/WorldAssemblerM3/Linux46/captures"), "--capture-dir", str(LINUX47 / "captures"),
"--data", str(OWNER)], "--data", str(OWNER)],
binary=CLANG, data=OWNER, recipe="surface-world", style=1, binary=CLANG, data=OWNER, recipe="surface-world", style=1,
layout_path=landmark_layout, env=env_for(capture=True), layout_path=landmark_layout, env=env_for(capture=True),
script_inputs=[landmark_script, VERIFY_DIR / "capture_quality.py", script_inputs=[landmark_script, LINUX47 / "MeasureEntranceL47.py",
VERIFY_DIR / "capture_quality.py",
LINUX37 / "AuditMouthShapeL37.py", LINUX37 / "AuditMouthShapeL37.py",
ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"]) ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"])
@@ -777,16 +790,18 @@ def prepare_cost_baseline(runner: VerificationRunner) -> tuple[Path, str]:
baseline_commit = resolved.stdout.strip() baseline_commit = resolved.stdout.strip()
if not re.fullmatch(r"[0-9a-f]{40,64}", baseline_commit): if not re.fullmatch(r"[0-9a-f]{40,64}", baseline_commit):
raise VerificationFailure(f"git returned an invalid baseline commit: {baseline_commit!r}") raise VerificationFailure(f"git returned an invalid baseline commit: {baseline_commit!r}")
temp = Path(f"/tmp/voxelforge-cost-baseline-{baseline_commit[:12]}") temp = OUT / f"baseline-{baseline_commit}"
if temp.exists(): if temp.exists():
shutil.rmtree(temp) shutil.rmtree(temp)
temp.mkdir(parents=True) temp.mkdir(parents=True)
atexit.register(shutil.rmtree, temp, ignore_errors=True)
tarball = temp / "baseline.tar" tarball = temp / "baseline.tar"
archive = bounded_run(["git", "archive", "-o", str(tarball), archive = bounded_run(["git", "archive", "-o", str(tarball),
baseline_commit, "Standalone"], timeout=120) baseline_commit, "Standalone/Core", "Standalone/Source",
"Standalone/Compat"], timeout=120)
if archive is None or archive.returncode != 0 or not tarball.is_file(): if archive is None or archive.returncode != 0 or not tarball.is_file():
raise VerificationFailure( raise VerificationFailure(
f"could not export Standalone from cost baseline commit {baseline_commit}") f"could not export build inputs from cost baseline commit {baseline_commit}")
extract = bounded_run(["tar", "-xf", str(tarball), "-C", str(temp)], timeout=120) extract = bounded_run(["tar", "-xf", str(tarball), "-C", str(temp)], timeout=120)
if extract is None or extract.returncode != 0: if extract is None or extract.returncode != 0:
raise VerificationFailure("cost baseline source extraction failed") raise VerificationFailure("cost baseline source extraction failed")
@@ -794,6 +809,7 @@ def prepare_cost_baseline(runner: VerificationRunner) -> tuple[Path, str]:
base = temp / "Standalone" base = temp / "Standalone"
(base / "Tools/verify").mkdir(parents=True, exist_ok=True) (base / "Tools/verify").mkdir(parents=True, exist_ok=True)
shutil.copy2(ROOT / "Standalone/Build.ps1", base / "Build.ps1")
shutil.copy2(ROOT / "Standalone/BuildLinux.sh", base / "BuildLinux.sh") shutil.copy2(ROOT / "Standalone/BuildLinux.sh", base / "BuildLinux.sh")
shutil.copy2(VERIFY_DIR / "source_stamp.py", base / "Tools/verify/source_stamp.py") shutil.copy2(VERIFY_DIR / "source_stamp.py", base / "Tools/verify/source_stamp.py")
shutil.copy2(ROOT / "Standalone/Source/SourceStamp.h", base / "Source/SourceStamp.h") shutil.copy2(ROOT / "Standalone/Source/SourceStamp.h", base / "Source/SourceStamp.h")