feat(core): reachable SW landmark chamber demo with a reachability audit (L45); research R12

Stamp vocabulary v2; the flagged SW demo opens through a side-facing portal and a bent ramp (max 14 deg,
two landings) into a 17 x 15 m chamber with a rock dais. New reachability audit: a capsule surface search
from walkable ground near the arrival mouth to the chamber floor (66 samples, 0 blocked/unsupported); the
buried L43 layout fails it. Demo off: owner hashes identical to L44. fast/medium/full PASS (Clang+GCC).
Research R12 (read-only): composite cavern-graph generator design and slices.

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 07:42:30 +02:00
1 parent 036bd530fc
commit f13b145f23
14 files changed
+1404 -45

No files matched your search

@@ -0,0 +1,345 @@
#!/usr/bin/env python3
"""Capture the L45 landmark views and audit player-scale built-shape limits."""
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"[L45CaptureRun] 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)
layout = json.loads(LAYOUT_PATH.read_text(encoding="utf-8"))
if layout.get("stamp_vocabulary_version") not in (1, 2):
raise RuntimeError("L45 capture needs a prior resolved L45 stamp layout")
entrance_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-ramp-000")
next_ramp_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-ramp-001")
portal_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-mouth-approach-v1")
chamber_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-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 = metres(entrance_stamp)
next_ramp = metres(next_ramp_stamp)
portal = metres(portal_stamp)
chamber = metres(chamber_stamp)
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] - 1.6
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-l45.vfmesh"
export_camera = ((entrance[0] + chamber[0]) * 0.5,
(entrance[1] + chamber[1]) * 0.5,
(ground_z + floor_top) * 0.5)
export_target = (chamber[0], chamber[1], floor_top)
run_logged("landmark-export", [str(HEADLESS), str(OPTIONS.data), "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--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"))
if layout.get("stamp_vocabulary_version") != 2:
raise RuntimeError("L45 export did not resolve ellipsoidal-dais stamp vocabulary v2")
entrance_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-ramp-000")
next_ramp_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-ramp-001")
portal_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-mouth-approach-v1")
chamber_stamp = stamp_by_id(layout, "landmark/sw-arrival-chamber-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 = metres(entrance_stamp)
next_ramp = metres(next_ramp_stamp)
portal = metres(portal_stamp)
chamber = metres(chamber_stamp)
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] - 1.6
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}")
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_ramp_stamps = sorted((stamp for stamp in layout.get("stamps", [])
if stamp.get("stable_id", "").startswith("landmark/sw-arrival-chamber-ramp-")),
key=lambda stamp: stamp["stable_id"])
if not entry_ramp_stamps:
raise RuntimeError("resolved layout has no chamber ramp stamps")
entry_target_stamp = entry_ramp_stamps[min(8, len(entry_ramp_stamps) - 1)]
entry_target_center = metres(entry_target_stamp)
entry_target = (entry_target_center[0],entry_target_center[1],entry_target_center[2]-1.2)
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),
("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 65.0
if name == "ground-entrance" else 60.0)
if name == "ground-entrance":
fov = 55.0
print(f"[L45CaptureCamera] 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 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"[L45CaptureJPEG] 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 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))
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({
"vocabulary_version": layout["stamp_vocabulary_version"],
"entry_m": entrance, "portal_m": portal, "entry_ground_m": ground_z,
"chamber_center_m": chamber, "chamber_floor_m": floor_top,
"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, "dais_platform": dais_metrics,
"mesh_tiles": tile_count, "mesh_voxel_um": voxel_um,
}, indent=2) + "\n", encoding="utf-8")
print(f"[L45CaptureShapeMetrics] file={metrics_path} status=PASS", flush=True)
@@ -0,0 +1,48 @@
# L45 — reachable landmark chamber
## Design and stamps
The SW demo opens through a rounded, side-facing ellipsoid portal, then follows a bent rock ramp with two level landings into the 17 × 15 m chamber. The sloped section drops 5.85 m; the maximum measured grade is 14.039°. An ellipsoidal rock dais rises 1.138 m above the chamber floor.
The demo layout resolves 166 version-2 stamps: 57 ellipsoid carves, 107 support pillars, and two filled ellipsoids for the floor and dais. The side portal and supported arrival landing are runtime-only under the demo flag. `owner_world.json` is unchanged.
## Reachability and shape audits
The surface search joins walkable ground near the arrival mouth to the chamber floor. The capsule route passed 66 samples and 6,798 clearance checks: zero blocked samples, zero unsupported samples across 330 support checks, and zero grade failures. Minimum clearance is 0.085 m. The exact buried L43 layout fails the same reachability audit as required.
L37 thresholds (planar patch <0.50 m², straight run ≤0.75 m, no sharp corners): the portal measured 0.417 m² / 0.522 m / 0 corners; the dais measured 0.282 m² / 0.750 m / 0 corners.
## Hashes and cost
- Source stamp: `0ec9177748faff6ef4813af0c418057edfcacf95c7c39c1292288a29c7775015` (GCC and Clang).
- Owner JSON SHA-256: `7b095f9bcc6b90133d5a463f99f0b571133b2c549ce1ecf2d64fe9175e41e183`.
- Demo-off layout IDs: FI `b7a7d9ecc48d8f26`; SW `8a52e84c26ed92d2`. The 12-hash W1/W12 owner signatures match L44: FI `9820f4f38b189b3f7263810e7b3ff62aa502b21f476ffcfdbc7761a65cb07d58`; SW `a1e9268a4bbcc77d2a1ac54095df6851a70bab65c43ae50b332ed75132617834`.
- Five-pair demo-off cost vs L44: FI median 16.476 → 16.677 s (+1.22%); SW 1.733 → 1.731 s (−0.12%).
## Verification and captures
`verify.py fast`, `medium`, and `full` passed in 189.5 s, 561.8 s, and 1,609.1 s. Passage, interface/hole, seeded random-camera, W1/W8/W12 determinism, and 30-cell checks passed.
All four JPEGs pass `capture_quality.py` and are under 300 KB:
- [Ground entrance, eye height at 7.03 m](captures/ground-entrance.jpg) — 36,041 B
- [Entrance looking in](captures/entrance-looking-in.jpg) — 41,507 B
- [Inside toward the dais](captures/inside-to-dais.jpg) — 36,590 B
- [Overview of entrance and chamber](captures/overview-entrance-and-chamber.jpg) — 58,255 B
## Failed runs
Six geometry trial exports were used, within the 15-trial budget. The boxy arch exceeded the shape limits; early ramp/support variants blocked the path or exceeded the grade limit; a deeper mouth cut broke the surface connection and was reverted. Early eye-level camera angles were wall-blocked or poorly framed. The first final fast invocation rejected a stale GCC stamp; GCC was rebuilt to match and fast passed. The buried L43 and deliberate verifier negative controls fail as expected.
## Orchestrator review (Claude)
Accepted as the flagged demo. The stamp matches the final Clang and GCC builds (`0ec91777…`); demo-off owner
hashes are identical to L44; the buried L43 layout fails the new reachability audit. The overview reads as a real
place: a sinkhole and curved path cut into the ground, leading down into a chamber with a rock dais. Not yet natural:
- **The ramp is visibly segmented.** Evenly spaced ribs run along it in the overview and the entrance view, the same
per-segment seam family as the L35 ribs and L36 washboard. It is built from 57 carves plus 107 support pillars.
- **The ramp's raised edge has straight vertical walls,** so in `entrance-looking-in.jpg` it reads as a built walkway.
- **Headroom along the path is tight:** 0.085 m minimum clearance.
- The dais is a smooth dome, readable but plain.
Next (L46): replace the stamped ramp with one continuous floor-capped tunnel edge, following R12's first slice.
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

@@ -0,0 +1,66 @@
# Research R12 — Composite cavern graph
**Read-only review; no source changes, scripts, tests, or binaries.**
## Design
Build one immutable cavern field per selected strate. Its resolved graph has stable chamber nodes, tunnel edges, and a required arrival-to-departure backbone. Resolve it before tile jobs from `(world seed, absolute strate, recipe/version, stable node or edge ID)`. Store positions, radii, floors, and conservative bounds in integer micrometres; sort records by stable ID. The field must return the same value for a physical coordinate at every LOD.
Build the required spine first, connecting the actual resolved mouth anchors. Add optional branches and dead ends afterward; add loops only as edges between already connected nodes. Graph connectivity alone does not guarantee a walkable route: room and tunnel portals must overlap with clearance, their floors must meet, and the final field must pass the arrival-to-departure player-fit audit.
Use smooth ellipsoid or blob SDFs for chamber envelopes and swept capsule SDFs for tunnel edges. Keep an explicit floor profile on each room and edge; cap the void at that floor and keep the floor cap separate from smooth unions and wall relief. Start from the native substrate, carve the graph into it, then let the existing shared boundary and inter-strate passage composition run. With Core’s signed-field convention, carve using smooth maximum with the negated void SDF. Keep seeded, bounded relief on walls and ceilings, away from the walk plane.
For floors, apply R1’s floor-first carve and narrow native-solid support fallback. R3 warns that overlapping coils can select the wrong floor owner: for floor samples, consider only candidates within half a voxel (0.125 m) and within the bore radius; choose by floor-point distance, then stable feature ID. Keep tube ownership separate from floor ownership. R3’s synthetic result does not prove that rule fixes the real L38 floor behavior, so verify it on the actual field before adoption. [R1](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research1/REPORT.md:10>) · [R3](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research3/REPORT.md:1>)
The design follows useful patterns from prior work: Minecraft names distinct large “cheese” caverns, winding “spaghetti” tunnels, and narrow “noodle” passages; Deep Rock Galactic connects authored room templates with narrow tunnels and varies room interiors; Spelunky 2 constructs a safe route from room templates before adding optional exploration. Cave-generation papers likewise separate network topology from its SDF geometry: one uses Voronoi/Delaunay structure for playable cave connections, and another builds a cave graph and derives conduit geometry from an SDF construction tree. [Minecraft](https://www.minecraft.net/de-de/article/caves---cliffs--part-ii-out-today-java) · [Deep Rock Galactic](https://store.steampowered.com/news/posts/?appgroupname=Deep+Rock+Galactic&appids=548430&enddate=1730296516&feed=steam_community_announcements) · [Derek Yu interview](https://blog.playstation.com/archive/2018/08/29/first-look-at-spelunky-2-gameplay-plus-new-derek-yu-interview/) · [Santamaría-Ibirika et al., 2014](https://santosgrueiro.com/papers/2014/2014-Santam-Cave.pdf) · [Paris et al., 2021](https://onlinelibrary.wiley.com/doi/full/10.1111/cgf.14420)
## Fit with R6 and current code
- **Resolved layout:** Add optional, versioned graph records to the resolved snapshot, following L43’s optional stamp vocabulary pattern. Include active graph records in canonical identity; bump the selected recipe/evaluator version when its geometry changes. Existing graph-free layouts should retain their identity. The snapshot currently holds strates, passages, and stamps in [VoxelForgeCore.h:907](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h:907>).
- **Bound field table:** L40 binds one concrete evaluator per strate in `BindResolvedStratumFields`; its variant currently covers SurfaceWorld, FloatingIslands, and legacy fields. Add a native cavern-network adapter that binds the graph and substrate once. Keep evaluation free of manager lookups. R6’s target uses physical metre queries; today’s adapter evaluates in voxel coordinates, so keep graph data in micrometres and convert at the binding boundary, or add the physical-coordinate API as a separate migration step. [R6 §2](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research6/REPORT.md:22>) · [L40 adapter and binding](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:136>) · [Binding function](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:286>)
- **Capabilities and shared composition:** L42 resolves interface and passage policy before sampling. Resolve graph-floor policy at the same stage; avoid per-sample generator-name branches. Preserve the current ceiling/content/bedrock order and let shared passages retain ownership of inter-strate openings. `ApplyResolvedStrateComposition` currently applies boundary bands, passage carving, then stamps. Put cavern carving inside the bound field evaluator, before shared boundary composition, so a chamber cannot cut through protected bands. [L42 architecture](</home/jahni/VoxelM/Plugins/VoxelForge/ARCHITECTURE.md:2218>) · [Composition](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1781>)
- **Stamps and BVH culling:** Reuse L43’s ellipsoid SDF, bounded relief, stable IDs, and BVH implementation patterns. Keep graph rooms as graph records rather than generic landmark stamps: stamp placement rejects route/walk-corridor intersections, while network features intentionally connect to the route. Add room and edge bounds to the tile feature query so tiles evaluate only relevant graph features. [Stamp records](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h:736>) · [Stamp BVH](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:3327>) · [Passage feature BVH](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:3497>)
- **Shape-free proofs:** Extend `World::isTileShapeFree` with conservative bounds for every graph room, edge, blend radius, relief reach, and support feature. Any overlap must make native-only eligibility fail; box classification must remain mixed/unknown on graph overlap. L44 traced a worker-count mismatch to omitted support-column bounds in this proof. [Proof function](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:1359>) · [L44 finding](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux44/REPORT.md:5>)
The legacy `TunnelNetwork` already has room/corridor SDFs and worm tunnels, but R6 describes it as a legacy evaluation route. A resolved Core graph adds the missing precomputed connectivity, floor ownership, bounded feature records, and field contract.
## Risks
| Risk | Control and evidence |
|---|---|
| Connected graph, unwalkable field | Keep explicit floor profiles and portal overlap; audit final-density capsule clearance, support, grade, and mouth-to-mouth connectivity. |
| Floor errors at loops or crossings | Use floor-near ownership with stable tie-breaking; measure the real owner field because R3’s synthetic case did not reproduce L38. |
| Per-voxel cost grows with graph size | Use conservative feature AABBs and tile BVH candidates; record candidates and SDF evaluations per tile and benchmark paired graph-off/on views. |
| Chamber or support crosses a seal | Include smooth blend, relief, and support extents in graph bounds; reject placements touching ceiling, bedrock, or protected bands. |
| Nondeterminism or stale identity | Hash sorted graph records and versions; use fixed-point placement and keyed per-feature seeds; compare worker counts and compilers. |
## Smallest slices
1. **Owner demo:** Add one large chamber and one floor-capped tunnel edge connected to the existing tested arrival-to-departure route. Keep that route as the guaranteed spine. Use the accepted L45 chamber record as the first room node if its bounds and connection fit; otherwise use one fixed, opt-in demo record. No random branches or loops. Verify a deterministic layout round trip, readable approach/entrance/interior captures, and the existing walkability and boundary checks.
2. **Resolved graph generator:** Support multiple room motifs and a mandatory connected backbone, with per-node/edge bounds and graph-aware culling. Verify seed stability, graph connectivity, and arrival-to-departure walkability across the owner seed matrix.
3. **Optional topology and detail:** Add bounded branches, dead ends, then loops; add seeded wall relief after floor and clearance gates pass. Verify loop crossings, floor ownership, cost scaling, worker/compiler parity, and LOD ring behavior.
4. **Authoring and runtime integration:** Expose motif and rule data to the owner, then connect the opt-in recipe to Unreal preview, collision, and cave presentation.
## Measurements to run
- Snapshot round-trip and canonical identity across repeat resolves, worker counts, and reordered inputs.
- Graph connectivity plus final-density six-neighbour player-fit flood fill from resolved arrival to departure; require the project’s full seed law gate, not just graph reachability.
- Passage audits for capsule fit, floor support, ≤15° grade, open air, floor ripple, and mouth transitions.
- Strict bounds and hole/bedrock-band audits, including negative controls that force a graph feature into a protected band.
- W1/W8/W12 determinism at the L44 failure camera plus random cameras; compare density, mesh, payload, and topology hashes.
- Five paired graph-off/on cost runs at matching tile sets; report whole-view time, density time, BVH candidates, and per-feature evaluations. Repeat at increasing graph sizes after the first slice.
## Owner authoring
Start from the project’s existing scale families: chamber radii of 4–10 m, tunnel radii of 1.5–2 m, and metric bounds relative to strate interfaces. [ARCHITECTURE §8.0](</home/jahni/VoxelM/Plugins/VoxelForge/ARCHITECTURE.md:17>)
The owner should author chamber motifs and floor styles; connector radius, bend, and widening rules; required route and allowed degree; branch, loop, and dead-end limits; and bounded wall-relief palettes. Keep the required spine’s clearances and floor-grade limit fixed across motifs.
**Owner question:** May the accepted L45 chamber become the first graph node, with the existing arrival-to-departure route retained as the guaranteed spine?