From f13b145f23349e82fbf3c520d3228f1ff3e678c8 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Sat, 24 Oct 2026 07:42:30 +0200 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1 --- .../include/VoxelForgeCore/VoxelForgeCore.h | 6 + Standalone/Core/src/ResolvedLayoutInternal.h | 19 +- Standalone/Core/src/ShapeWorldInternal.h | 15 +- Standalone/Core/src/VoxelForgeCore.cpp | 43 ++ .../Linux45/CaptureLandmarkL45.py | 345 ++++++++++++++ .../WorldAssemblerM3/Linux45/REPORT.md | 48 ++ .../Linux45/captures/entrance-looking-in.jpg | Bin 0 -> 41507 bytes .../Linux45/captures/ground-entrance.jpg | Bin 0 -> 36041 bytes .../Linux45/captures/inside-to-dais.jpg | Bin 0 -> 36590 bytes .../overview-entrance-and-chamber.jpg | Bin 0 -> 58255 bytes .../WorldAssemblerM3/Research12/REPORT.md | 66 +++ Standalone/Source/StreamBenchmarkHeadless.cpp | 400 +++++++++++++++-- Standalone/Source/StreamBenchmarkPipeline.h | 422 +++++++++++++++++- Standalone/Tools/verify/verify.py | 85 +++- 14 files changed, 1404 insertions(+), 45 deletions(-) create mode 100644 Standalone/Research/WorldAssemblerM3/Linux45/CaptureLandmarkL45.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux45/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Linux45/captures/entrance-looking-in.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux45/captures/ground-entrance.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux45/captures/inside-to-dais.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux45/captures/overview-entrance-and-chamber.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Research12/REPORT.md diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index cc4d176..82e5af7 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -706,6 +706,7 @@ namespace VoxelForgeCore // source fixed-point units so this record never rounds the recipe itself. inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV3 = 3; inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV1 = 1; + inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV2 = 2; enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict }; // Stable, bounded geometry edits composed into every Core field after the shared @@ -962,6 +963,10 @@ namespace VoxelForgeCore ResolvedLayoutSnapshotV3& layout, std::int32_t seed, std::span requests, std::string& error); + VFCORE_API bool ResolveResolvedLayoutStampsV2( + ResolvedLayoutSnapshotV3& layout, std::int32_t seed, + std::span requests, + std::string& error); VFCORE_API ResolvedLayoutStampPlacementAuditV1 AuditResolvedLayoutStampPlacementV1( const ResolvedLayoutSnapshotV3& layout); @@ -1243,6 +1248,7 @@ namespace VoxelForgeCore const WorldConfiguration& configuration() const; const WorldAssemblyManifest* worldAssembly() const noexcept; const ResolvedLayoutSnapshotV3* resolvedLayout() const noexcept; + ResolvedWorldSnapshot withResolvedLayout(const ResolvedLayoutSnapshotV3& layout) const; ResolvedWorldSnapshot withoutResolvedLayoutPassages() const; private: diff --git a/Standalone/Core/src/ResolvedLayoutInternal.h b/Standalone/Core/src/ResolvedLayoutInternal.h index 024730c..5b4feb2 100644 --- a/Standalone/Core/src/ResolvedLayoutInternal.h +++ b/Standalone/Core/src/ResolvedLayoutInternal.h @@ -830,8 +830,9 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode, if(stampVocabularyVersion!=0) add("stamp_vocabulary_version","stamp vocabulary version is set without any stamp records"); } - else if(stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV1) - add("stamp_vocabulary_version","resolved stamps require vocabulary version 1"); + else if(stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV1 + &&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV2) + add("stamp_vocabulary_version","resolved stamps require supported vocabulary version 1 or 2"); std::vector stampIds; for(const auto& stamp:stamps) { @@ -1065,6 +1066,20 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t layout=std::move(candidate);return true; } +bool ResolveResolvedLayoutStampsV2(ResolvedLayoutSnapshotV3& layout,std::int32_t seed, + std::span requests,std::string& error) +{ + if(!ResolveResolvedLayoutStampsV1(layout,seed,requests,error))return false; + if(requests.empty())return true; + ResolvedLayoutSnapshotV3 candidate=layout; + candidate.stampVocabularyVersion=ResolvedLayoutStampVocabularyVersionV2; + if(!candidate.finalize(error))return false; + std::vector issues;std::string strictError; + if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,strictError)) + {error="strict stamp vocabulary-2 validation failed: "+strictError;return false;} + layout=std::move(candidate);return true; +} + ResolvedLayoutStampPlacementAuditV1 AuditResolvedLayoutStampPlacementV1( const ResolvedLayoutSnapshotV3& layout) { diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 643b039..462c990 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -3339,6 +3339,10 @@ private: return result; } QueryStampSpatialNode(0,minimum,maximum,result); + // Stamp union/carve composition follows canonical stable-ID order. The BVH query + // order is spatial and can differ per tile, which would let a later carve erase a + // fill on meshed tiles even though unscoped density queries use the canonical order. + std::sort(result.begin(),result.end()); return result; } @@ -5673,9 +5677,14 @@ private: case VoxelForgeCore::ResolvedSdfStampKindV1::fillPillar: distance=StampPillarSdf(localX,localY,localZ,stamp.halfExtentsVoxels);break; case VoxelForgeCore::ResolvedSdfStampKindV1::fillDais: - distance=StampRoundedBoxSdf(localX,localY,localZ,stamp.halfExtentsVoxels, - std::min(0.35f,std::min({stamp.halfExtentsVoxels[0], - stamp.halfExtentsVoxels[1],stamp.halfExtentsVoxels[2]})*0.15f));break; + if(ResolvedLayout&&ResolvedLayout->stampVocabularyVersion + >=VoxelForgeCore::ResolvedLayoutStampVocabularyVersionV2) + distance=StampEllipsoidSdf(localX,localY,localZ,stamp.halfExtentsVoxels); + else + distance=StampRoundedBoxSdf(localX,localY,localZ,stamp.halfExtentsVoxels, + std::min(0.35f,std::min({stamp.halfExtentsVoxels[0], + stamp.halfExtentsVoxels[1],stamp.halfExtentsVoxels[2]})*0.15f)); + break; default:return std::numeric_limits::infinity(); } if(stamp.irregularityAmplitudeVoxels>0.0f) diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp index acc15c9..4f2fa9b 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -989,6 +989,15 @@ namespace VoxelForgeCore const ResolvedLayoutSnapshotV3* ResolvedWorldSnapshot::resolvedLayout() const noexcept { return data_?data_->resolvedLayout.get():nullptr; } + ResolvedWorldSnapshot ResolvedWorldSnapshot::withResolvedLayout( + const ResolvedLayoutSnapshotV3& layout) const + { + if(!data_)return {}; + auto copy=std::make_shared(*data_); + copy->resolvedLayout=std::make_shared(layout); + return ResolvedWorldSnapshot(std::move(copy)); + } + ResolvedWorldSnapshot ResolvedWorldSnapshot::withoutResolvedLayoutPassages() const { if(!data_||!data_->resolvedLayout)return {}; @@ -2222,6 +2231,40 @@ namespace VoxelForgeCore RelevantMinimum=std::min(RelevantMinimum,FeatureMinimum); RelevantMaximum=std::max(RelevantMaximum,FeatureMaximum); } + // The native heightfield band only covers the owner terrain and its + // manifest features. Resolved SDF stamps can carve below that band or + // add a floor there, so include every overlapping stamp's bounded Z + // support before clipping marching-cubes samples. + if(impl_->resolvedLayout&&impl_->owner->CoreVoxelSizeMicrons>0) + { + const double MicronsToVoxels=1.0 + /static_cast(impl_->owner->CoreVoxelSizeMicrons); + const double TileMinimumX=static_cast(origin.X-step); + const double TileMinimumY=static_cast(origin.Y-step); + const double TileMaximumX=static_cast(origin.X+extent+step); + const double TileMaximumY=static_cast(origin.Y+extent+step); + for(const auto& Stamp:impl_->resolvedLayout->stamps) + { + if(Stamp.strateIndex!=PlanIt->strateIndex)continue; + const double MinimumX=Stamp.boundsMinimumMicrometres[0] + *MicronsToVoxels; + const double MaximumX=Stamp.boundsMaximumMicrometres[0] + *MicronsToVoxels; + const double MinimumY=Stamp.boundsMinimumMicrometres[1] + *MicronsToVoxels; + const double MaximumY=Stamp.boundsMaximumMicrometres[1] + *MicronsToVoxels; + if(MaximumXTileMaximumX + ||MaximumYTileMaximumY)continue; + const double MinimumZ=Stamp.boundsMinimumMicrometres[2] + *MicronsToVoxels; + const double MaximumZ=Stamp.boundsMaximumMicrometres[2] + *MicronsToVoxels; + if(MaximumZTileMaximumZ)continue; + RelevantMinimum=std::min(RelevantMinimum,MinimumZ); + RelevantMaximum=std::max(RelevantMaximum,MaximumZ); + } + } if(std::isfinite(RelevantMinimum)&&std::isfinite(RelevantMaximum)) { const int32 NativeBandMinimum=FMath::FloorToInt(RelevantMinimum)-step; diff --git a/Standalone/Research/WorldAssemblerM3/Linux45/CaptureLandmarkL45.py b/Standalone/Research/WorldAssemblerM3/Linux45/CaptureLandmarkL45.py new file mode 100644 index 0000000..ea4b954 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux45/CaptureLandmarkL45.py @@ -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) diff --git a/Standalone/Research/WorldAssemblerM3/Linux45/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux45/REPORT.md new file mode 100644 index 0000000..5597399 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux45/REPORT.md @@ -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. diff --git a/Standalone/Research/WorldAssemblerM3/Linux45/captures/entrance-looking-in.jpg b/Standalone/Research/WorldAssemblerM3/Linux45/captures/entrance-looking-in.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2551e2a6a564af01b5b1ab6c03ce6128267879d3 GIT binary patch literal 41507 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 #wewhmt*Wc0DdDCObvH^U4+< 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

pJ5U+yXTu>`@BBz}WWZG-R)+@SP^ z!o48f8q5T+0d@e`$5^~16k~@&P(;rWlOXsGc8;5{bhEREB3_${*Afkh2LEXd;>Le! z^4}eY?pkrX1HKB2C~-cp)kjg#zp&3m7(#I7_BRnraffFC(E^d8-cOYh&E!58jw(Y% zxu{^X$B7qIOq5!}nI(~6>@QG4eYyT0xH};WVI<1-Sn3YH8}Sy4DijrWlVauQfY%*J z6ErK}f(Edo31s`3rp0>wNB~AKwTSMR4bU192VT@U`M>~;7X%&v<^=I0y87?R3fBI2 zXZ_z>1L`6cir*4q62M2-vim{8fSre(e4~Pe$0X literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux45/captures/ground-entrance.jpg b/Standalone/Research/WorldAssemblerM3/Linux45/captures/ground-entrance.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a224bdb2253854e45beb8387fe0ec115dbbf69ef GIT binary patch literal 36041 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

fBcScE)+irnK0tv@XF0#afgoU;w7 z%1?VdT$v(+mJEF4m9?(~-3f!jprEs@P_jI^BLq}UWeSKh57PN|zkTbzGhmOgNOj+Z z2^t+cNZ$_JC!|51oGx9Om zvCr#S@JGz>a*?7^lW(LMzFT2Nh4V{H_A%J5$)0@kj-V;gfF44GWZ)QW@r2iXMwJd{ zMBk=Z%V=FLb_{0reyC!7Vk-EJWhHh&@}|$}e#dajeD6<};-fQ}EirlHwMQZ!5u9al z{<;fdV)aKP#71TFyc`@cCN8e?3w}qNi%hvy-$w$5ezXXVj*X%xqvvO(#ELR?t5%JD zP#YVXkp(s8@$OG@NUCQ3$hTG62 zc;%CA@lt#)K6m~>rL>jRX2%5Z$a zC#+V9oyNFa*LZyU@KZy2!}yT~*Og>~t96s}H4ByW>k?N59$l)S`c9{i=2oO7O<30T zILo;Gn0zh)KrcLiWT@}T^PJH}YUD%7NHG22)yMY=U_C;%6`rnKb{CkD*R zcjVmgWSxb0IO)?V^ni;g;s_@N9fFD=lleVHCp=nD)G1g)L*=YD;s{m5yk%_IttvIX z`$#&%S!2~(ys-s59QS`p=l3$M_Q5?p^vb;X3t4eB0*-6+yTre|G8**R_dp(b20bN&Fd?1Po+lmzz2rsTo~eW|!-%3$AN7 zxVtP6^vk#`Uu{EugX@k-pnI%o+mN&GbOq=Rs%K=S_ESZt=RIyC&5$gvwEFsS$Cr-B zar0A#fV(&Ujnnm$3vS995rT9EYB}`z9U2xNVM}))PN0|9q>d-aoPWDlUKJbaZ{Ki1Xh-4T1Bjbf!$htjUUdQJL zd3`5p@{@0&a4j;Z>@$*_^b9HuqBgln4t5sEY||-Bu9F>1r!+`c))%G9<)Wd6lw%=d z#aoWFVF$}~6zmP|>7RUTa8JSa2X5MvkM(I!mh0%BROC8CMXn9SQXkxjIYI}^sNvEp z&SgxD?pw~?ma#9IK8)u*pDKSE+aU46CBii{jXrzYI_jqPScS3j}h@AUdAXYgzv0<>P<|6fqkEN>?k9QLA8GW#h7at%Vh6jh0KYjnp3OrZ zYp2<-d_Y#uHUH200?1|LUCfvZX{8pIC%0vOOJ-;22l1=UbC99Ee}qaTe}rP4gDA56 zpfk*GA#rH`1f)<6?YlvK;xXC5$B@1~{8jjt+zUE&F^IwmQspD#dPLFlEhPpr{`O!M zbSSnpDEzfuoPz$ET1P%dPCH=Og%~8Ioj)%Rxfn5DUT%MX!XeFe> z58u<7{rHG8%Ylz7f7%FaH34U5BLs3MpwU6g>vP`91J4OqcF3+nd=Nh%E9h(|AUSQy zBJfpEvUO-f+EyiTMS2M9dL-b$pc{g+1!ObNw|Q79zW#ucCY@&4(u|%O1Y^7kXvp zi}m9DGH*K;RyOivnH4TKa*5)6M``JCA_eEe<$F^|56?$xV`fKVOE?32T%z3>U zVulftyUJSp@>M$cvoMvpnn(!H z6BidTtbZC!HH?@F$x`Ii!deH$&pEs6MHLSEwW)uKb7xKR`KDA@kyNVeFC!WJ9VXpF z@zlNhfc9zaDL8Z6spRPq$m1M9n#eDKhOnVf7r$fggfs9+3mXx9&44BcSF5 zg_#E-fm#{*!-TxDAPsO&x2568e<3=9r0^0Zo>CHNZpK_vJT09t${G4Vm%vS{?22*Y zyj7)I!b7e44OvPiQo)#(@*4_TV{OL5^j)uJ;7+3r{7zeeXAKo_@5`lbcO9k8a*;OD*c380rBsf4L=gn-tY6Uby77PoWTqER0@vOW;9ia} znsx?`aAFm}XZNBhIQn1-AG9!F-i=mtDls@ZmTSWoc}tBvu-^>aH$`E;Ot1^BYy;an z!-4x$ce;Wye7*(#g6)kH@>CiSC3I37N`m;($waAd+0x0&BZR->ig1l{)xK=}9q|kW zIxp=YDsO=I5V_julWqf>J3cEHK8T*R7EVY?$vi#0p}C@n5-?V6k}i^=FUYyyPdLG+ z=U#v0oS#+2b3IM}sMIg+)P+{e;CzNIlOuHMdJr8i2X!HIrxg$tr2!yH{}Qmb@@&6` zCnV%ptjz$2&m&hnH}Jcv^ko6zzGX*76Z@=>;SkQ z*&YklqU5}1f!A|4@OW+ZK`JDHyzXqWqy-3+wyuS^cD^FE@&Kx&e==L%00SfgV7&%# z0O|YeX!*OU86f%$vO~J*|H|lTIC(fvCZ+>=C!A6eeoc%G;}m|4(d9bLjTYt#8|48S z8P`39t4cfCn67T_;Za+i{kD(}8g3HsKj6fu!3De_Y zrk-o%hYoV{QGx~DpZhzo0Ii}1VSYgV?07hI_1lA)&vYZ7abEA|ynEk?_Qd7)WTKJJ z4&HZyG)~_D(jn;hg9*q?}Jq>OY&O0I^M>tJmb6AC zhX^d001WlCDiyy zct@0L2+s^1J&NMjQ?EVpd44rQTtj$4y#97Vy`zZpB|iztUjgoYVhiGzKn{{5w%`RK z1UMqVB9zAmuzY&#;&vu@^1heEYQzQAD7>H$CQqkhY)p+`H!GXijLt{8pC8Lmjm(P{ z<<=<@8KdM8EKfVHue_933z^#0+$X!JQ>_p6ngCH)OvbEd7xbVbpS;EZN$FXbOFaz) zW^YOQX*)-4M7db{X@_s1K8RfG8I*JfI-8+QfmR(o3Drv%FDPJA*O=54Q>vzh*?yR7 zt;j9oehGFK*@6?@E2r$T`IAj9UEd zqlW_$eJz>wA|2vd%;{ZK_eh8}@&@1VnCnHy!8m%Y+pdXz92Lccz00xT z#yQfx)U0Z^O-ARiN_lIXDIsA}S;{Z#6E!MSGkK{UOY{*&dca5dF!zH_dCT&O9^`&n z;CjkSZ4VZu$nNWaq11c_{Bg{F0`8^BuAOk{FyLegj*KztIp*Ig!Sw+QJB-(}B3&7! z;dzUf$FW$4F5D~~oU2J!Hi*w4GXZX>MWX3+SAu~{aOSD|is`&Gi9b@PllboIATa7B z_Y@exDouy35qVF^0BuZ58+(PL5D4U0)mz-(To2w*U#3WW(H$GiK)jSBC$@m@| z&PZ1Vr+C0|4ZsESG`Mx>RKdxeqmH(HM+35C7k{`D8-G;KwE)HO=Xe7swQsp-MgTP? zuH`1s3?fHJ>V1{I;b49UPY=Eud?`i}7?|Pzs0SxU1_>I%A3;#Bak|ns+E=tas}uzM zJQpRSWImSVejJm4CwOVX2$+W;fTv?tWy0`avsht`WEB5L5~w~VQCX@XN&tlhn?*?i zbtr(RQ`o!y)+ z$Hi+di;bDs#%qr61P03jaLNDx_3wK)yP5E0JrHqwQL4csH6kYE<2|L-79HKu%=g)r z6_I>R>PmEbV&dmv_KRoRi@0msSY=au?v$CJn%o;OLQ@8gOqPtM1rC?gGarQ}x`Sgp zTrVF55$%k zMLOQivtE=G^3%*)u;k_44P!~SfBKZ`^u{{rhO61)k-o!vT5>-v@Gw^};;?~dCXnI| zA{x?LYOSCMIw$qD53^;BWs>c(p&gK4PEHAP1wU|# zJTo;n3>Xv+t8(UpYetRL8^`yq=9N6G{4H;AlOuC%PtX1m#h+>KpAK`v3ZD7sx(j?a zXO2JB)j!>moDHgw)j(Y_^s3kfn<2^aR-_k!>eX}(4 z7qIx54@AvLC(oDSTFkMFIf`bWVLL29p&4>JyBP3S_Si+6OFsk5!ISS@vG*smGTPr% zFy+KF`nYAh9Vm7v*Ak3f$hqu)q)!YmTuJ9kF>gCd9WH3eVHPBX2a2zk&96F5f(1gj za!#7WMVrf}Fc0>7JDqwmvBJcA{&uxHfEe!Yb_Sddlj3Vuj3bGKjr0htkN~;3Tu!b* z84yHC(Wr+Q5??_ysuZQ3`M5%ANwkqS994+{W#7G-eglrKY5X+RM+3r}KjU=UIrDU= zn{ufVZNTpJD35yBtx}#VRQDT8dB|h9?kVZ&a8Ng%Q3A_S#zwFxXF}2f6 zY{|=k9%-FYl8$Oelh4nJ*M;26S}&4Y8kv`R+`m>;jr5WA^_|SjT-1UL72$c2p@y#x_+;NBHpjWkvP-oejVZ)>b=SN@f=BYy8MG z^fIJIPIu9<_VEWdl(nh1*=^I6N6W5j@dZxFH^v&8D&}hTSI%0_j4lcq42TUkMvF_kz|$`Pl5<`&T*Z0p(@qs$_r57N z8=In>`)d8`!l%AdswqsjP8xQ;a-P$WPenIkd7wZ=!UVBJ>)9!8w$J#m^ zBfv2p;0$nVy0uj-ttG&%q^&-kOLJQ!D(-bXh4nU^Zdu>!`3=uwjsC>$i_?1u>fZ!% zFUwt<0re9#u;nf)%Jg{2gwJ8iGXV!2%gZ~SI~0T$2k6ZNRP{Mddz?>}v3vK?B6dCP zfoGqunfh6x$>VkUQQ7eishGiTy@MqWWM8goRjrMq%gY*df63}10mipi?E=VnvO2LS zV~qN=K~$*Cg?W~zjD;rK$4#|1XFCSKxoV>gN$~SsNbU_b#$laNoeRS=#E#mtOBu~c zwm*l&cmM)7_S2(^h0Z2!O63c8$pKHt^-saHtVp(0{F9T@`goMUHU3v6*#jq!@$*uU4P{COQSWgA_aFb-;1bslWv^3M@(Ro(>PV z#*$~$w~}WVtw9tHFFB~^W{n-m(s)mXUq5W4$P6sQSa-R78L&W%AyxMR;a4vsce}g> z&21id`saxYuoUZQ{p`cv$4AlXon)PSJGSbe^j^{hgJaEV$l7cpcXx#48jI}TO|YgR*q&nu%6}F zcOeh>k>`PfOg*019TyHa zs>S}nD{=mjhX*P+ySTCQ=lkzBtLc|+Fu^5yIp3H!#^vG6`ue5cp!03xJ(rJNz~tdr zL4KF*Zdx+QE~x?(U>UQ`cMSLX;4ZwhcQt*$l(Rl#dDP{o| zt8kohV{@<<(jXZ$*UF-Zkp@iQE=-4iW-1idC|MAf$kGo6BU&*Q(PMkx!<6cE9Maig znv%&R=umGu{5642T!-h|uMg)8ReCIJ1c&Du^vjI-5($IN_B-=%cbD_a_POwDCC2wezBna4ZWOSlRDDMT zc0=K~Z?tWMuW&T#olh5v8>3aWJpbf*=3(oT2|D{l4ykh;K1-i~REQ`#pDrAixAT?` z_~jhnr5^C%cG31EB?c1wA_uYEH$T8pZeT1k*jWZLeOU&A(}q;wz&e;MwuM&K{_XFV!qa^M^LP2bPK-TS5Q z?Z55&?p^O?nz~i~Qzl%qd$%SSuXcd8d3Q+Q@%q=yCNP++@jGMN4sHWZ?v1UW-K-zc z0StmaJn$qiI!yr4k@$Z}f>9vBRXvG2nBe42;k_iN3vvld#4m{D0OCr3xW{Yf8N$gY z9$p`A?4=E4G}Z>$Ff~6&HA(d$1hXQ&qN8derS!6koqaA08ZcoKU2&7`Z+wlW`daY0 zp)+o{$r|gyF3BhT7p9x@-K_>+SP$d%o2wm@eO!d!;{kuDFu!BU&)>7`TZcPoa;g)- zFNC+}%2(opbmb9hU{jd}aa9|st3Z1{^cFV<)qU7!2lkEu(R^}lo*)nh+g$nN$8)Jv z)4{a-Yof4g?r(O}{-F_`Q#}9CtWQo!$E7S!fdv0fiGmsmBJYGuI>7f4IokN1l2T&{ zRM>b%9v$0gO}3wr|9mFzL30`7Re*MngDpUx9eO-9?*a{)i2wlA&}x zuBE&Y?%XBk`5JOJUUuztZ?P=YaY{a} zPKVzf6a~N62pEJwG`I`chIU?Y1SB{B9Dv_>1dcNI!IzHo^Wb$Hhq=@A3z&?M0T}ZI zp8zx`-_>|_y#lw2i&;-UKNfzS8{BTRBaMOK7|7(@h&i+fN|ECqxaMyX%D|c6%SZk< zaELA1Vb;6wV?_m_Z8|_uG7{5UVIFS}Kn@k1B?~nc;ms&?MNxcC?}j;wnZMkjmaY5U zd_s?70eS+EGq!=?Xey{ue)GwgEv_g&tM`ey8*QHfz-7LNE81?>yGif2g)%@P-;4Yg z9BPktzMJpCB~_S*fI`6RV=8zFU|NDP1=`Kt5Yf2cP_u3r{(UM2NdJL?m(QC;J4D84xPVB@zJ+v()@+4giHf3rRrCHoMGYDbZC#gFmaFj1b3d&_?P- zH^mlQpPXit{3*_nl{M>}lx|U0K!E!3I7jCBxo<&UUs?=pw&~^mj|tg4y%)ybzlN)2 z7l^-XilHv%T0TF8V)%43Uch}~(#bh}y6xBrl}o0%1g9wAW359kF*$;9Gf!$bYaij_ z+T)%WIwW*7SzSqe=G}7fhf1G5cWmP-?tw+4>zbdHOBGR~#rj;`4ZNo7-Gl|S#85T? zE!_QF+-0kJKrFPKkceZo2%nOYL;IK=Nwk}q`Y;;Ve}hCK)YV0arx+dsUM&r%wr4#z z2o~G}2v*^W4@^3&&KK28ibMLYyC*Tx0hg$w%uYg)og?vn8?OEJ&64V$Fph(qr`}$7 zA27K8nM7dwkTT-nvbf2UI9uSaR@fpm_I4a0JT>nwbr2u*Ku2zE#AB9taHU|x$Fapc zLWrS42^>AAx&r-3#zC$G(Si>}D93Nf=x>Pc&iyOoz;SKYOCRiVfusCfVCx1x8A-M) zwBdJk9SrU>LDLZ=s?VLghe?SE&vXH%<(S)n&)6Vca9S@mu^WIi50p#FAeTAR7y+D&;i55DY?aum5<_BO>V1_#VBTCz7h zTawlLc;?3sIK1uSE1S;&Auyv5Q&?keJM;m^i)x$wpl$diW@w{n@Y;&{y*CuV@7nq0 zg#{XCnShF+iaa2DeqQ2|ze z+s+o(v@jXOgey8mhn6HF7Hljz_X>Hq1O;aQpMK}f?$e^shCjRewP4@pi+j=kB67sN zto*`!qzs=(M~hQ&JtKiTw<@>ctaVmsj1cn*g>s2MKHit{VS({t)tt*_SIP(`=Yhod zLfP!1y21M02ge_Z1#+J5!$0skx-!wU{#cfEJ@D$NKm8@dt5y;zDPaKN-K5_CVhZiu zWRY*}o7K8d=VUyJ`TuIW7I>((?|;lSH>8X_Lf%CZ3Dpk`MwxV7VLS?jGFRxG$DQ(= zTiuk`P{!kzn3Ry7T#sCjq?(dPo|PV|X=V&bgfbre*O{T~*T2v2*RT8k?>TeMK9AY^ zoU_+jd+oKpYtLxvfdSp?G7S?Kc5iPsLul!k;y-DtT*Ys;Gt7o{Z!Lhe9kv_5$xsF0Sj&HNGI-ZoST2U zV4cT)`?>iw=iN6+zUawN@|6>CAGm@9yX#!1xGz`qMQ_FeUpm+>3-<*m2Gp383z#yv zI|F*$m8&x3a6zrmDgP+j0uZM7g%DOIxmb^rEt{a@Fl&}~`>&qM)N)fL4|r3;$Gxto zIdeiU=Po z&%gSig*Tp6U!Gq}Z`oPDW8Is;jKyLbI#9Ct*tG;hzC!)GOqIrdRd4O^Ykh&CG`b7c7LNE1P=8x6^3uQnA1+Dl$`gzaJ?+D-}s4=n@#H8`LC5!7Q43Z zdF{UwChd882|v8*S$p5{g8YIZ%ZLc^I>RnQ<-t)0W$VW7^oUvPD(^Smn=jFa(`_~! zaZvWz-_~AdLfM^OtB_U}cYeDMc}QC=zw-Kc9Qg?|TXk`&iezrotbF#tsFT4K?(xBV zlA@=w7kW4 z*{(slQIfTvvA^v1J^id9`dM-4Lg23)@(du~T~YGcHFh}dvw9`^Kl z+tk|I`(}!?I{K1UhOCT??jOtA7#K&Xx52lvEy9Jw_0lqZ{Nc#l(y>*gZI-`2uhJFYq*i`(;6$yMIDBcc3BszMm0D!A z)!wGK{k*a`T6-c+2%3CueHY8h2mdn_ZT<* z{PQxIE&JNrm?rPaViU}p9+y_d^#(9|#+o|SOkc(8aqsm-uIA@*vkDpr91Um$AE4+WUv`^~~^G@Q0X>dK# z@0j{;%aNgEhITq9;LXD;ij7&%Y878i1O;_jF0{w@pNKkjW8h9gB)x99+F)Ycdxx&z zsQ!+z+M$W2y{}*LQf>w>w$$94K6%=* zsQ&$gl;GTL|FAi&tmOCn22Z=4M*?`P9QxD!&r&>^UBg$6@BQQHki)$!Js(rfSSCg6 z0nK!>+%szlpU#e|>P2JSqT8QTzvl924q=B=A22&YSD*G?3}>?{;cU)_=#JHs>mt*J zK9re8NJjXbz25%5T=m|l1xyOS+p)9ZdEbUDES(tI+3{o^*{jMhJgjSduNnU&SGqxE z{%G=o1WffjPFj8UXV>+BH{!fym<)v zU4$U>KrTs8QGA)ikwan-TW|qWpo0SJ7=XAFglSB3AsEdBKrj^M=!k7c;b4hKvKxl9 zRh&rGl8>3KJH4RtZl)G0|3<%z9*jTO(VzID+Mvj1Nm85Ps1~-Nw3JXf(k2BQ@i4aJ$Ntq zQc?h}7iV|PC^OfaA{D}|ufG?!%C4}oh=@J!N`$-kt`ud}c_rEWRR;qz!oYOo36>nH z{*FhC;(UMfn{BYGPmUDk)a*LHWNTh!5ti7Oh8aOGal>@L#vfUaB;P``w#wQ9aCH&Z z))9tQ1W~FudOIi?fFlAyFjUOvIZyyrDVqo*A8^cLK`*l+wu=z+9pyZdvCb$GPh$lT zi0)N1mS$RmCm=$T?X(qF&?7=%3-pH`f(2;g!Zevceg>ro+%Nz%MDvO#P8Y-6mvz11%RD3sN!!$Vgut#!v?t#O48RtEA z4oj&yJNas=_=u;-6kIZTUAJ3PNt-0I>UZRCB#|+`+7hoJaAoOWtq^e+;8z^He=)?C zA>7f?a&fdpL~n^<+JFGo+|Wr9S%SzjWH*RmZpPIn5OJ54D-GSCLlnA5H@L1t!M zXdWd8f-PWBT|{HPJonXofkQ#i%BXKaeUZ@04{a-o2qggg3dPw|(O7*Q`HR%!#fXgV zdIn%c-#6^9Rpbp7o_gC}xA^zSesEsOQ7EA{yYYB#VqlYjcH~~pKuVvAauw~@gUsRT zVdf{q+wK#x`5k3^`{j$T%R?grhpe^TN7u&Hg%oqPn7+1NVOMzhpy~U&{=~q0Y1u%e ziQlQy2%j}_Jo>)s3zP-a1&xacCKnLI<;_E!Zi@T_B0c#Aay145+vYRmW*WQ^cYxAVRvQ%768UaFM&q+GOcly!%669W*-vB>uD3L1i%Af$ss^P zCZQpI|I)tG^EbJPxIlGOK|k`BS@p{rzGeSk;`C5jzyNWGpk%H~)yo5xmAgiuZzrtT z!Y}{Gvr+woxP)@Y`NU(_v_06#I=bg|7eVfi@&$Zp*wIFnxNQ2{&ND>1-=pD{9^5-jif7T3D=qTI_Ug816sP+t@du8=bPmQV??hN09tc&l zd>Dd%GjeM}GW-B=^n9tXzNEQjw`GtiByumEt0k#^MD|6m8vfaaVR>4IMBxmQM(QSL^O27IPh^g3O%8M)@8s86vtU> zksJlP!WH(jksdYHBSH(EU_EsaN+zfXfH~D;V#M+oUn7eNec?mrO?uq8aXbAb)*es!<=1kKuq|ane{qX-$(+0A z&%DRc25K#JCLhU`30~{YJ7?6h|6$yvPgdO%$JR4CLBuXf{P7?~+r_FTe|FDW$Hcn( z4JZk;%u}ET&XJ`4+h-PsxXfu335ef+lOhK#+&%~e07y0rV&&*aK#~wQtUMyGfw&a0ADYjlY0eg<^~A7CAa?C#CLFuf^wVp*wC% zak^})&TgyQLvQPMwMrAy1+G`ManE4qm$uU=Ox%@CgO9_#Ma{@%%pk>itP>uJvL{@i rh0qAKpdCuFqKi(vXj{49kfPRYbB<@9B)pt&OY_h!@T*Dc)8qdE>gd^q literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux45/captures/inside-to-dais.jpg b/Standalone/Research/WorldAssemblerM3/Linux45/captures/inside-to-dais.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a7410b0accab0bf517051cedee50b025a3f7fa43 GIT binary patch literal 36590 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 CHTa1zN5FwX@(L;a+_%6a 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�nFVjJQ1}Sdim#$6r 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 · [R3]() + +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](). + +- **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]() · [L40 adapter and binding]() · [Binding function]() + +- **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]() · [Composition]() + +- **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]() · [Stamp BVH]() · [Passage feature BVH]() + +- **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]() · [L44 finding]() + +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]() + +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? \ No newline at end of file diff --git a/Standalone/Source/StreamBenchmarkHeadless.cpp b/Standalone/Source/StreamBenchmarkHeadless.cpp index 71c5d2c..12287d3 100644 --- a/Standalone/Source/StreamBenchmarkHeadless.cpp +++ b/Standalone/Source/StreamBenchmarkHeadless.cpp @@ -56,6 +56,7 @@ namespace int RenderDistanceOverride=0; bool FloatingIslandsRecipe=false; bool LandmarkChamberDemo=false; + bool AuditLandmarkReachability=false; bool TlsProbeRequested=false; int TlsProbeWorkers=0; std::string TlsProbeTouch="none"; @@ -111,6 +112,7 @@ namespace bool MakeLandmarkChamberDemoRequests( const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout, + const VoxelForgeCore::WorldAssemblyManifest& worldAssembly, std::int32_t seed, std::vector& requests, std::string& error) @@ -147,49 +149,371 @@ namespace chamber.stableId="landmark/sw-arrival-chamber-v1"; chamber.strateIndex=surface->strateIndex; chamber.kind=ResolvedSdfStampKindV1::carveEllipsoid; + // Anchor the chamber horizontally beside the arrival passage. Its height is + // set from the local SurfaceWorld ground sample below so the carve keeps a + // real rock roof instead of breaking through a low patch of terrain. chamber.anchorPositionMicrometres={ arrivalMicrometres[0]+static_cast(std::llround(sideX*28000000.0)), arrivalMicrometres[1]+static_cast(std::llround(sideY*28000000.0)), - arrivalMicrometres[2]-14000000}; - chamber.placementRadiusMicrometres=2000000; - chamber.halfExtentsMicrometres={8500000,7500000,6000000}; - chamber.smoothWidthMicrometres=800000; - chamber.irregularityAmplitudeMicrometres=700000; - chamber.irregularityWavelengthMicrometres=4800000; + arrivalMicrometres[2]-13000000}; + chamber.placementRadiusMicrometres=0; + chamber.halfExtentsMicrometres={8500000,7500000,3000000}; + chamber.smoothWidthMicrometres=600000; + chamber.irregularityAmplitudeMicrometres=600000; + chamber.irregularityWavelengthMicrometres=3800000; ResolvedLayoutStampRequestV1 dais; - dais.stableId="landmark/sw-arrival-chamber-dais-v1"; + // Resolved stamps compose in stable-ID order. Keep the raised fills after + // chamber-v1 so that the enclosing carve cannot cut them away. + dais.stableId="landmark/sw-arrival-chamber-v1-dais"; dais.strateIndex=surface->strateIndex; + // The version-2 ellipsoid fill rounds the platform into the chamber floor + // instead of leaving the long, straight wall of a flat-topped pillar. dais.kind=ResolvedSdfStampKindV1::fillDais; dais.anchorPositionMicrometres=chamber.anchorPositionMicrometres; - dais.anchorPositionMicrometres[2]-=2600000; - dais.placementRadiusMicrometres=1000000; - dais.halfExtentsMicrometres={3200000,2800000,900000}; - dais.smoothWidthMicrometres=300000; + // Keep the raised platform on the broad filled floor rather than merging + // it into the chamber's outer slope. Leave a capsule-width landing around + // the ramp's centerline at the chamber entrance. + dais.anchorPositionMicrometres[0]+=static_cast(std::llround(sideX*2600000.0)); + dais.anchorPositionMicrometres[1]+=static_cast(std::llround(sideY*2600000.0)); + dais.anchorPositionMicrometres[2]-=400000; + dais.placementRadiusMicrometres=0; + dais.halfExtentsMicrometres={2100000,1800000,1300000}; + dais.smoothWidthMicrometres=450000; dais.irregularityAmplitudeMicrometres=180000; - dais.irregularityWavelengthMicrometres=2200000; + dais.irregularityWavelengthMicrometres=1800000; - const double entranceDirectionX=sideX,entranceDirectionY=sideY; - ResolvedLayoutStampRequestV1 entrance; - entrance.stableId="landmark/sw-arrival-chamber-entrance-v1"; - entrance.strateIndex=surface->strateIndex; - entrance.kind=ResolvedSdfStampKindV1::carveArch; - entrance.anchorPositionMicrometres={ - chamber.anchorPositionMicrometres[0] - +static_cast(std::llround(entranceDirectionX*6500000.0)), - chamber.anchorPositionMicrometres[1] - +static_cast(std::llround(entranceDirectionY*6500000.0)), - chamber.anchorPositionMicrometres[2]}; - entrance.placementRadiusMicrometres=1000000; - entrance.halfExtentsMicrometres={2600000,4200000,3200000}; - entrance.smoothWidthMicrometres=350000; - entrance.irregularityAmplitudeMicrometres=250000; - entrance.irregularityWavelengthMicrometres=2600000; - if(std::abs(entranceDirectionX)>std::abs(entranceDirectionY)) - entrance.yawQuarterTurns=entranceDirectionX>0.0?3:1; - else - entrance.yawQuarterTurns=entranceDirectionY>0.0?0:2; - requests={std::move(chamber),std::move(entrance),std::move(dais)}; + ResolvedLayoutStampRequestV1 chamberFloor; + chamberFloor.stableId="landmark/sw-arrival-chamber-v1-floor"; + chamberFloor.strateIndex=surface->strateIndex; + chamberFloor.kind=ResolvedSdfStampKindV1::fillDais; + chamberFloor.anchorPositionMicrometres=chamber.anchorPositionMicrometres; + chamberFloor.anchorPositionMicrometres[2]-=2500000; + chamberFloor.placementRadiusMicrometres=0; + chamberFloor.halfExtentsMicrometres={5800000,5200000,2000000}; + chamberFloor.smoothWidthMicrometres=250000; + chamberFloor.irregularityAmplitudeMicrometres=80000; + chamberFloor.irregularityWavelengthMicrometres=2200000; + + const auto surfacePlan=std::find_if(worldAssembly.plan.strates.begin(), + worldAssembly.plan.strates.end(),[&](const auto& value) + {return value.strateIndex==surface->strateIndex;}); + if(surfacePlan==worldAssembly.plan.strates.end()) + {error="landmark chamber demo needs its SurfaceWorld heightfield plan";return false;} + const Heightfield2D field(worldAssembly.recipe,*surfacePlan); + if(!field.isValid()) + {error="landmark chamber demo SurfaceWorld heightfield is invalid";return false;} + + const double coreMicrometres=std::max(1,layout.coreVoxelSizeMicrometres); + const double qToMicrometres=coreMicrometres/WorldAssemblySubvoxelsPerVoxel; + const auto qToMetres=[&](std::int64_t value) + {return static_cast(value)*qToMicrometres/1000000.0;}; + const auto metresToQ=[&](double value) + {return static_cast(std::llround(value*1000000.0/qToMicrometres));}; + const double startX=qToMetres(mouth[0])+sideX*20.0; + const double startY=qToMetres(mouth[1])+sideY*20.0; + const auto surfaceSample=field.point(metresToQ(startX),metresToQ(startY),0); + const std::int64_t surfaceHeightQ=surfaceSample.groundHeightSubvoxels; + const double surfaceHeight=qToMetres(surfaceHeightQ); + + // Search beside the arrival corridor for a chamber site whose actual terrain + // height lets the player descend from this surface entrance at a walkable grade. + // The earlier fixed XY site lay over a deep valley, leaving no room for a roof + // and asking the ramp to drop 37 m in 71 m. + double chamberSideOffset=0.0,chamberTangentOffset=0.0; + double chamberGroundHeight=0.0,bestChamberSiteScore=std::numeric_limits::infinity(); + for(double sideOffset=28.0;sideOffset<=52.0;sideOffset+=2.0) + for(double tangentOffset=-32.0;tangentOffset<=32.0;tangentOffset+=2.0) + { + const double x=qToMetres(mouth[0])+sideX*sideOffset+dx/length*tangentOffset; + const double y=qToMetres(mouth[1])+sideY*sideOffset+dy/length*tangentOffset; + const double distanceFromEntrance=std::hypot(x-startX,y-startY); + if(distanceFromEntrance<16.0)continue; + const auto sample=field.point(metresToQ(x),metresToQ(y),0); + const double ground=qToMetres(sample.groundHeightSubvoxels); + if(groundsurfaceHeight+4.0)continue; + const double sideDelta=sideOffset-42.0; + const double tangentDelta=tangentOffset-2.0; + const double score=sideDelta*sideDelta+tangentDelta*tangentDelta + +4.0*(ground-surfaceHeight)*(ground-surfaceHeight); + if(score>=bestChamberSiteScore)continue; + bestChamberSiteScore=score;chamberSideOffset=sideOffset; + chamberTangentOffset=tangentOffset;chamberGroundHeight=ground; + chamber.anchorPositionMicrometres[0]=static_cast( + std::llround(x*1000000.0)); + chamber.anchorPositionMicrometres[1]=static_cast( + std::llround(y*1000000.0)); + } + if(!std::isfinite(bestChamberSiteScore)) + {error="landmark chamber has no nearby surface site with a walkable descent grade";return false;} + constexpr double ChamberRoofCoverMetres=0.5; + const double chamberCarveTopReach= + static_cast(chamber.halfExtentsMicrometres[2] + +chamber.smoothWidthMicrometres + +chamber.irregularityAmplitudeMicrometres)/1000000.0; + const double chamberCenterZ=chamberGroundHeight-chamberCarveTopReach + -ChamberRoofCoverMetres; + chamber.anchorPositionMicrometres[2]=static_cast( + std::llround(chamberCenterZ*1000000.0)); + dais.anchorPositionMicrometres[0]=chamber.anchorPositionMicrometres[0] + +static_cast(std::llround(sideX*2600000.0)); + dais.anchorPositionMicrometres[1]=chamber.anchorPositionMicrometres[1] + +static_cast(std::llround(sideY*2600000.0)); + dais.anchorPositionMicrometres[2]=chamber.anchorPositionMicrometres[2]-400000; + chamberFloor.anchorPositionMicrometres[0]=chamber.anchorPositionMicrometres[0]; + chamberFloor.anchorPositionMicrometres[1]=chamber.anchorPositionMicrometres[1]; + chamberFloor.anchorPositionMicrometres[2]=chamber.anchorPositionMicrometres[2]-2500000; + + struct FPathPoint { double x=0.0,y=0.0; }; + const FPathPoint start{startX,startY}; + const FPathPoint firstTurn{ + qToMetres(mouth[0])+sideX*34000000.0/1000000.0+dx/length*14000000.0/1000000.0, + qToMetres(mouth[1])+sideY*34000000.0/1000000.0+dy/length*14000000.0/1000000.0}; + const FPathPoint end{ + chamber.anchorPositionMicrometres[0]/1000000.0, + chamber.anchorPositionMicrometres[1]/1000000.0}; + const std::array waypoints={start,firstTurn,end}; + std::vector densePath; + const auto appendLine=[&](FPathPoint from,FPathPoint to) + { + const double distance=std::hypot(to.x-from.x,to.y-from.y); + const int steps=std::max(1,static_cast(std::ceil(distance/0.25))); + for(int i=0;i<=steps;++i) + { + const double t=static_cast(i)/steps; + const FPathPoint point{from.x+(to.x-from.x)*t,from.y+(to.y-from.y)*t}; + if(densePath.empty()||std::hypot(point.x-densePath.back().x, + point.y-densePath.back().y)>1.0e-6)densePath.push_back(point); + } + }; + constexpr double BendRadius=6.0; + FPathPoint current=waypoints.front(); + for(std::size_t i=1;i+1(step)/ArcSteps,oneMinus=1.0-t; + const FPathPoint point{ + oneMinus*oneMinus*before.x+2.0*oneMinus*t*corner.x+t*t*after.x, + oneMinus*oneMinus*before.y+2.0*oneMinus*t*corner.y+t*t*after.y}; + if(densePath.empty()||std::hypot(point.x-densePath.back().x, + point.y-densePath.back().y)>1.0e-6)densePath.push_back(point); + } + current=after; + } + appendLine(current,waypoints.back()); + if(densePath.size()<2) + {error="landmark cave ramp path is degenerate";return false;} + std::vector arcLength(densePath.size(),0.0); + for(std::size_t i=1;i::infinity(); + for(std::size_t i=0;istrateIndex; + mouthStamp.kind=ResolvedSdfStampKindV1::carveEllipsoid; + mouthStamp.anchorPositionMicrometres={ + static_cast(std::llround(startX*1000000.0)), + static_cast(std::llround(startY*1000000.0)), + static_cast(std::llround((startZ+1.0)*1000000.0))}; + mouthStamp.placementRadiusMicrometres=0; + mouthStamp.halfExtentsMicrometres={4200000,3700000,2800000}; + mouthStamp.smoothWidthMicrometres=450000; + // A broad, rounded upper flare blends the side-facing portal into the slope. + mouthStamp.irregularityAmplitudeMicrometres=250000; + mouthStamp.irregularityWavelengthMicrometres=2400000; + std::printf("[LandmarkChamberPlan] center_m=(%.3f,%.3f,%.3f) local_ground_m=%.3f carve_top_reach_m=%.3f roof_cover_m=%.3f\n", + end.x,end.y,chamberCenterZ,chamberGroundHeight,chamberCarveTopReach, + chamberGroundHeight-(chamberCenterZ+chamberCarveTopReach)); + std::printf("[LandmarkChamberSite] side_offset_m=%.3f tangent_offset_m=%.3f entrance_ground_m=%.3f chamber_ground_m=%.3f\n", + chamberSideOffset,chamberTangentOffset,surfaceHeight,chamberGroundHeight); + std::printf("[LandmarkRampPlan] entry_m=(%.3f,%.3f,%.3f) ground_m=%.3f cap_m=%.3f end_m=(%.3f,%.3f,%.3f) horizontal_m=%.3f entry_landing_m=%.3f turn_landing_m=%.3f turn_distance_m=%.3f sloped_m=%.3f descent_m=%.3f grade_deg=%.3f required_length_m=%.3f\n", + startX,startY,startZ,surfaceHeight, + qToMetres(surfaceSample.capHeightSubvoxels),end.x,end.y,endZ,totalLength, + EntryLandingLength,firstTurnLandingLength,firstTurnDistance, + slopedLength,descent, + std::atan2(std::abs(descent),slopedLength)*180.0/3.14159265358979323846, + std::abs(descent)/std::tan(15.0*3.14159265358979323846/180.0)); + if(!(slopedLength>0.0)||std::atan2(std::abs(descent),slopedLength) + >15.0*3.14159265358979323846/180.0) + {error="landmark cave ramp cannot meet the 15 degree walk grade";return false;} + + const double entryPathLength=std::hypot(firstTurn.x-start.x, + firstTurn.y-start.y); + const double entryPathX=(firstTurn.x-start.x)/entryPathLength; + const double entryPathY=(firstTurn.y-start.y)/entryPathLength; + const double mouthNormalX=-entryPathY,mouthNormalY=entryPathX; + constexpr double MouthPortalDepth=3.8; + constexpr double MouthPortalHalfWidth=2.8; + constexpr double MouthPortalCenterOffset=2.6; + const double mouthPortalX=startX+mouthNormalX*MouthPortalCenterOffset; + const double mouthPortalY=startY+mouthNormalY*MouthPortalCenterOffset; + ResolvedLayoutStampRequestV1 mouthPortal; + mouthPortal.stableId="landmark/sw-arrival-chamber-mouth-approach-v1"; + mouthPortal.strateIndex=surface->strateIndex; + mouthPortal.kind=ResolvedSdfStampKindV1::carveEllipsoid; + mouthPortal.anchorPositionMicrometres={ + static_cast(std::llround(mouthPortalX*1000000.0)), + static_cast(std::llround(mouthPortalY*1000000.0)), + static_cast(std::llround(startZ*1000000.0))}; + mouthPortal.placementRadiusMicrometres=0; + mouthPortal.halfExtentsMicrometres={3800000,2800000,2000000}; + mouthPortal.smoothWidthMicrometres=450000; + mouthPortal.irregularityAmplitudeMicrometres=180000; + mouthPortal.irregularityWavelengthMicrometres=2000000; + + ResolvedLayoutStampRequestV1 mouthLanding; + mouthLanding.stableId="landmark/sw-arrival-z-mouth-landing-v1"; + mouthLanding.strateIndex=surface->strateIndex; + mouthLanding.kind=ResolvedSdfStampKindV1::fillPillar; + mouthLanding.anchorPositionMicrometres={ + static_cast(std::llround(mouthPortalX*1000000.0)), + static_cast(std::llround(mouthPortalY*1000000.0)), + static_cast(std::llround((startZ-RampRadiusMetres-1.5) + *1000000.0))}; + mouthLanding.placementRadiusMicrometres=0; + mouthLanding.halfExtentsMicrometres={3800000,2800000,1500000}; + mouthLanding.smoothWidthMicrometres=180000; + mouthLanding.irregularityAmplitudeMicrometres=0; + mouthLanding.irregularityWavelengthMicrometres=2000000; + std::printf("[LandmarkMouthPortal] center_m=(%.3f,%.3f,%.3f) normal=(%.3f,%.3f) depth_m=%.2f half_width_m=%.2f landing_top_m=%.3f\n", + mouthPortalX,mouthPortalY,startZ,mouthNormalX,mouthNormalY, + MouthPortalDepth,MouthPortalHalfWidth,surfaceHeight); + + const auto atDistance=[&](double distance) + { + distance=std::clamp(distance,0.0,totalLength); + const auto upper=std::lower_bound(arcLength.begin(),arcLength.end(),distance); + const std::size_t hi=std::min(densePath.size()-1, + static_cast(upper-arcLength.begin())); + const std::size_t lo=hi?hi-1:0; + const double span=arcLength[hi]-arcLength[lo]; + const double t=span>0.0?(distance-arcLength[lo])/span:0.0; + return FPathPoint{densePath[lo].x+(densePath[hi].x-densePath[lo].x)*t, + densePath[lo].y+(densePath[hi].y-densePath[lo].y)*t}; + }; + const auto centerlineHeight=[&](double distance) + { + const double beforeFirstTurn=std::clamp(distance-EntryLandingLength,0.0, + firstTurnLandingStart-EntryLandingLength); + const double afterFirstTurn=distance>firstTurnLandingEnd + ?std::clamp(distance-firstTurnLandingEnd,0.0, + totalLength-firstTurnLandingEnd):0.0; + return startZ+descent*std::clamp((beforeFirstTurn+afterFirstTurn) + /slopedLength,0.0,1.0); + }; + const double pointSpacing=0.6; + const std::size_t rampCount=static_cast(std::ceil(totalLength/pointSpacing)); + std::vector rampCarves; + std::vector rampSupports; + rampCarves.reserve(rampCount+1); + // Footfall supports are denser and narrower than the tunnel stamps. This + // keeps their overlapping flat caps from forming broad downhill shelves. + const double supportSpacing=0.3; + const std::size_t supportCount=static_cast(std::ceil( + totalLength/supportSpacing)); + rampSupports.reserve(supportCount+1); + for(std::size_t i=0;i<=rampCount;++i) + { + const double distance=std::min(totalLength,static_cast(i)*pointSpacing); + const FPathPoint point=atDistance(distance); + ResolvedLayoutStampRequestV1 ramp; + const std::size_t number=i; + ramp.stableId=std::string("landmark/sw-arrival-chamber-ramp-")+ + (number<10?"00":number<100?"0":"")+std::to_string(number); + ramp.strateIndex=surface->strateIndex; + ramp.kind=ResolvedSdfStampKindV1::carveEllipsoid; + const double centerZ=centerlineHeight(distance); + ramp.anchorPositionMicrometres={ + static_cast(std::llround(point.x*1000000.0)), + static_cast(std::llround(point.y*1000000.0)), + static_cast(std::llround(centerZ*1000000.0))}; + ramp.placementRadiusMicrometres=0; + ramp.halfExtentsMicrometres={2600000,2600000,2000000}; + ramp.smoothWidthMicrometres=650000; + // Keep the footfall surface smooth enough for the capsule grade limit; + // the chamber and entrance carry the larger natural wall variation. + ramp.irregularityAmplitudeMicrometres=250000; + ramp.irregularityWavelengthMicrometres=2400000; + rampCarves.push_back(std::move(ramp)); + } + for(std::size_t i=0;i<=supportCount;++i) + { + const double distance=std::min(totalLength,static_cast(i)*supportSpacing); + const FPathPoint point=atDistance(distance); + const double floorZ=centerlineHeight(distance)-RampRadiusMetres; + ResolvedLayoutStampRequestV1 support; + support.stableId=std::string("landmark/sw-arrival-ramp-support-")+ + (i<10?"00":i<100?"0":"")+std::to_string(i); + support.strateIndex=surface->strateIndex; + support.kind=ResolvedSdfStampKindV1::fillPillar; + support.anchorPositionMicrometres={ + static_cast(std::llround(point.x*1000000.0)), + static_cast(std::llround(point.y*1000000.0)), + static_cast(std::llround((floorZ-1.5)*1000000.0))}; + support.placementRadiusMicrometres=0; + // The first support widens into a buried entry landing under the open + // mouth, so the approach surface stays supported at its carved rim. + support.halfExtentsMicrometres=i==0 + ?std::array{3500000,3000000,1500000} + :std::array{550000,550000,1500000}; + support.smoothWidthMicrometres=180000; + support.irregularityAmplitudeMicrometres=0; + support.irregularityWavelengthMicrometres=2200000; + rampSupports.push_back(std::move(support)); + } + requests.push_back(std::move(mouthStamp)); + requests.push_back(std::move(mouthPortal)); + requests.insert(requests.end(),std::make_move_iterator(rampCarves.begin()), + std::make_move_iterator(rampCarves.end())); + requests.push_back(std::move(chamber)); + requests.insert(requests.end(),std::make_move_iterator(rampSupports.begin()), + std::make_move_iterator(rampSupports.end())); + requests.push_back(std::move(mouthLanding)); + requests.push_back(std::move(chamberFloor)); + requests.push_back(std::move(dais)); (void)seed; return true; } @@ -247,13 +571,14 @@ namespace " --mesh-export=FILE (write the exact generated tile meshes for offline rendering)\n" " --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n" " --seed=N --recipe=surface-world|floating-islands\n" - " --landmark-chamber-demo (opt-in version-1 SDF chamber and entry arch)\n" + " --landmark-chamber-demo (opt-in natural SW landmark chamber and ramp)\n" " --resolved-layout-output=FILE (canonical plan-time layout snapshot)\n" " --resolved-layout-only (exit after plan and snapshot validation)\n" " --audit-resolved-layout-boundaries (query Core at each recorded endpoint)\n" " --audit-strate-transitions (measure interface crossing, band thickness and holes)\n" " --transition-mode=hard|gradient|interleaved --transition-width-chunks=N (fixture override)\n" " --audit-resolved-layout-passages (capsule, support, route-binding and blocked-control audit)\n" + " --audit-landmark-reachability (capsule, support, grade and buried-L43 control)\n" " --audit-surface-clearance (sample biome terrain/caps against the resolved ceiling)\n" " --auto-surface-camera (place the 27-tile cube on this seed's surface)\n" " --mesh-coverage (report non-empty tiles and indexed triangles)\n" @@ -275,6 +600,7 @@ namespace else if(arg=="--resolved-layout-only")options.ResolvedLayoutOnly=true; else if(arg=="--audit-resolved-layout-boundaries")options.AuditResolvedLayoutBoundaries=true; else if(arg=="--audit-resolved-layout-passages")options.AuditResolvedLayoutPassages=true; + else if(arg=="--audit-landmark-reachability")options.AuditLandmarkReachability=true; else if(arg=="--audit-surface-clearance")options.AuditSurfaceClearance=true; else if(arg=="--audit-strate-transitions")options.AuditStrateTransitions=true; else if(arg.starts_with("--transition-mode=")) @@ -494,8 +820,8 @@ int main(int argc,char** argv) if(options.LandmarkChamberDemo) { std::vector requests; - if(!MakeLandmarkChamberDemoRequests(resolvedLayout,seed,requests,error) - ||!VoxelForgeCore::ResolveResolvedLayoutStampsV1( + if(!MakeLandmarkChamberDemoRequests(resolvedLayout,worldAssembly,seed,requests,error) + ||!VoxelForgeCore::ResolveResolvedLayoutStampsV2( resolvedLayout,seed,requests,error)) {std::fprintf(stderr,"Landmark chamber stamp resolution failed: %s\n",error.c_str());return 2;} const auto audit=VoxelForgeCore::AuditResolvedLayoutStampPlacementV1(resolvedLayout); @@ -798,5 +1124,5 @@ int main(int argc,char** argv) options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence, options.AuditRouteParity,options.AuditResolvedLayoutBoundaries, options.AuditResolvedLayoutPassages,options.AuditStrateTransitions, - &resolvedLayout,options.StreamHashTiles); + &resolvedLayout,options.StreamHashTiles,options.AuditLandmarkReachability); } diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index ffa296d..a91e81e 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include #include @@ -122,6 +124,363 @@ namespace StandaloneStreamBenchmark return Result; } + struct FLandmarkReachabilityResult + { + std::uint64_t pathSamples=0,capsuleSamples=0,blockedCapsuleSamples=0; + std::uint64_t supportSamples=0,unsupportedSamples=0,gradeFailures=0; + double maximumGradeDegrees=0.0,maximumSupportDepthMetres=0.0; + double minimumClearanceMetres=std::numeric_limits::infinity(); + double latestPointMinimumClearanceMetres=std::numeric_limits::infinity(); + std::array latestPointMinimumClearancePosition{}; + bool passed=false; + }; + + inline FLandmarkReachabilityResult AuditLandmarkWalkPath( + const VoxelForgeCore::World& shape, + const VoxelForgeCore::WorldAssemblyManifest* assembly, + const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout, + std::uint32_t coreVoxelSizeMicrometres,std::string_view label) + { + FLandmarkReachabilityResult result; + const auto surface=std::find_if(layout.strates.begin(),layout.strates.end(), + [](const auto& value){return value.generatorKind=="SurfaceWorld";}); + if(surface==layout.strates.end())return result; + const auto passage=std::find_if(layout.passages.begin(),layout.passages.end(), + [&](const auto& value){return value.lowerStrateIndex==surface->strateIndex;}); + if(passage==layout.passages.end()||assembly==nullptr)return result; + const auto surfacePlan=std::find_if(assembly->plan.strates.begin(),assembly->plan.strates.end(), + [&](const auto& value){return value.strateIndex==surface->strateIndex;}); + if(surfacePlan==assembly->plan.strates.end())return result; + const VoxelForgeCore::Heightfield2D surfaceField(assembly->recipe,*surfacePlan); + if(!surfaceField.isValid())return result; + std::vector ramp; + for(const auto& stamp:layout.stamps) + if(stamp.stableId.starts_with("landmark/sw-arrival-chamber-ramp-")) + ramp.push_back(&stamp); + std::sort(ramp.begin(),ramp.end(),[](const auto* a,const auto* b) + {return a->stableIdstableId;}); + if(ramp.size()<2)return result; + + const double voxelMetres=static_cast(std::max(1, + coreVoxelSizeMicrometres))/1000000.0; + const double metresPerSubvoxel=voxelMetres + /VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; + struct FPathPoint { double x=0.0,y=0.0,z=0.0;bool tunnel=false; }; + std::vector path; + const auto& arrival=passage->lowerMouthSubvoxels; + const double arrivalX=arrival[0]*metresPerSubvoxel; + const double arrivalY=arrival[1]*metresPerSubvoxel; + const double arrivalCenterZ=arrival[2]*metresPerSubvoxel+0.9; + + const auto densityMetres=[&](double x,double y,double z) + { + return static_cast(shape.densityAt({x/voxelMetres, + y/voxelMetres,z/voxelMetres}))*voxelMetres; + }; + const auto samplePoint=[&](double x,double y,double centerZ,bool tunnel) + { + constexpr double FloorStep=0.01; + double floorZ=centerZ-5.0; + bool foundFloor=false; + if(tunnel) + { + if(densityMetres(x,y,centerZ)>0.025) + for(double offset=-0.10;offset>=-10.0;offset-=FloorStep) + if(densityMetres(x,y,centerZ+offset)<0.0) + {floorZ=centerZ+offset+FloorStep;foundFloor=true;break;} + } + else + { + const auto xSubvoxels=static_cast(std::llround(x/metresPerSubvoxel)); + const auto ySubvoxels=static_cast(std::llround(y/metresPerSubvoxel)); + floorZ=surfaceField.point(xSubvoxels,ySubvoxels,0).groundHeightSubvoxels + *metresPerSubvoxel; + foundFloor=std::isfinite(floorZ); + } + ++result.pathSamples; + if(!foundFloor) + { + ++result.unsupportedSamples; + return std::pair{floorZ,false}; + } + + // Reserve a footfall margin above the sampled ground for the vertical + // capsule; it prevents the lower hemisphere from grazing adjacent floor + // samples on a gently sloped ramp. + floorZ+=0.15; + + constexpr double CapsuleRadius=0.34; + constexpr double CapsuleHeight=1.76; + constexpr double RequiredClearance=0.025; + constexpr double VerticalStep=0.18; + constexpr double HorizontalStep=0.17; + result.latestPointMinimumClearanceMetres=std::numeric_limits::infinity(); + for(double height=0.08;height<=CapsuleHeight;height+=VerticalStep) + { + double capOffset=0.0; + if(heightCapsuleHeight-CapsuleRadius) + capOffset=height-(CapsuleHeight-CapsuleRadius); + const double radial=std::sqrt(std::max(0.0, + CapsuleRadius*CapsuleRadius-capOffset*capOffset)); + for(double oy=-radial;oy<=radial+1.0e-9;oy+=HorizontalStep) + for(double ox=-radial;ox<=radial+1.0e-9;ox+=HorizontalStep) + { + if(ox*ox+oy*oy>radial*radial+1.0e-9)continue; + const double clearance=densityMetres(x+ox,y+oy,floorZ+height); + ++result.capsuleSamples; + result.minimumClearanceMetres=std::min( + result.minimumClearanceMetres,clearance); + if(clearance,5> supportOffsets={{{0.0,0.0}, + {0.28,0.0},{-0.28,0.0},{0.0,0.28},{0.0,-0.28}}}; + std::uint32_t supported=0; + for(const auto& offset:supportOffsets) + { + ++result.supportSamples; + bool hit=false; + for(double depth=0.10;depth<=0.55;depth+=0.08) + if(densityMetres(x+offset[0],y+offset[1],floorZ-depth)<0.0) + {hit=true;result.maximumSupportDepthMetres=std::max( + result.maximumSupportDepthMetres,depth);break;} + if(hit)++supported; + } + const bool supportOk=supported>=3; + if(!supportOk)++result.unsupportedSamples; + return std::pair{floorZ,supportOk}; + }; + + // Find a walkable surface route around the passage's support rock. The nearby grid is + // evaluated against the same composed density field as the ramp and both L43/L45 runs. + constexpr int GridRadius=52; + constexpr int GridWidth=GridRadius*2+1; + constexpr double GridStep=0.50; + constexpr double MaximumGradeRadians=15.0*3.14159265358979323846/180.0; + struct FSurfaceNode + { + bool evaluated=false,walkable=false,closed=false; + double floorZ=0.0,cost=std::numeric_limits::infinity(); + int parent=-1; + }; + const int goalX=GridRadius+static_cast(std::llround( + (ramp.front()->positionMicrometres[0]/1000000.0-arrivalX)/GridStep)); + const int goalY=GridRadius+static_cast(std::llround( + (ramp.front()->positionMicrometres[1]/1000000.0-arrivalY)/GridStep)); + const auto nodeIndex=[](int x,int y){return y*GridWidth+x;}; + std::vector surfaceNodes(static_cast(GridWidth*GridWidth)); + std::uint64_t exploredNodes=0; + const auto evaluateNode=[&](int ix,int iy)->FSurfaceNode* + { + if(ix<0||iy<0||ix>=GridWidth||iy>=GridWidth)return nullptr; + auto& node=surfaceNodes[static_cast(nodeIndex(ix,iy))]; + if(node.evaluated)return &node; + node.evaluated=true;++exploredNodes; + const double x=arrivalX+(ix-GridRadius)*GridStep; + const double y=arrivalY+(iy-GridRadius)*GridStep; + const auto blockedBefore=result.blockedCapsuleSamples; + const auto unsupportedBefore=result.unsupportedSamples; + const auto pathBefore=result.pathSamples; + const double scanCenter=arrivalCenterZ; + const auto [floorZ,supported]=samplePoint(x,y,scanCenter,false); + node.floorZ=floorZ; + node.walkable=supported&&result.pathSamples>pathBefore + &&result.blockedCapsuleSamples==blockedBefore + &&result.unsupportedSamples==unsupportedBefore; + return &node; + }; + struct FOpenNode { double score=0.0;int index=0; }; + const auto farther=[](const FOpenNode& a,const FOpenNode& b) + {return a.score>b.score;}; + std::priority_queue,decltype(farther)> open(farther); + const int goalIndex=goalX>=0&&goalY>=0&&goalX=0) + { + constexpr double GroundStartRadius=18.0; + const int sourceRadius=static_cast(std::ceil(GroundStartRadius/GridStep)); + for(int iy=GridRadius-sourceRadius;iy<=GridRadius+sourceRadius;++iy) + for(int ix=GridRadius-sourceRadius;ix<=GridRadius+sourceRadius;++ix) + { + const double dx=(ix-GridRadius)*GridStep,dy=(iy-GridRadius)*GridStep; + const double distance=std::hypot(dx,dy); + if(distance>GroundStartRadius)continue; + auto* start=evaluateNode(ix,iy); + if(!start||!start->walkable)continue; + start->cost=distance;start->parent=-1; + open.push({distance+GridStep*std::hypot(goalX-ix,goalY-iy), + nodeIndex(ix,iy)}); + } + constexpr std::array,8> neighbours={{{1,0},{-1,0},{0,1},{0,-1}, + {1,1},{1,-1},{-1,1},{-1,-1}}}; + while(!open.empty()) + { + const int currentIndex=open.top().index;open.pop(); + auto& current=surfaceNodes[static_cast(currentIndex)]; + if(current.closed)continue; + current.closed=true; + if(currentIndex==goalIndex)break; + const int currentX=currentIndex%GridWidth,currentY=currentIndex/GridWidth; + for(const auto& direction:neighbours) + { + const int nextX=currentX+direction[0],nextY=currentY+direction[1]; + auto* next=evaluateNode(nextX,nextY); + if(!next||!next->walkable||next->closed)continue; + const double run=GridStep*std::hypot(direction[0],direction[1]); + const double rise=std::abs(next->floorZ-current.floorZ); + if(std::atan2(rise,run)>MaximumGradeRadians)continue; + if(direction[0]!=0&&direction[1]!=0) + { + const auto* sideA=evaluateNode(currentX+direction[0],currentY); + const auto* sideB=evaluateNode(currentX,currentY+direction[1]); + if(!sideA||!sideA->walkable||!sideB||!sideB->walkable)continue; + } + const double cost=current.cost+run+rise*0.25; + if(cost>=next->cost)continue; + next->cost=cost;next->parent=currentIndex; + const double heuristic=GridStep*std::hypot(goalX-nextX,goalY-nextY); + open.push({cost+heuristic,nodeIndex(nextX,nextY)}); + } + } + } + std::vector surfaceRoute; + if(goalIndex>=0&&surfaceNodes[static_cast(goalIndex)].closed) + { + for(int current=goalIndex;current>=0;current=surfaceNodes[static_cast(current)].parent) + surfaceRoute.push_back(current); + std::reverse(surfaceRoute.begin(),surfaceRoute.end()); + } + const bool surfaceGoalWalkable=goalIndex>=0 + &&evaluateNode(goalX,goalY)->walkable; + if(surfaceRoute.size()<2)surfaceRoute.clear(); + const auto surfaceRouteSamples=surfaceRoute.size(); + double surfaceStartDistance=std::numeric_limits::infinity(); + if(!surfaceRoute.empty()) + { + path.reserve(surfaceRoute.size()+ramp.size()); + const int startX=surfaceRoute.front()%GridWidth; + const int startY=surfaceRoute.front()/GridWidth; + surfaceStartDistance=GridStep*std::hypot(startX-GridRadius,startY-GridRadius); + for(const int index:surfaceRoute) + { + const int ix=index%GridWidth,iy=index/GridWidth; + const auto& node=surfaceNodes[static_cast(index)]; + path.push_back({arrivalX+(ix-GridRadius)*GridStep, + arrivalY+(iy-GridRadius)*GridStep,node.floorZ+0.9,false}); + } + for(const auto* stamp:ramp) + path.push_back({stamp->positionMicrometres[0]/1000000.0, + stamp->positionMicrometres[1]/1000000.0, + stamp->positionMicrometres[2]/1000000.0,true}); + } + std::printf("[LandmarkSurfaceRoute] reachable=%s goal_walkable=%s grid_step_m=%.2f start_offset_m=%.2f explored=%llu surface_points=%zu grade_limit_deg=15.000\n", + surfaceRoute.empty()?"false":"true",surfaceGoalWalkable?"true":"false",GridStep,surfaceStartDistance, + static_cast(exploredNodes),surfaceRouteSamples); + result={}; + if(path.size()<2)return result; + + bool allSupport=true,allGrade=true; + bool havePrior=false;double priorX=0.0,priorY=0.0,priorFloor=0.0; + std::size_t diagnosticCount=0; + for(std::size_t segment=1;segment(1, + static_cast(std::ceil(horizontal/0.8))); + for(std::size_t step=segment==1?0:1;step<=steps;++step) + { + const double t=static_cast(step)/steps; + const double x=from.x+(to.x-from.x)*t; + const double y=from.y+(to.y-from.y)*t; + const double centerZ=from.z+(to.z-from.z)*t; + const auto blockedBefore=result.blockedCapsuleSamples; + const auto capsuleBefore=result.capsuleSamples; + const auto supportBefore=result.supportSamples; + const bool tunnel=from.tunnel&&to.tunnel; + const auto [floorZ,supportOk]=samplePoint(x,y,centerZ,tunnel); + allSupport&=supportOk; + double grade=0.0; + if(havePrior) + { + const double run=std::hypot(x-priorX,y-priorY); + grade=run>1.0e-6 + ?std::atan2(std::abs(floorZ-priorFloor),run)*180.0/3.14159265358979323846 + :std::numeric_limits::infinity(); + result.maximumGradeDegrees=std::max(result.maximumGradeDegrees,grade); + if(grade>15.0) {++result.gradeFailures;allGrade=false;} + } + if(diagnosticCount<16&&(result.blockedCapsuleSamples>blockedBefore + ||!supportOk||grade>15.0)) + { + std::printf("[LandmarkWalkPoint] field=%.*s mode=%s x=%.3f y=%.3f center_z=%.3f floor_z=%.3f previous_x=%.3f previous_y=%.3f previous_floor_z=%.3f horizontal_step_m=%.3f blocked=%llu/%llu support_probes=%llu grade_deg=%.3f density_center_m=%.3f capsule_min_m=%.3f capsule_min_at_m=(%.3f,%.3f,%.3f)\n", + static_cast(label.size()),label.data(),tunnel?"ramp":"surface",x,y,centerZ,floorZ, + priorX,priorY,priorFloor,std::hypot(x-priorX,y-priorY), + static_cast(result.blockedCapsuleSamples-blockedBefore), + static_cast(result.capsuleSamples-capsuleBefore), + static_cast(result.supportSamples-supportBefore), + grade,densityMetres(x,y,centerZ),result.latestPointMinimumClearanceMetres, + result.latestPointMinimumClearancePosition[0], + result.latestPointMinimumClearancePosition[1], + result.latestPointMinimumClearancePosition[2]); + ++diagnosticCount; + } + havePrior=true;priorX=x;priorY=y;priorFloor=floorZ; + } + } + result.passed=result.pathSamples>1&&result.capsuleSamples>0 + &&result.blockedCapsuleSamples==0&&allSupport&&allGrade + &&result.unsupportedSamples==0&&result.gradeFailures==0; + return result; + } + + inline bool MakeL43BuriedLandmarkControl( + const VoxelForgeCore::ResolvedLayoutSnapshotV3& source, + VoxelForgeCore::ResolvedLayoutSnapshotV3& control,std::string& error) + { + using namespace VoxelForgeCore; + control=source;control.stamps.clear();control.stampVocabularyVersion=1; + const auto add=[&](std::string id,ResolvedSdfStampKindV1 kind, + std::array position, + std::array half,std::int64_t smooth, + std::int64_t rough,std::int64_t wavelength,std::uint64_t seed, + std::uint8_t yaw) + { + ResolvedLayoutStampV1 stamp;stamp.stableId=std::move(id); + stamp.strateIndex=1;stamp.kind=kind;stamp.positionMicrometres=position; + stamp.halfExtentsMicrometres=half;stamp.smoothWidthMicrometres=smooth; + stamp.irregularityAmplitudeMicrometres=rough; + stamp.irregularityWavelengthMicrometres=wavelength; + stamp.irregularitySeed=seed;stamp.yawQuarterTurns=yaw; + const std::int64_t pad=smooth+rough; + const std::array bounds={ + half[yaw%2?1:0]+pad,half[yaw%2?0:1]+pad,half[2]+pad}; + for(std::size_t axis=0;axis<3;++axis) + {stamp.boundsMinimumMicrometres[axis]=position[axis]-bounds[axis]; + stamp.boundsMaximumMicrometres[axis]=position[axis]+bounds[axis];} + control.stamps.push_back(std::move(stamp)); + }; + add("landmark/sw-arrival-chamber-v1",ResolvedSdfStampKindV1::carveEllipsoid, + {-8917798,31949858,-513083425},{8500000,7500000,6000000}, + 800000,700000,4800000,4704841454711558ull,0); + add("landmark/sw-arrival-chamber-entrance-v1",ResolvedSdfStampKindV1::carveArch, + {-12289139,37026886,-513310578},{2600000,4200000,3200000}, + 350000,250000,2600000,2371181934872559ull,0); + add("landmark/sw-arrival-chamber-dais-v1",ResolvedSdfStampKindV1::fillDais, + {-7489021,30688194,-515838454},{3200000,2800000,900000}, + 300000,180000,2200000,7240637434535735ull,0); + if(!control.finalize(error))return false; + std::vector issues; + return control.validate(ResolvedLayoutValidationModeV3::strict,issues,error); + } + inline bool SameTileOutput(const VoxelForgeCore::TileMesh& A, const VoxelForgeCore::TileMesh& B) { @@ -424,7 +783,7 @@ namespace StandaloneStreamBenchmark bool bAuditResolvedLayoutPassages=false, bool bAuditStrateTransitions=false, const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr, - bool bPrintTileHashes=false) + bool bPrintTileHashes=false,bool bAuditLandmarkReachability=false) { using Clock=std::chrono::steady_clock; FStandaloneOwnerWorld& World=*OwnerWorld; @@ -609,6 +968,67 @@ namespace StandaloneStreamBenchmark Streamer.Stop(); std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2; } + if(bAuditLandmarkReachability) + { + Streamer.Stop(); + if(ResolvedLayout==nullptr||!CoreSnapshot.resolvedLayout() + ||ResolvedLayout->stamps.empty()) + {std::fprintf(stderr,"[LandmarkReachabilityAudit] missing demo layout and stamps\n");return 2;} + const auto candidate=AuditLandmarkWalkPath(Shape,Assembly,*ResolvedLayout, + World.CoreVoxelSizeMicrons,"L45"); + const auto daisStamp=std::find_if(ResolvedLayout->stamps.begin(), + ResolvedLayout->stamps.end(),[](const auto& stamp) + {return stamp.stableId=="landmark/sw-arrival-chamber-v1-dais";}); + if(daisStamp!=ResolvedLayout->stamps.end()) + { + const double voxelMetres=std::max(1, + World.CoreVoxelSizeMicrons)/1000000.0; + for(const double offset:{-0.5,0.0,0.5,0.9,1.2}) + { + const double x=daisStamp->positionMicrometres[0]/1000000.0; + const double y=daisStamp->positionMicrometres[1]/1000000.0; + const double z=daisStamp->positionMicrometres[2]/1000000.0+offset; + const double density=Shape.densityAt({x/voxelMetres,y/voxelMetres, + z/voxelMetres})*voxelMetres; + std::printf("[LandmarkDaisProfile] offset_m=%.2f sample_z_m=%.3f density_m=%.4f\n", + offset,z,density); + } + } + VoxelForgeCore::ResolvedLayoutSnapshotV3 oldLayout; + std::string oldError; + if(!MakeL43BuriedLandmarkControl(*ResolvedLayout,oldLayout,oldError)) + {std::fprintf(stderr,"[LandmarkReachabilityAudit] L43 control layout failed: %s\n", + oldError.c_str());return 2;} + auto oldSnapshot=CoreSnapshot.withResolvedLayout(oldLayout); + if(!oldSnapshot) + {std::fprintf(stderr,"[LandmarkReachabilityAudit] could not construct L43 control snapshot\n");return 2;} + VoxelForgeCore::World oldShape(oldSnapshot); + std::string oldBuildError; + if(!oldShape.compile(ShapeRequest,Scheduler,oldBuildError)) + {std::fprintf(stderr,"[LandmarkReachabilityAudit] L43 control field build failed: %s\n", + oldBuildError.c_str());return 2;} + const auto buried=AuditLandmarkWalkPath(oldShape,Assembly,*ResolvedLayout, + World.CoreVoxelSizeMicrons,"L43"); + std::printf("[LandmarkWalkability] layout=L45 path_samples=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu max_grade_deg=%.3f limit_deg=15.000 min_clearance_m=%.3f max_support_depth_m=%.3f status=%s\n", + static_cast(candidate.pathSamples), + static_cast(candidate.capsuleSamples), + static_cast(candidate.blockedCapsuleSamples), + static_cast(candidate.supportSamples), + static_cast(candidate.unsupportedSamples), + static_cast(candidate.gradeFailures), + candidate.maximumGradeDegrees,candidate.minimumClearanceMetres, + candidate.maximumSupportDepthMetres,candidate.passed?"PASS":"FAIL"); + std::printf("[LandmarkReachabilityControl] layout=L43-buried path_samples=%llu blocked=%llu unsupported=%llu grade_failures=%llu result=%s expected=FAIL status=%s\n", + static_cast(buried.pathSamples), + static_cast(buried.blockedCapsuleSamples), + static_cast(buried.unsupportedSamples), + static_cast(buried.gradeFailures), + buried.passed?"PASS":"FAIL",buried.passed?"FAIL":"PASS"); + const bool passed=candidate.passed&&!buried.passed; + std::printf("[LandmarkReachabilityAudit] positive=PASS control=%s status=%s\n", + buried.passed?"FAILED":"EXPECTED_FAIL",passed?"PASS":"FAIL"); + return passed?0:13; + } if(bAuditStrateTransitions) { if(ResolvedLayout==nullptr||!CoreSnapshot.resolvedLayout()) diff --git a/Standalone/Tools/verify/verify.py b/Standalone/Tools/verify/verify.py index 2b13e27..a7ff4a5 100644 --- a/Standalone/Tools/verify/verify.py +++ b/Standalone/Tools/verify/verify.py @@ -152,7 +152,9 @@ def run_owner_passages(runner: VerificationRunner, binaries: tuple[Path, ...]) - compiler = compiler_name(binary) for recipe, style in (("floating-islands", 2), ("surface-world", 1)): name = f"owner-{recipe}-style{style}-{compiler}" + flags = ["--landmark-chamber-demo"] if recipe == "surface-world" else [] row, output = run_passage(runner, binary, name, OWNER, recipe, style, + flags=flags, passage_style=2, expected_strate_styles=expected_strate_styles) result[f"{compiler}:{recipe}"] = (row, output) actual_styles = row.get("resolved_strate_styles", {}) @@ -171,6 +173,62 @@ def run_owner_passages(runner: VerificationRunner, binaries: tuple[Path, ...]) - return result +def run_landmark_reachability(runner: VerificationRunner, + binaries: tuple[Path, ...]) -> None: + for binary in binaries: + compiler = compiler_name(binary) + name = f"landmark-reachability-{compiler}" + row, output = runner.run_cpp(name, binary, OWNER, "surface-world", + ["--landmark-chamber-demo", "--audit-landmark-reachability", + "--max-clip-level=0", "--render-distance-chunks=0"], + expected_style=2, expected_strate_styles={"0": 2, "1": 1}, + max_level=0, layout_path=OUT / "layouts" / f"{name}.json") + row["requested_style"] = 1 + row["expected_passage_style"] = 2 + passage_name = f"landmark-demo-passages-{compiler}" + _, passage_output = runner.run_cpp(passage_name, binary, OWNER, "surface-world", + ["--landmark-chamber-demo", "--audit-resolved-layout-passages", + "--max-clip-level=0", "--render-distance-chunks=0"], + expected_style=2, expected_strate_styles={"0": 2, "1": 1}, + max_level=0, layout_path=OUT / "layouts" / f"{passage_name}.json") + runner.require(f"{passage_name}:demo-enabled", + all_match(passage_output, r"^\[LandmarkStampDemo\] enabled=true .*status=PASS$"), + "landmark demo flag resolved while running passage audits") + validate_passage(runner, passage_name, passage_output) + + interface_name = f"landmark-demo-interfaces-{compiler}" + _, interface_output = runner.run_cpp(interface_name, binary, OWNER, "surface-world", + ["--landmark-chamber-demo", "--audit-strate-transitions", + "--max-clip-level=0", "--render-distance-chunks=0"], + expected_style=2, expected_strate_styles={"0": 2, "1": 1}, + max_level=0, layout_path=OUT / "layouts" / f"{interface_name}.json") + runner.require(f"{interface_name}:demo-enabled", + all_match(interface_output, r"^\[LandmarkStampDemo\] enabled=true .*status=PASS$"), + "landmark demo flag resolved while running interface audits") + runner.require(f"{interface_name}:hole-audit", + all_match(interface_output, r"^\[StrateTransitionAuditSummary\].*hole_columns=0 " + r"air_samples=0 .*status=PASS$"), + "demo-on stratum interfaces contain no holes or air columns") + runner.require(f"{interface_name}:negative-controls", + all_match(interface_output, r"^\[StrateTransitionNegativeControls\].*status=PASS$"), + "demo-on interface audit detects both thickness controls") + runner.require(f"{name}:candidate-walkability", + all_match(output, r"^\[LandmarkWalkability\] layout=L45 .*status=PASS$"), + "demo capsule clearance, support and <=15 degree grade") + runner.require(f"{name}:buried-l43-control", + all_match(output, r"^\[LandmarkReachabilityControl\] layout=L43-buried .*" + r"result=FAIL expected=FAIL status=PASS$"), + "the exact buried L43 chamber fails the same route audit") + runner.require(f"{name}:surface-to-floor-route", + all_match(output, r"^\[LandmarkSurfaceRoute\] reachable=true .*" + r"surface_points=[1-9][0-9]* grade_limit_deg=15\.000$"), + "walkable surface path from ground near the arrival mouth joins the ramp") + runner.require(f"{name}:reachability-control-summary", + all_match(output, r"^\[LandmarkReachabilityAudit\] positive=PASS " + r"control=EXPECTED_FAIL status=PASS$"), + "candidate passes and buried control fails") + + def expected_control_row(runner: VerificationRunner, name: str) -> None: if runner.runs: row = runner.runs[-1] @@ -361,6 +419,8 @@ def run_random_camera_sweep(runner: VerificationRunner, binaries: tuple[Path, .. f"--clip-radius={clip_radius}", f"--max-clip-level={max_clip_level}", f"--max-level={max_level}", "--render-distance-chunks=0"] + if recipe == "surface-world": + flags.append("--landmark-chamber-demo") row, output = runner.run_cpp(name, binary, OWNER, recipe, flags, expected_style=2, expected_strate_styles={"0": 2, "1": 1}, @@ -460,8 +520,11 @@ def run_transitions_and_invariants(runner: VerificationRunner, for recipe, style in (("floating-islands", 2), ("surface-world", 1)): for mode in ("hard", "gradient", "interleaved"): name = f"transition-{recipe}-{mode}-{compiler}" + flags = [f"--transition-mode={mode}", "--audit-strate-transitions"] + if recipe == "surface-world": + flags.append("--landmark-chamber-demo") row, output = runner.run_cpp(name, binary, OWNER, recipe, - [f"--transition-mode={mode}", "--audit-strate-transitions"], + flags, expected_style=2, expected_strate_styles={"0": 2, "1": 1}, layout_path=OUT / "layouts" / f"{name}.json") row["requested_style"] = style @@ -472,8 +535,11 @@ def run_transitions_and_invariants(runner: VerificationRunner, runner.require(f"{name}:gate-{index + 1}", all_match(output, pattern), pattern) name = f"invariant-route-proof-{recipe}-{compiler}" + flags = ["--audit-uniform-block-proofs", "--audit-route-parity"] + if recipe == "surface-world": + flags.append("--landmark-chamber-demo") row, output = runner.run_cpp(name, binary, OWNER, recipe, - ["--audit-uniform-block-proofs", "--audit-route-parity"], + flags, expected_style=2, expected_strate_styles={"0": 2, "1": 1}, layout_path=OUT / "layouts" / f"{name}.json") row["requested_style"] = style @@ -545,6 +611,18 @@ def run_captures_and_mesh_audits(runner: VerificationRunner) -> None: script_inputs=[overview_script, VERIFY_DIR / "capture_quality.py", OUT / "owner-fi-ground-mouth.vfmesh"]) + landmark_script = ROOT / "Standalone/Research/WorldAssemblerM3/Linux45/CaptureLandmarkL45.py" + landmark_layout = OUT / "layouts" / "landmark-reachability-clang.json" + runner.run_stage("capture-landmark-l45", landmark_script, + ["--layout", str(landmark_layout), "--artifact-dir", str(OUT), + "--capture-dir", str(ROOT / "Standalone/Research/WorldAssemblerM3/Linux45/captures"), + "--data", str(OWNER)], + binary=CLANG, data=OWNER, recipe="surface-world", style=1, + layout_path=landmark_layout, env=env_for(capture=True), + script_inputs=[landmark_script, VERIFY_DIR / "capture_quality.py", + LINUX37 / "AuditMouthShapeL37.py", + ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"]) + def verify_determinism_cell(runner: VerificationRunner, binary: Path, kind: str, recipe: str, style: int, workers: int, @@ -718,6 +796,7 @@ def run_cost_pairs(runner: VerificationRunner) -> None: def run_medium_suite(runner: VerificationRunner, *, both_compilers: bool = False) -> None: binaries = (CLANG, GCC) if both_compilers else (CLANG,) run_owner_passages(runner, binaries) + run_landmark_reachability(runner, binaries) run_regression_controls(runner) style_override_matrix(runner, CLANG) if both_compilers: @@ -732,6 +811,7 @@ def run_medium_suite(runner: VerificationRunner, *, both_compilers: bool = False def run_gcc_supplement(runner: VerificationRunner) -> None: """Add GCC evidence to a stamped, already-passed Clang medium tier.""" run_owner_passages(runner, (GCC,)) + run_landmark_reachability(runner, (GCC,)) style_override_matrix(runner, GCC) run_transitions_and_invariants(runner, (GCC,)) @@ -756,6 +836,7 @@ def main() -> int: runner.probe_binary(binary) if ARGS.tier == "fast": owners = run_owner_passages(runner, (CLANG,)) + run_landmark_reachability(runner, (CLANG,)) run_regression_controls(runner) run_hash_pairs(runner, owners) run_random_camera_sweep(runner, (CLANG,), 3, ((3, 0, 0),))