diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/PassageEnvelopeAudit.py b/Standalone/Research/WorldAssemblerM3/Linux159/PassageEnvelopeAudit.py new file mode 100644 index 0000000..90712da --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux159/PassageEnvelopeAudit.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python3 +"""Audit cave primitive bounds against passage envelopes, allowing chamber joints only.""" +from __future__ import annotations + +import math +from typing import Any + + +INFLUENCE_REACH_M = 1.053334 +SHELL_THICKNESS_M = 0.75 + + +def _point(control: dict[str, Any], scale: float) -> tuple[float, float, float, float]: + x, y, z = control["position_q"] + return (x * scale, y * scale, z * scale, control["radius_q"] * scale) + + +def _knot_span(a, b) -> float: + return max(1.0e-6, math.sqrt(math.sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2))) + + +def _evaluate(controls, span: int, t: float): + p1, p2 = controls[span], controls[span + 1] + p0 = controls[span - 1] if span else controls[0] + p3 = controls[min(span + 2, len(controls) - 1)] + dt0, dt1, dt2 = _knot_span(p0, p1), _knot_span(p1, p2), _knot_span(p2, p3) + t2, t3 = t * t, t * t * t + result = [] + for axis in (0, 1, 3): + m1 = dt1 * ((p1[axis] - p0[axis]) / dt0 + - (p2[axis] - p0[axis]) / (dt0 + dt1) + + (p2[axis] - p1[axis]) / dt1) + m2 = dt1 * ((p2[axis] - p1[axis]) / dt1 + - (p3[axis] - p1[axis]) / (dt1 + dt2) + + (p3[axis] - p2[axis]) / dt2) + value = ((2 * t3 - 3 * t2 + 1) * p1[axis] + + (-2 * t3 + 3 * t2) * p2[axis] + + (t3 - 2 * t2 + t) * m1 + (t3 - t2) * m2) + result.append(value) + radius = min(max(result[2], min(p1[3], p2[3])), max(p1[3], p2[3])) + return result[0], result[1], p1[2] + (p2[2] - p1[2]) * t, radius + + +def _segments(passage: dict[str, Any], scale: float): + controls = [_point(row, scale) for row in passage["control_points"]] + route_end = min(max(1, int(passage.get("lower_mouth_control_index", len(controls) - 1))), + len(controls) - 1) + along = 0.0 + for span in range(route_end): + a, b = controls[span], controls[span + 1] + chord = math.hypot(b[0] - a[0], b[1] - a[1]) + steps = max(1, math.ceil(chord / 2.0)) + prior = _evaluate(controls, span, 0.0) + for step in range(1, steps + 1): + current = _evaluate(controls, span, step / steps) + length = math.dist(prior[:3], current[:3]) + yield prior, current, along, max(prior[3], current[3]) + INFLUENCE_REACH_M + along += length + prior = current + + +def _distance_squared(a, b, bounds, t: float) -> float: + total = 0.0 + for axis in range(3): + pos = a[axis] + (b[axis] - a[axis]) * t + lo, hi = bounds[axis] + gap = lo - pos if pos < lo else pos - hi if pos > hi else 0.0 + total += gap * gap + return total + + +def _overlap_interval(a, b, bounds, radius: float): + cuts = {0.0, 1.0} + for axis in range(3): + delta = b[axis] - a[axis] + if abs(delta) < 1.0e-15: + continue + for edge in bounds[axis]: + t = (edge - a[axis]) / delta + if 0.0 < t < 1.0: + cuts.add(t) + cuts = sorted(cuts) + best_t, best_d2 = 0.0, math.inf + for lo, hi in zip(cuts, cuts[1:]): + mid = (lo + hi) * 0.5 + quadratic = linear = 0.0 + for axis in range(3): + delta = b[axis] - a[axis] + pos = a[axis] + delta * mid + low, high = bounds[axis] + edge = low if pos < low else high if pos > high else None + if edge is not None: + quadratic += delta * delta + linear += delta * (a[axis] - edge) + candidate = min(hi, max(lo, -linear / quadratic)) if quadratic else lo + d2 = _distance_squared(a, b, bounds, candidate) + if d2 < best_d2: + best_t, best_d2 = candidate, d2 + for t in (0.0, 1.0): + d2 = _distance_squared(a, b, bounds, t) + if d2 < best_d2: + best_t, best_d2 = t, d2 + if best_d2 > radius * radius: + return None + + radius2 = radius * radius + if _distance_squared(a, b, bounds, 0.0) <= radius2: + left = 0.0 + else: + low, high = 0.0, best_t + for _ in range(42): + mid = (low + high) * 0.5 + if _distance_squared(a, b, bounds, mid) <= radius2: + high = mid + else: + low = mid + left = high + if _distance_squared(a, b, bounds, 1.0) <= radius2: + right = 1.0 + else: + low, high = best_t, 1.0 + for _ in range(42): + mid = (low + high) * 0.5 + if _distance_squared(a, b, bounds, mid) <= radius2: + low = mid + else: + high = mid + right = low + return left, right, best_t + + +def _inside_chamber(point, chamber: dict[str, Any], scale: float) -> bool: + cx, cy, cz = (value * scale for value in chamber["floor_center_q"]) + radius = chamber["radius_q"] * scale + clear = chamber["clear_height_q"] * scale + gx, gy = (value / 65536.0 for value in chamber["floor_gradient_q16"]) + floor = cz + gx * (point[0] - cx) + gy * (point[1] - cy) + chamber_distance = max(math.hypot(point[0] - cx, point[1] - cy) - radius, + floor - point[2], point[2] - (floor + clear)) + return chamber_distance <= SHELL_THICKNESS_M + + +def _primitive_bounds(row: dict[str, Any]): + return tuple((row["bounds_min_um"][i] / 1.0e6, + row["bounds_max_um"][i] / 1.0e6) for i in range(3)) + + +def _landing_content_band_audit(layout: dict[str, Any]) -> list[dict[str, Any]]: + """Recheck the validator's complete graded floor, headroom and shell envelope.""" + voxel_um = int(layout["core_voxel_size_um"]) + q_per_metre = 256 * 1_000_000 / voxel_um + strata = {int(row["strate_index"]): row for row in layout.get("strates", [])} + + def q_to_um(q: int) -> int: + numerator = q * voxel_um + return numerator // 256 if numerator >= 0 else -((-numerator) // 256) + + result = [] + shell_q = math.floor(0.75 * q_per_metre + 0.5) + for passage in layout.get("passages", []): + for side, key, strate_key in ( + ("upper", "upper_landing_chamber", "upper_strate_index"), + ("lower", "lower_landing_chamber", "lower_strate_index")): + chamber = passage[key] + host = strata.get(int(passage[strate_key])) + content = host.get("content", {}) if host else {} + gradient = chamber["floor_gradient_q16"] + gradient_magnitude = math.isqrt(gradient[0] ** 2 + gradient[1] ** 2) + plane_reach_q = (int(chamber["radius_q"]) * gradient_magnitude + 65535) // 65536 + floor_q = int(chamber["floor_center_q"][2]) + minimum_um = q_to_um(floor_q - plane_reach_q - shell_q) + maximum_um = q_to_um(floor_q + plane_reach_q + + int(chamber["clear_height_q"]) + shell_q) + band_bottom_um, band_top_um = int(content.get("bottom_um", 0)), int( + content.get("top_um", 0)) + passed = bool(content.get("present", False) + and minimum_um >= band_bottom_um + and maximum_um <= band_top_um) + result.append({ + "passage": passage["stable_id"], "side": side, + "host_strate": int(passage[strate_key]), + "minimum_um": minimum_um, "maximum_um": maximum_um, + "content_bottom_um": band_bottom_um, "content_top_um": band_top_um, + "bottom_margin_m": (minimum_um - band_bottom_um) / 1_000_000.0, + "top_margin_m": (band_top_um - maximum_um) / 1_000_000.0, + "passed": passed, + }) + return result + + +def _bezier(points, t: float): + u = 1.0 - t + return tuple(u**3 * points[0][axis] + 3*u*u*t * points[1][axis] + + 3*u*t*t * points[2][axis] + t**3 * points[3][axis] + for axis in range(3)) + + +def _distance_to_ellipse(radial: float, vertical: float, + radius: float, height: float) -> float: + """Euclidean distance to the edge bore's horizontal/vertical ellipse.""" + radial = abs(radial) + vertical = abs(vertical) + if (radial / radius) ** 2 + (vertical / height) ** 2 <= 1.0: + return 0.0 + + lo, hi = 0.0, math.pi / 2.0 + ratio = (math.sqrt(5.0) - 1.0) / 2.0 + + def squared_distance(angle: float) -> float: + return ((radial - radius * math.cos(angle)) ** 2 + + (vertical - height * math.sin(angle)) ** 2) + + left = hi - ratio * (hi - lo) + right = lo + ratio * (hi - lo) + left_value, right_value = squared_distance(left), squared_distance(right) + for _ in range(48): + if left_value < right_value: + hi, right, right_value = right, left, left_value + left = hi - ratio * (hi - lo) + left_value = squared_distance(left) + else: + lo, left, left_value = left, right, right_value + right = lo + ratio * (hi - lo) + right_value = squared_distance(right) + return math.sqrt(min(left_value, right_value)) + + +def _actual_edge_clearance(passage, edge, scale: float, + chamber: dict[str, Any]) -> float: + """Return minimum true tube clearance outside the landing chamber. + + The route edge is a swept ellipse, with its recorded polyline as the axis. + Comparing the tunnel tube to that ellipse avoids the false clashes caused by + intersecting two enclosing boxes. + """ + controls = [tuple(value / 1.0e6 for value in point) + for point in edge["control_points_um"]] + axis = [tuple(value / 1.0e6 for value in point) + for point in edge.get("polyline_um", [])] + if len(axis) < 2: + axis = [_bezier(controls, index / 64.0) for index in range(65)] + edge_radius = (edge["bore_radius_um"] + + edge["irregularity_amplitude_um"]) / 1.0e6 + edge_height = (edge["bore_height_um"] + + edge["irregularity_amplitude_um"]) / 1.0e6 + passage_segments = passage.get("_audit_segments") or tuple(_segments(passage, scale)) + minimum_clearance = math.inf + for a, b, _, passage_radius in passage_segments: + length = math.dist(a[:3], b[:3]) + steps = max(1, math.ceil(length / 0.05)) + for step in range(steps + 1): + t = step / steps + point = tuple(a[i] + (b[i] - a[i]) * t for i in range(3)) + if _inside_chamber(point, chamber, scale): + continue + tunnel_radius = (a[3] + (b[3] - a[3]) * t + + INFLUENCE_REACH_M) + for start, end in zip(axis, axis[1:]): + delta = tuple(end[i] - start[i] for i in range(3)) + length_squared = sum(value * value for value in delta) + projection = (sum((point[i] - start[i]) * delta[i] + for i in range(3)) / length_squared + if length_squared else 0.0) + projection = min(1.0, max(0.0, projection)) + center = tuple(start[i] + projection * delta[i] for i in range(3)) + radial = math.hypot(point[0] - center[0], point[1] - center[1]) + vertical = abs(point[2] - center[2]) + clearance = (_distance_to_ellipse(radial, vertical, + edge_radius, edge_height) + - tunnel_radius) + minimum_clearance = min(minimum_clearance, clearance) + return minimum_clearance + + +def _test_bounds(bounds, strate_index: int, passages, scale: float, + *, allow_chamber: bool = True): + for passage in passages: + if strate_index not in (passage["upper_strate_index"], passage["lower_strate_index"]): + continue + chamber_key = ("upper_landing_chamber" if strate_index == passage["upper_strate_index"] + else "lower_landing_chamber") + chamber = passage[chamber_key] + segments = passage.get("_audit_segments") + if segments is None: + segments = tuple(_segments(passage, scale)) + for a, b, along, radius in segments: + expanded = [(a[i] - radius, b[i] + radius) if a[i] <= b[i] + else (b[i] - radius, a[i] + radius) for i in range(3)] + if any(expanded[i][1] < bounds[i][0] or expanded[i][0] > bounds[i][1] + for i in range(3)): + continue + overlap = _overlap_interval(a, b, bounds, radius) + if overlap is None: + continue + first, last, closest = overlap + if allow_chamber: + points = [tuple(a[i] + (b[i] - a[i]) * t for i in range(3)) + for t in (first, closest, last)] + if all(_inside_chamber(point, chamber, scale) for point in points): + continue + point = tuple(a[i] + (b[i] - a[i]) * closest for i in range(3)) + return passage["stable_id"], along + closest * math.dist(a[:3], b[:3]), point + return None + + +def audit_layout(layout: dict[str, Any]) -> dict[str, Any]: + scale = layout["core_voxel_size_um"] / (256.0 * 1.0e6) + passages = [dict(row, _audit_segments=tuple(_segments(row, scale))) + for row in layout.get("passages", [])] + primitives = [*layout.get("cavern_rooms", []), *layout.get("cavern_edges", [])] + checked = conflicts = chamber_joints = bounds_candidates = refined_false_positives = 0 + first_conflict = None + for primitive in primitives: + bounds = _primitive_bounds(primitive) + checked += 1 + primitive_conflicted = False + for passage in passages: + found = _test_bounds(bounds, primitive["strate_index"], [passage], scale) + if not found: + continue + bounds_candidates += 1 + passage_id, station, point = found + if "control_points_um" in primitive: + side = ("upper_landing_chamber" + if primitive["strate_index"] == passage["upper_strate_index"] + else "lower_landing_chamber") + clearance = _actual_edge_clearance(passage, primitive, scale, + passage[side]) + if clearance >= 0.0: + refined_false_positives += 1 + continue + primitive_conflicted = True + conflicts += 1 + if first_conflict is None: + first_conflict = {"primitive": primitive["stable_id"], + "passage": passage_id, "station_m": round(station, 3), + "center_m": [round(value, 3) for value in point]} + break + if not primitive_conflicted: + # Count the edge's named landing association as a confirmed joint. + anchor = primitive.get("start_anchor_stable_id", "") + if anchor.endswith(("/upper-landing-chamber", "/lower-landing-chamber")): + chamber_joints += 1 + + negative_controls = 0 + long_passage = max(passages, key=lambda row: len(row["control_points"]), default=None) + if long_passage: + controls = [_point(row, scale) for row in long_passage["control_points"]] + index = min(max(1, len(controls) // 2), len(controls) - 2) + center = controls[index] + bounds = tuple((center[axis] - 0.25, center[axis] + 0.25) for axis in range(3)) + # Select the side whose chamber is farther from this forced tunnel overlap. + strate = long_passage["lower_strate_index"] + if _test_bounds(bounds, strate, [long_passage], scale, allow_chamber=False): + negative_controls = 1 + landing_content_band = _landing_content_band_audit(layout) + landing_content_band_passed = bool(landing_content_band) and all( + row["passed"] for row in landing_content_band) + return {"primitives_checked": checked, "bounds_candidates": bounds_candidates, + "refined_edge_false_positives": refined_false_positives, + "outside_chamber_conflicts": conflicts, + "chamber_joints": chamber_joints, "negative_controls": negative_controls, + "negative_controls_expected": 1, "first_conflict": first_conflict, + "landing_content_band": landing_content_band, + "landing_content_band_passed": landing_content_band_passed, + "passed": conflicts == 0 and negative_controls == 1 + and landing_content_band_passed} diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux159/REPORT.md new file mode 100644 index 0000000..e98e6ea --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux159/REPORT.md @@ -0,0 +1,82 @@ +# L159 — pad stamp only + +The stamp is implemented and built as evaluator v240, with no planner edits. All 12 captures passed image quality, but the seed-0 fast tier and both 16-seed composed sweeps failed; no baselines were added. + +## Seed-0 tunnel-end captures + +Each row matches the L151 eye-height pose and adds a pulled-back L159 wide view. + +| Tunnel end | Before (L151) | After, eye height (L159) | After, wide (L159) | +|---|---|---|---| +| `passage/0-1/0` upper | ![L151 upper](../Linux151/captures/after-l151-0-1-0-upper-landing.jpg) | ![L159 upper eye](captures/after-l159-0-1-0-upper-landing.jpg) | ![L159 upper wide](captures/after-l159-0-1-0-upper-wide.jpg) | +| `passage/0-1/0` lower | ![L151 lower](../Linux151/captures/after-l151-0-1-0-lower-landing.jpg) | ![L159 lower eye](captures/after-l159-0-1-0-lower-landing.jpg) | ![L159 lower wide](captures/after-l159-0-1-0-lower-wide.jpg) | +| `passage/1-2/0` upper | ![L151 upper](../Linux151/captures/after-l151-1-2-0-upper-landing.jpg) | ![L159 upper eye](captures/after-l159-1-2-0-upper-landing.jpg) | ![L159 upper wide](captures/after-l159-1-2-0-upper-wide.jpg) | +| `passage/1-2/0` lower | ![L151 lower](../Linux151/captures/after-l151-1-2-0-lower-landing.jpg) | ![L159 lower eye](captures/after-l159-1-2-0-lower-landing.jpg) | ![L159 lower wide](captures/after-l159-1-2-0-lower-wide.jpg) | +| `passage/2-3/0` upper | ![L151 upper](../Linux151/captures/after-l151-2-3-0-upper-landing.jpg) | ![L159 upper eye](captures/after-l159-2-3-0-upper-landing.jpg) | ![L159 upper wide](captures/after-l159-2-3-0-upper-wide.jpg) | +| `passage/2-3/0` lower | ![L151 lower](../Linux151/captures/after-l151-2-3-0-lower-landing.jpg) | ![L159 lower eye](captures/after-l159-2-3-0-lower-landing.jpg) | ![L159 lower wide](captures/after-l159-2-3-0-lower-wide.jpg) | + +Capture quality: **12/12 PASS**. Poses use the seed-0 layout resolved for L151; the six after-eye views reuse those camera poses. The image manifest and capture commands are in [captures/INDEX.md](captures/INDEX.md) and [RunL159Captures.py](RunL159Captures.py). + +## Change + +- Added a last-step stamp to both `ApplyResolvedStrateComposition` branches. It uses the existing landing-chamber floor center as P, radius `max(3 m, mouth radius + 1 m)`, fill depth 1.25 m, headroom 3 m, 12° cones, and an 8 m blend reach. The tube-only distance mask leaves the bore open; the smooth-min width rises to 0.5 m over the first metre beyond the flat radius. +- Removed the landing chamber box/shell composition cases. Tunnel endpoints and planner output were left unchanged. +- Extended tile feature bounds, proof bounds, fallback overlap bounds, and the proof-miss sampler for the pad influence. +- Added `LandingPadAudit` for floor flatness, support, headroom, blend grade, and four negative controls. +- Bumped `WorldAssemblyEvaluatorVersion` from 239 to 240. + +No Unreal `Source/` files changed. Changed Standalone files: [ShapeWorldInternal.h](../../../Core/src/ShapeWorldInternal.h), [VoxelForgeCore.h](../../../Core/include/VoxelForgeCore/VoxelForgeCore.h), and [StreamBenchmarkPipeline.h](../../../Source/StreamBenchmarkPipeline.h). Planner files are unchanged. + +## Gates + +| Gate | Tunnel Network | Vertical Shafts | Result detail | +|---|---:|---:|---| +| Seed-0 fast tier | FAIL | — | Stopped before composition: `owner-floating-islands-style2-clang` rejected because `passage/0-1/0 lower landing chamber does not fit inside its host content band` (exit 2, 0.465 s). | +| Combined composed run | 0/16 PASS | 0/16 PASS | Each seed returned failure from the resolved passage audit. | +| Pad audit | 0/16 seeds PASS | 0/16 seeds PASS | 8/96 endpoints passed per mode. Across the 96 pads: 49 had flatness misses (369 samples total; worst reported variation 0.74946 m), 75 had blend-grade misses (2,072 samples; max 80.754°), support was 9,384/10,368 probes, and headroom passed on 93/96 pads. There were 1,682 missing blend crossings. All four negative controls were detected on every pad: 384/384 per mode. | +| Route plan grade | 16/16 PASS | 16/16 PASS | Planned grades stayed within the 15° limit. | +| Selected-stratum route audit | 0/16 PASS | 1/16 PASS | Vertical Shafts passed only seed 2; its selected route had 0 blocked or unsupported samples. Tunnel Network failed all 16: blocked samples were present on 15 seeds and unsupported samples on 13. The full composed passage walkability summary still failed 16/16 in each mode; `route_walkable` was 0 on 15 seeds and 2 on seed 2. | +| Interior air | 0/16 PASS | 0/16 PASS | Every seed had solid capsule samples (839–1,588 per seed). | +| Shell | 11/16 PASS | 11/16 PASS | Failed seeds 2, 3, 4, 5, and 14: 2,571 floor violations and 570 wall violations in total. Other shell samples passed. | +| Ripple | 5/16 PASS | 5/16 PASS | Passing seeds: 3, 7, 11, 12, and 14. | +| Layout envelope | 8/16 PASS | 8/16 PASS | Conflicts on seeds 1, 4, 6, 7, 8, 10, 13, and 15, on existing route-edge, room, or shaft primitives near passage endpoints. | +| Tile seams | NOT REACHED, 0/16 rows | NOT REACHED, 0/16 rows | The command included `--audit-passage-tile-seams`, but every run returned from the failed passage summary before reaching the seam-audit block. Seam behavior is therefore unverified. | +| Pad proof/feature bounds (L130 sampler) | 16/16 PASS | 16/16 PASS | Per mode: 413,952 sampled points, 305,571 sign-relevant; zero proof misses and zero tile-feature misses. | +| Mode blocked-route control | 16/16 PASS | 16/16 PASS | Deliberately blocked controls were detected as expected. | + +Per-seed detail: pad `F/G` is the number of endpoints with any flatness/blend-grade failure; route `B/U` is the selected-stratum blocked/unsupported sample count. Shell `F (x/y)` gives floor/wall violations. + +| Seed | Pads passed | Pad F/G | Tunnel Network B/U | Vertical Shafts B/U | Interior solids | Shell | Ripple | Envelope | +|---:|---:|---:|---:|---:|---:|---:|:---:|:---:| +| 0 | 1/6 | 3/5 | 324/12 | 507/0 | 1,233 | PASS | FAIL | PASS | +| 1 | 1/6 | 3/4 | 190/0 | 157/0 | 839 | PASS | FAIL | FAIL | +| 2 | 1/6 | 3/4 | 159/2 | 0/0 | 915 | FAIL (513/110) | FAIL | PASS | +| 3 | 1/6 | 3/4 | 0/14 | 521/0 | 1,588 | FAIL (522/117) | PASS | PASS | +| 4 | 0/6 | 3/5 | 140/0 | 222/0 | 855 | FAIL (512/114) | FAIL | FAIL | +| 5 | 0/6 | 3/4 | 152/15 | 379/0 | 892 | FAIL (516/116) | FAIL | PASS | +| 6 | 0/6 | 3/6 | 436/24 | 495/0 | 1,350 | PASS | FAIL | FAIL | +| 7 | 1/6 | 3/5 | 299/5 | 374/0 | 1,011 | PASS | PASS | FAIL | +| 8 | 1/6 | 3/4 | 177/15 | 541/0 | 1,079 | PASS | FAIL | FAIL | +| 9 | 0/6 | 3/4 | 325/16 | 540/0 | 1,205 | PASS | FAIL | PASS | +| 10 | 1/6 | 3/5 | 171/16 | 385/0 | 921 | PASS | FAIL | FAIL | +| 11 | 0/6 | 3/5 | 150/30 | 537/0 | 1,025 | PASS | PASS | PASS | +| 12 | 1/6 | 3/4 | 266/4 | 353/0 | 1,161 | PASS | PASS | PASS | +| 13 | 0/6 | 4/6 | 153/15 | 376/0 | 1,146 | PASS | FAIL | FAIL | +| 14 | 0/6 | 3/4 | 277/18 | 386/0 | 1,075 | FAIL (508/113) | PASS | PASS | +| 15 | 0/6 | 3/6 | 173/0 | 265/0 | 970 | PASS | FAIL | FAIL | + +Full per-seed records: [Tunnel Network summary](../../../../Saved/Linux/l159/seeds/tunnel-network/summary.json) and [Vertical Shafts summary](../../../../Saved/Linux/l159/seeds/vertical-shafts/summary.json). + +The pad, route, shell, interior-air, ripple, and envelope results are composition audits, one run per seed 0–15 with `--workers=8 --clip-radius=3 --max-clip-level=8 --max-level=0 --force-generate --recipe=surface-world --audit-resolved-layout-passages --audit-passage-tile-seams` and the same source stamp (`5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640`). Both modes produced the same shared-field results. The selected-stratum route row and blocked-route control are mode-specific. + +The seed runner recorded 6,711.2 s summed child wall time for Tunnel Network and 6,762.5 s for Vertical Shafts (monotonic windows from each seed process launch to exit). These are sweep execution windows, not performance benchmark results; no L80 timing was taken. + +## Checks and remaining risks + +- Build: `bash Standalone/BuildLinux.sh --compiler=clang++-19 --variant=l159-clang --fp-contract=off --jobs=4` — passed. +- Captures: `RunL159Captures.py` — 12 views, 0 quality failures. +- Fast tier: `VOXELFORGE_L130_PASSAGE_REACH_PROOF_AUDIT=1 python3 Standalone/Tools/verify/verify.py fast --out Saved/Linux/l159 --overall-timeout 1740` — failed at the owner floating-islands fixture before composition. +- Composed sweeps: `RunL159SeedSweep.py --kind tunnel-network` and `--kind vertical-shafts`, seeds 0–15, with `VOXELFORGE_L130_PASSAGE_REACH_PROOF_AUDIT=1` — completed, all aggregate gates failed. +- `git diff --check` passed before the final report was written. No Unreal build or timing benchmark was run. + +The pad flatness, support, headroom, and blend gates remain open, and all composed passage summaries fail. Shell failures are seed-dependent. The proof-tree and tile-feature bound sampler passed; fallback overlap bounds were extended but have no separate standalone audit. Seam behavior remains unverified because the process exits before that gate. No baselines were added because the requested gates did not all pass. diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/RunL159Captures.py b/Standalone/Research/WorldAssemblerM3/Linux159/RunL159Captures.py new file mode 100644 index 0000000..51a699d --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux159/RunL159Captures.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""Capture matched L151 eye views and L159 wide views of all six owner pads.""" +from __future__ import annotations + +import json +import math +import os +from pathlib import Path +import re +import subprocess +import sys + + +ROOT = Path(__file__).resolve().parents[4] +DATA = ROOT / "Standalone/data/owner_world.json" +OUT = ROOT / "Saved/Linux/l159/captures" +LAYOUT = ROOT / "Saved/Linux/l151/captures/after-resolved-layout.json" +ARTIFACTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux159/captures" +HEADLESS = ROOT / "Saved/Linux/l159/bin/VoxelForgeCoreHeadless-l159-clang" +RENDERER = ROOT / "Saved/Linux/l159/bin/VoxelForgeOffscreenCapture-l159-clang" +STAMP = "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640" +LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"] +STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", re.M) + + +def fmt(values) -> str: + return ",".join(f"{value:.6f}" for value in values) + + +def angles(camera, target) -> tuple[float, float]: + 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 point_q(layout: dict, values) -> tuple[float, float, float]: + scale = layout["core_voxel_size_um"] / (256.0 * 1_000_000.0) + return tuple(value * scale for value in values) + + +def chamber_views(layout: dict) -> list[dict]: + result = [] + for passage in layout["passages"]: + controls = passage["control_points"] + for side, endpoint, neighbor in ( + ("upper", controls[0], controls[1]), + ("lower", controls[-1], controls[-2])): + chamber = passage[f"{side}_landing_chamber"] + center = point_q(layout, chamber["floor_center_q"]) + a = point_q(layout, neighbor["position_q"]) + b = point_q(layout, endpoint["position_q"]) + dx, dy = a[0] - b[0], a[1] - b[1] + length = math.hypot(dx, dy) + if length <= 1.0e-9: + raise ValueError(f"degenerate {side} tunnel tangent: {passage['stable_id']}") + dx, dy = dx / length, dy / length + eye_z = center[2] + 1.45 + distance = 9.0 + target = (center[0] + dx * distance, center[1] + dy * distance, + eye_z + (chamber["floor_gradient_q16"][0] / 65536.0 * dx + + chamber["floor_gradient_q16"][1] / 65536.0 * dy) + * distance) + camera = (center[0], center[1], eye_z) + slug = passage["stable_id"].replace("passage/", "").replace("/", "-") + result.append({"name": f"{slug}-{side}-landing", "passage": passage["stable_id"], + "side": side, "view_kind": "eye", "camera": camera, + "target": target, "focus": (center[0], center[1], eye_z, 18.0)}) + wide_camera = (center[0] - dx * 14.0, center[1] - dy * 14.0, + center[2] + 5.5) + wide_target = (center[0] + dx * 2.0, center[1] + dy * 2.0, + center[2] + 1.25) + result.append({"name": f"{slug}-{side}-wide", "passage": passage["stable_id"], + "side": side, "view_kind": "wide", "camera": wide_camera, + "target": wide_target, + "focus": (center[0], center[1], eye_z, 42.0)}) + return result + + +def stamped_run(name: str, command: list[object], log: Path, env: dict[str, str]) -> str: + probe = subprocess.run([*LIMIT, "timeout", "15s", str(command[0]), "--print-source-stamp"], + cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, check=False, timeout=20) + match = STAMP_RE.search(probe.stdout or "") + if probe.returncode or not match or match.group(1) != STAMP: + raise RuntimeError(f"{name}: source stamp mismatch for {command[0]}") + log.parent.mkdir(parents=True, exist_ok=True) + with log.open("w", encoding="utf-8") as stream: + result = subprocess.run([*LIMIT, "timeout", "1740s", *map(str, command)], + cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT, + timeout=1745, check=False) + output = log.read_text(encoding="utf-8", errors="replace") + emitted = STAMP_RE.search(output) + if result.returncode or not emitted or emitted.group(1) != STAMP: + raise RuntimeError(f"{name}: export or render failed; see {log}") + return output + + +def main() -> int: + if not DATA.is_file() or not HEADLESS.is_file() or not RENDERER.is_file(): + raise SystemExit("missing owner fixture or L159 Clang capture binaries") + OUT.mkdir(parents=True, exist_ok=True) + ARTIFACTS.mkdir(parents=True, exist_ok=True) + env = os.environ.copy() + env["LP_NUM_THREADS"] = "12" + env["XDG_CACHE_HOME"] = str(OUT / "xdg-cache") + layout = json.loads(LAYOUT.read_text(encoding="utf-8")) + if len(layout.get("passages", [])) != 3: + raise RuntimeError("owner layout did not resolve the expected three tunnels") + + quality_tool = ROOT / "Standalone/Tools/verify/capture_quality.py" + manifest = [] + failures = [] + for view in chamber_views(layout): + camera, target = view["camera"], view["target"] + yaw, pitch = angles(camera, target) + mesh_dir = OUT / "meshes" + mesh_dir.mkdir(parents=True, exist_ok=True) + mesh = mesh_dir / f"{view['name']}.vfmesh" + capture_layout = mesh_dir / f"{view['name']}-layout.json" + export = [HEADLESS, DATA, "--world-assembler-prototype", "--stream-benchmark", + "--stream-pipeline", "--workers=12", "--clip-radius=6", "--max-clip-level=0", + "--max-level=0", "--force-generate", "--seed=0", "--recipe=surface-world", + "--render-distance-chunks=0", f"--camera={fmt((*camera, yaw, pitch))}", + f"--resolved-layout-output={capture_layout}", f"--mesh-export={mesh}"] + stamped_run(f"l159-{view['name']}-export", export, + OUT / f"{view['name']}-export.log", env) + if not mesh.is_file() or mesh.stat().st_size == 0: + raise RuntimeError(f"{view['name']}: empty mesh export") + png = OUT / f"{view['name']}.png" + jpg = ARTIFACTS / f"after-l159-{view['name']}.jpg" + render = [RENDERER, mesh, png, "--size=1280x720", "--fov-deg=78.0", + f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}", + f"--pitch-deg={pitch:.6f}", "--tile-skirts=off", "--shade=readable", + "--headlamp=on", "--height-tint=on", "--height-center-m=-950", + "--height-range-m=60", "--tile-edges=off", + f"--focus-region={fmt(view['focus'])}"] + stamped_run(f"l159-{view['name']}-render", render, + OUT / f"{view['name']}-render.log", env) + subprocess.run([*LIMIT, "timeout", "120s", "ffmpeg", "-nostdin", + "-hide_banner", "-loglevel", "error", "-y", "-i", png, + "-frames:v", "1", "-q:v", "4", jpg], cwd=ROOT, env=env, + check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, timeout=125) + check = subprocess.run([*LIMIT, "timeout", "120s", sys.executable, + quality_tool, jpg], cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, check=False, timeout=125) + quality_ok = check.returncode == 0 and "status=PASS" in check.stdout + if not quality_ok: + failures.append(view["name"]) + quality_line = (check.stdout.strip().splitlines()[-1] + if check.stdout.strip() else f"quality exit={check.returncode}") + manifest.append({"version": "after-l159", "view": view["name"], + "passage": view["passage"], "side": view["side"], + "view_kind": view["view_kind"], + "file": str(jpg.relative_to(ROOT)), "camera_m": camera, + "target_m": target, "yaw_deg": yaw, "pitch_deg": pitch, + "source_stamp": STAMP, "pose_layout_identity": layout.get("stable_identity"), + "quality_status": "PASS" if quality_ok else "FAIL", "quality": quality_line}) + print(f"[L159Capture] view={view['name']} quality={'PASS' if quality_ok else 'FAIL'} " + f"file={jpg.relative_to(ROOT)}", flush=True) + + (ARTIFACTS / "manifest.json").write_text( + json.dumps(manifest, indent=2) + "\n", encoding="utf-8") + with (ARTIFACTS / "INDEX.md").open("w", encoding="utf-8") as index: + index.write("# L159 owner landing pad captures\n\n" + "All poses use the seed 0 layout resolved for L151. Eye-height after views " + "reuse the six L151 cameras; each pad also has one pulled-back wide view.\n\n") + for view in chamber_views(layout): + image = f"after-l159-{view['name']}.jpg" + if view["view_kind"] == "eye": + before = f"after-l151-{view['name']}.jpg" + index.write(f"## {view['passage']} / {view['side']} landing\n\n" + f"- Before: [{before}](../../Linux151/captures/{before})\n" + f"- After: [eye view]({image})\n") + else: + index.write(f"- After: [wide view]({image})\n\n") + index.write(f"Capture quality failures: {len(failures)}/{len(manifest)}.\n") + print(f"[L159CaptureSummary] views={len(manifest)} " + f"quality_failures={len(failures)}", flush=True) + return 0 if not failures else 1 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except Exception as error: + print(f"[L159CaptureFailure] {error}", file=sys.stderr, flush=True) + raise SystemExit(1) diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/RunL159SeedSweep.py b/Standalone/Research/WorldAssemblerM3/Linux159/RunL159SeedSweep.py new file mode 100644 index 0000000..3b7ebf2 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux159/RunL159SeedSweep.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +"""Run the L159 landing-pad, passage, and seam gates across 16 owner seeds.""" +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import time +from pathlib import Path + +from PassageEnvelopeAudit import audit_layout + + +ROOT = Path(__file__).resolve().parents[4] +OUT = ROOT / "Saved/Linux/l159/seeds" +BIN = ROOT / "Saved/Linux/l159/bin/VoxelForgeCoreHeadless-l159-clang" +DATA = ROOT / "Standalone/data/owner_world.json" +STAMP = "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640" +STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]+)$", re.M) +DISPATCH = { + "tunnel-network": re.compile( + r"^\[DensityDispatch\] strate=2 generator=TunnelNetwork " + r"recipe=Core\.Caves\.PrimitiveField version=1 " + r"bound_path=Caves-native adapter=FCavesFieldAdapter$", re.M), + "vertical-shafts": re.compile( + r"^\[DensityDispatch\] strate=3 generator=VerticalShafts " + r"recipe=Core\.Caves\.PrimitiveField version=1 " + r"bound_path=Caves-native adapter=FCavesFieldAdapter$", re.M), +} +ROUTES = { + "tunnel-network": re.compile( + r"^\[ResolvedStrateRouteAudit\] strate=2 .*unsupported=0 " + r"grade_failures=0 .*status=PASS$", re.M), + "vertical-shafts": re.compile( + r"^\[ResolvedStrateRouteAudit\] strate=3 .*unsupported=0 " + r"grade_failures=0 .*status=PASS$", re.M), +} +CONTROLS = { + "tunnel-network": re.compile( + r"^\[TunnelNetworkBlockedRouteControl\].*expected=FAIL status=PASS$", re.M), + "vertical-shafts": re.compile( + r"^\[VerticalShaftsBlockedRouteControl\].*expected=FAIL status=PASS$", re.M), +} +REQUIRED = { + "passage_summary": re.compile(r"^\[ResolvedPassageAuditSummary\].*status=PASS$", re.M), + "seams": re.compile(r"^\[PassageTileSeamAudit\].*status=PASS$", re.M), + "shell": re.compile(r"^\[PassageShellAuditSummary\].*status=PASS$", re.M), + "naturalness": re.compile(r"^\[PassageNaturalnessSummary\].*status=PASS$", re.M), + "mouth_profile": re.compile(r"^\[PassageMouthProfileSummary\].*status=PASS$", re.M), + "ripple": re.compile(r"^\[PassageRippleAuditSummary\].*status=PASS$", re.M), + "interior_capsules": re.compile( + r"^\[PassageInteriorCapsuleAuditSummary\].*status=PASS$", re.M), + "landing_pads": re.compile( + r"^\[LandingPadAuditSummary\] pads=6 passed=6 negative_controls=24/24 status=PASS$", re.M), + "layout_controls": re.compile( + r"^\[ResolvedLayoutValidatorControls\] rejected=10/10 status=PASS$", re.M), + "floor_profile_controls": re.compile( + r"^\[FloorProfileBlendNegativeControls\].*detected=true/true status=PASS$", re.M), + "naturalness_control": re.compile( + r"^\[PassageNaturalnessNegativeControl\].*detected=true$", re.M), + "mouth_control": re.compile( + r"^\[PassageMouthProfileNegativeControl\].*detected=true$", re.M), +} + + +def passage_rows(content: str) -> list[str]: + return re.findall(r"^\[ResolvedPassageAudit\].*$", content, re.M) + + +def grade_audit(content: str) -> tuple[bool, list[str]]: + rows = re.findall(r"^\[PassagePlanGradeAudit\].*$", content, re.M) + ids = set() + passed = bool(rows) + for row in rows: + identifier = re.search(r"\bid=([^ ]+)", row) + maximum = re.search(r"\bmax_grade_deg=([0-9.]+)", row) + limit = re.search(r"\blimit_deg=([0-9.]+)", row) + violations = re.search(r"\bviolations=(\d+)", row) + status = "status=PASS" in row + if identifier: + ids.add(identifier.group(1)) + passed &= bool(identifier and maximum and limit and violations and status + and float(maximum.group(1)) <= float(limit.group(1)) + and float(limit.group(1)) == 15.0 + and int(violations.group(1)) == 0) + return passed and len(ids) >= 3, rows + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--kind", choices=tuple(DISPATCH), required=True) + parser.add_argument("--start-seed", type=int, default=0) + parser.add_argument("--end-seed", type=int, default=15) + args = parser.parse_args() + if not 0 <= args.start_seed <= args.end_seed <= 15: + raise SystemExit("seed range must be within 0..15 and start <= end") + if not BIN.is_file() or not DATA.is_file(): + raise SystemExit("missing L159 Clang binary or composed owner fixture") + mode_out = OUT / args.kind + mode_out.mkdir(parents=True, exist_ok=True) + partial = mode_out / "runs-partial.json" + results = (json.loads(partial.read_text(encoding="utf-8")) + if partial.is_file() and args.start_seed > 0 else []) + completed = {row["seed"] for row in results} + for seed in range(args.start_seed, args.end_seed + 1): + if seed in completed: + continue + log = mode_out / f"seed{seed}.log" + layout = mode_out / f"seed{seed}.json" + command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", + "timeout", "1740s", str(BIN), str(DATA), "--world-assembler-prototype", + "--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3", + "--max-clip-level=8", "--max-level=0", "--force-generate", f"--seed={seed}", + "--recipe=surface-world", "--density-dispatch-report", + "--audit-resolved-layout-passages", "--audit-passage-tile-seams", + f"--resolved-layout-output={layout}"] + started = time.monotonic() + with log.open("w", encoding="utf-8") as output: + proc = subprocess.run(command, cwd=ROOT, stdout=output, + stderr=subprocess.STDOUT, check=False, timeout=1740) + content = log.read_text(encoding="utf-8", errors="replace") + stamp = STAMP_RE.search(content) + checks = {name: bool(pattern.search(content)) for name, pattern in REQUIRED.items()} + checks["process_exit_zero"] = proc.returncode == 0 + checks["source_stamp"] = stamp is not None and stamp.group(1) == STAMP + checks["dispatch"] = bool(DISPATCH[args.kind].search(content)) + checks["route"] = bool(ROUTES[args.kind].search(content)) + checks["blocked_route_control"] = bool(CONTROLS[args.kind].search(content)) + + passages = passage_rows(content) + checks["three_passages_walkable_and_open"] = ( + len(passages) == 3 and all("walkability=PASS" in row + and "interior_air=PASS" in row + and re.search(r"\bblocked=0\b", row) + and re.search(r"\bunsupported=0\b", row) + and re.search(r"\bgrade_failures=0\b", row) + for row in passages)) + checks["three_interior_negative_controls"] = len(re.findall( + r"^\[ResolvedPassageInteriorNegativeControl\].*detected=true status=PASS$", + content, re.M)) == 3 + checks["three_ripple_gap_controls"] = len(re.findall( + r"^\[PassageRippleSamplingControl\].*status=PASS$", content, re.M)) == 3 + checks["three_blocked_passage_controls"] = len(re.findall( + r"^\[ResolvedPassageBlockedControl\].*detected=true$", content, re.M)) == 3 + chamber_rows = re.findall(r"^\[PassageLandingChamberAudit\].*$", content, re.M) + checks["six_chamber_floors"] = (len(chamber_rows) == 6 and all( + "status=PASS" in row and re.search(r"\bsolid_samples=0\b", row) + and re.search(r"\bgrade_failures=0\b", row) for row in chamber_rows)) + pad_rows = re.findall(r"^\[LandingPadAudit\].*$", content, re.M) + checks["six_landing_pad_audits"] = len(pad_rows) == 6 and all( + "status=PASS" in row and "negative_controls=4/4" in row + and all(f"{control}=detected" in row for control in ( + "pad_off", "tilt_2pct", "blend_B_half", "bore_mask_removed")) + for row in pad_rows) + checks["plan_grade"] = grade_audit(content)[0] + chamber_controls = re.search( + r"^\[PassageLandingChamberNegativeControlSummary\] " + r"detected=(\d+)/6 .*status=OBSERVED$", content, re.M) + checks["chamber_negative_control"] = bool( + chamber_controls and int(chamber_controls.group(1)) > 0) + + checks["three_ripple_profiles"] = len(re.findall( + r"^\[PassageRippleAudit\].*status=PASS$", content, re.M)) >= 3 + checks["passage_count"] = len(re.findall( + r"^\[ResolvedPassageAudit\].*walkability=PASS", content, re.M)) == 3 + checks["layout_output"] = layout.is_file() + envelope = audit_layout(json.loads(layout.read_text(encoding="utf-8"))) \ + if layout.is_file() else None + checks["envelope"] = bool(envelope and envelope["passed"] + and envelope["outside_chamber_conflicts"] == 0) + checks["envelope_negative_control"] = bool(envelope + and envelope["negative_controls"] == envelope["negative_controls_expected"] == 1) + checks["landing_content_band"] = bool(envelope + and envelope["landing_content_band_passed"] + and len(envelope["landing_content_band"]) == 6) + passed = all(checks.values()) + failed = [name for name, ok in checks.items() if not ok] + row = {"kind": args.kind, "seed": seed, + "verdict": "PASS" if passed else "FAIL", + "reason": ", ".join(failed) if failed else "all requested gates pass", + "exit": proc.returncode, "checks": checks, + "plan_grade_rows": grade_audit(content)[1], + "envelope_audit": envelope, + "elapsed_s": round(time.monotonic() - started, 3), + "log": str(log.relative_to(ROOT)), "layout": str(layout.relative_to(ROOT))} + results.append(row) + print("[L159SeedSweep] " + json.dumps(row, sort_keys=True), flush=True) + partial.write_text(json.dumps(results, indent=2) + "\n", encoding="utf-8") + + results.sort(key=lambda row: row["seed"]) + passed_count = sum(row["verdict"] == "PASS" for row in results) + completed_count = len(results) + missing_seeds = sorted(set(range(16)) - {row["seed"] for row in results}) + summary = {"kind": args.kind, "source_stamp": STAMP, + "seeds": [row["seed"] for row in results], + "completed": completed_count, "passed": passed_count, + "failed": completed_count - passed_count, "missing_seeds": missing_seeds, + "results": results} + (mode_out / "summary.json").write_text(json.dumps(summary, indent=2) + "\n", + encoding="utf-8") + status = "PASS" if completed_count == 16 and passed_count == 16 else "FAIL" + print(f"[L159SeedSweepSummary] kind={args.kind} passed={passed_count}/{completed_count} " + f"failed={completed_count-passed_count} missing={len(missing_seeds)} status={status}", + flush=True) + return 0 if status == "PASS" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/INDEX.md b/Standalone/Research/WorldAssemblerM3/Linux159/captures/INDEX.md new file mode 100644 index 0000000..934f9d6 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux159/captures/INDEX.md @@ -0,0 +1,41 @@ +# L159 owner landing pad captures + +All poses use the seed 0 layout resolved for L151. Eye-height after views reuse the six L151 cameras; each pad also has one pulled-back wide view. + +## passage/0-1/0 / upper landing + +- Before: [after-l151-0-1-0-upper-landing.jpg](../../Linux151/captures/after-l151-0-1-0-upper-landing.jpg) +- After: [eye view](after-l159-0-1-0-upper-landing.jpg) +- After: [wide view](after-l159-0-1-0-upper-wide.jpg) + +## passage/0-1/0 / lower landing + +- Before: [after-l151-0-1-0-lower-landing.jpg](../../Linux151/captures/after-l151-0-1-0-lower-landing.jpg) +- After: [eye view](after-l159-0-1-0-lower-landing.jpg) +- After: [wide view](after-l159-0-1-0-lower-wide.jpg) + +## passage/1-2/0 / upper landing + +- Before: [after-l151-1-2-0-upper-landing.jpg](../../Linux151/captures/after-l151-1-2-0-upper-landing.jpg) +- After: [eye view](after-l159-1-2-0-upper-landing.jpg) +- After: [wide view](after-l159-1-2-0-upper-wide.jpg) + +## passage/1-2/0 / lower landing + +- Before: [after-l151-1-2-0-lower-landing.jpg](../../Linux151/captures/after-l151-1-2-0-lower-landing.jpg) +- After: [eye view](after-l159-1-2-0-lower-landing.jpg) +- After: [wide view](after-l159-1-2-0-lower-wide.jpg) + +## passage/2-3/0 / upper landing + +- Before: [after-l151-2-3-0-upper-landing.jpg](../../Linux151/captures/after-l151-2-3-0-upper-landing.jpg) +- After: [eye view](after-l159-2-3-0-upper-landing.jpg) +- After: [wide view](after-l159-2-3-0-upper-wide.jpg) + +## passage/2-3/0 / lower landing + +- Before: [after-l151-2-3-0-lower-landing.jpg](../../Linux151/captures/after-l151-2-3-0-lower-landing.jpg) +- After: [eye view](after-l159-2-3-0-lower-landing.jpg) +- After: [wide view](after-l159-2-3-0-lower-wide.jpg) + +Capture quality failures: 0/12. diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-lower-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-lower-landing.jpg new file mode 100644 index 0000000..6e19b72 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-lower-landing.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-lower-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-lower-wide.jpg new file mode 100644 index 0000000..78d3492 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-lower-wide.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-upper-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-upper-landing.jpg new file mode 100644 index 0000000..309c25f Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-upper-landing.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-upper-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-upper-wide.jpg new file mode 100644 index 0000000..eba55a8 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-upper-wide.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-lower-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-lower-landing.jpg new file mode 100644 index 0000000..37a0121 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-lower-landing.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-lower-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-lower-wide.jpg new file mode 100644 index 0000000..bc3e24f Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-lower-wide.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-upper-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-upper-landing.jpg new file mode 100644 index 0000000..e4074e7 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-upper-landing.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-upper-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-upper-wide.jpg new file mode 100644 index 0000000..129ca17 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-upper-wide.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-lower-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-lower-landing.jpg new file mode 100644 index 0000000..05dc67b Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-lower-landing.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-lower-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-lower-wide.jpg new file mode 100644 index 0000000..b64172a Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-lower-wide.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-upper-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-upper-landing.jpg new file mode 100644 index 0000000..3a5b9e8 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-upper-landing.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-upper-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-upper-wide.jpg new file mode 100644 index 0000000..93a9746 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-upper-wide.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/captures/manifest.json b/Standalone/Research/WorldAssemblerM3/Linux159/captures/manifest.json new file mode 100644 index 0000000..0e262f5 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux159/captures/manifest.json @@ -0,0 +1,290 @@ +[ + { + "version": "after-l159", + "view": "0-1-0-upper-landing", + "passage": "passage/0-1/0", + "side": "upper", + "view_kind": "eye", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-upper-landing.jpg", + "camera_m": [ + 37.0, + -5.25, + -372.3810546875 + ], + "target_m": [ + 28.40474992442715, + -7.918646874047773, + -372.325197471535 + ], + "yaw_deg": -107.24848481126237, + "pitch_deg": 0.3555935154874107, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-upper-landing.jpg pixels=921600 mean_luma=113.911 stddev_luma=9.359 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l159", + "view": "0-1-0-upper-wide", + "passage": "passage/0-1/0", + "side": "upper", + "view_kind": "wide", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-upper-wide.jpg", + "camera_m": [ + 50.37038900644665, + -1.0987715292590199, + -368.3310546875 + ], + "target_m": [ + 35.08994442765048, + -5.843032638677283, + -372.5810546875 + ], + "yaw_deg": -107.24848481126239, + "pitch_deg": -14.8756820016388, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-upper-wide.jpg pixels=921600 mean_luma=119.719 stddev_luma=11.104 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l159", + "view": "0-1-0-lower-landing", + "passage": "passage/0-1/0", + "side": "lower", + "view_kind": "eye", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-lower-landing.jpg", + "camera_m": [ + 25.8369140625, + 3.19921875, + -736.6798828125 + ], + "target_m": [ + 17.97380590951016, + -1.1793118453464206, + -734.970169262755 + ], + "yaw_deg": -119.11101808688004, + "pitch_deg": 10.756207017349817, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-lower-landing.jpg pixels=921600 mean_luma=112.097 stddev_luma=12.698 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l159", + "view": "0-1-0-lower-wide", + "passage": "passage/0-1/0", + "side": "lower", + "view_kind": "wide", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-lower-wide.jpg", + "camera_m": [ + 38.06841563381753, + 10.0102663427611, + -732.6298828125 + ], + "target_m": [ + 24.089556695168923, + 2.226211951034129, + -736.8798828125 + ], + "yaw_deg": -119.11101808688004, + "pitch_deg": -14.875682001638797, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-0-1-0-lower-wide.jpg pixels=921600 mean_luma=105.110 stddev_luma=2.648 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l159", + "view": "1-2-0-upper-landing", + "passage": "passage/1-2/0", + "side": "upper", + "view_kind": "eye", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-upper-landing.jpg", + "camera_m": [ + 19.166015625, + -14.2001953125, + -731.8810546875 + ], + "target_m": [ + 10.35825176774404, + -16.050410390488395, + -733.216132282697 + ], + "yaw_deg": -101.86342177874246, + "pitch_deg": -8.437834550031965, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-upper-landing.jpg pixels=921600 mean_luma=117.708 stddev_luma=15.445 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l159", + "view": "1-2-0-upper-wide", + "passage": "passage/1-2/0", + "side": "upper", + "view_kind": "wide", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-upper-wide.jpg", + "camera_m": [ + 32.86698162517594, + -11.322082968962494, + -727.8310546875 + ], + "target_m": [ + 17.20873476783201, + -14.611354218719644, + -732.0810546875 + ], + "yaw_deg": -101.86342177874248, + "pitch_deg": -14.875682001638797, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-upper-wide.jpg pixels=921600 mean_luma=105.434 stddev_luma=3.032 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l159", + "view": "1-2-0-lower-landing", + "passage": "passage/1-2/0", + "side": "lower", + "view_kind": "eye", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-lower-landing.jpg", + "camera_m": [ + 19.166015625, + -14.2001953125, + -753.05390625 + ], + "target_m": [ + 10.48848361510052, + -16.587754362485967, + -751.7193175939389 + ], + "yaw_deg": -105.38384894032598, + "pitch_deg": 8.434788863627224, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-lower-landing.jpg pixels=921600 mean_luma=120.892 stddev_luma=15.997 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l159", + "view": "1-2-0-lower-wide", + "passage": "passage/1-2/0", + "side": "lower", + "view_kind": "wide", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-lower-wide.jpg", + "camera_m": [ + 32.664398751510305, + -10.486214568077383, + -749.00390625 + ], + "target_m": [ + 17.237675178355673, + -14.73076399027466, + -753.25390625 + ], + "yaw_deg": -105.38384894032599, + "pitch_deg": -14.875682001638793, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-1-2-0-lower-wide.jpg pixels=921600 mean_luma=104.587 stddev_luma=2.079 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l159", + "view": "2-3-0-upper-landing", + "passage": "passage/2-3/0", + "side": "upper", + "view_kind": "eye", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-upper-landing.jpg", + "camera_m": [ + -20.0625, + -5.068359375, + -812.075390625 + ], + "target_m": [ + -13.604954164712868, + -11.337338700629157, + -813.6662645746064 + ], + "yaw_deg": 134.15112141809087, + "pitch_deg": -10.024269857937275, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-upper-landing.jpg pixels=921600 mean_luma=119.468 stddev_luma=15.926 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l159", + "view": "2-3-0-upper-wide", + "passage": "passage/2-3/0", + "side": "upper", + "view_kind": "wide", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-upper-wide.jpg", + "camera_m": [ + -30.10757129933554, + 4.683386242645357, + -808.025390625 + ], + "target_m": [ + -18.627489814380638, + -6.46146589180648, + -812.275390625 + ], + "yaw_deg": 134.15112141809087, + "pitch_deg": -14.875682001638797, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-upper-wide.jpg pixels=921600 mean_luma=108.691 stddev_luma=5.224 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l159", + "view": "2-3-0-lower-landing", + "passage": "passage/2-3/0", + "side": "lower", + "view_kind": "eye", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-lower-landing.jpg", + "camera_m": [ + -20.0625, + -5.068359375, + -900.0333984375 + ], + "target_m": [ + -14.865342978484083, + -12.416118141706198, + -898.4427583280197 + ], + "yaw_deg": 144.72777711381306, + "pitch_deg": 10.022826283920647, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-lower-landing.jpg pixels=921600 mean_luma=120.115 stddev_luma=17.038 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l159", + "view": "2-3-0-lower-wide", + "passage": "passage/2-3/0", + "side": "lower", + "view_kind": "wide", + "file": "Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-lower-wide.jpg", + "camera_m": [ + -28.146966477913647, + 6.361487595431864, + -895.9833984375 + ], + "target_m": [ + -18.90757621744091, + -6.701194656490266, + -900.2333984375 + ], + "yaw_deg": 144.72777711381306, + "pitch_deg": -14.8756820016388, + "source_stamp": "5b8ab5e84364e26c1a555428fba8bedb23faed14ef0f6b7d397edf654883d640", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux159/captures/after-l159-2-3-0-lower-wide.jpg pixels=921600 mean_luma=104.815 stddev_luma=2.399 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + } +] diff --git a/Standalone/Research/WorldAssemblerM3/Linux159/l159-pad-stamp.patch b/Standalone/Research/WorldAssemblerM3/Linux159/l159-pad-stamp.patch new file mode 100644 index 0000000..bff6bbf --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux159/l159-pad-stamp.patch @@ -0,0 +1,1068 @@ +diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +index 4799b9e..13440df 100644 +--- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h ++++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +@@ -512,7 +512,8 @@ namespace VoxelForgeCore + // boundary-blended host rock from widening the authored passage wall. + // v238 plans one resolved passage spline with explicit landing chambers. + // v239 plans their floor planes as the passage's continuous floor profile. +- inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 239; ++ // v240 replaces chamber boxes and shells with bounded clamp-and-cone landing pads. ++ inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 240; + + enum class FieldChannel : std::uint32_t + { +diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h +index 6b1a6af..3ce93e4 100644 +--- a/Standalone/Core/src/ShapeWorldInternal.h ++++ b/Standalone/Core/src/ShapeWorldInternal.h +@@ -56,6 +56,11 @@ static bool VF_IsL130PassageReachProofAuditEnabled() + static constexpr double VF_PassageShellThicknessMetres=0.75; + static constexpr double VF_PassageWallReliefMetres=0.21; + static constexpr double VF_PassageCornerBlendMetres=0.50; ++static constexpr double VF_LandingPadConeSlope=0.2125565616700221; // tan(12 degrees) ++static constexpr double VF_LandingPadFillDepthMetres=1.25; ++static constexpr double VF_LandingPadHeadroomMetres=3.0; ++static constexpr double VF_LandingPadBlendRadiusMetres=8.0; ++static constexpr double VF_LandingPadSmoothMinMaximumMetres=0.50; + static constexpr double VF_PassageReachMarginMetres=0.01; + // Radial support in metres: shell + maximum relief + the smooth-max support + // (corner-blend width / 6) + a 1 cm guard for float and bound rounding. +@@ -1234,8 +1239,11 @@ public: + + float ApplyResolvedStratePassageCarves(float density,float X,float Y,float Z, + float nativeTunnelFieldDensity=std::numeric_limits::lowest(), +- float nativeFloorSupport=std::numeric_limits::lowest()) const ++ float nativeFloorSupport=std::numeric_limits::lowest(), ++ float* outTubeOnlyDistance=nullptr) const + { ++ if(outTubeOnlyDistance) ++ *outTubeOnlyDistance=std::numeric_limits::infinity(); + VoxelForgeCore::Linux77Profiler::FScopedPhase l77PassagePhase( + VoxelForgeCore::Linux77Profiler::EPhase::Passage); + if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X) +@@ -1280,8 +1288,6 @@ public: + double unionDistance=std::numeric_limits::infinity(); + double unionShellDistance=std::numeric_limits::infinity(); + double unionFloorShellDistance=std::numeric_limits::infinity(); +- double unionLandingChamberFloorShellDistance= +- std::numeric_limits::infinity(); + double plannedTunnelPassageShellDistance=std::numeric_limits::infinity(); + const std::uint32_t reliefSeed=static_cast(Owner?Owner->Seed:0); + const auto* queryStratum=FindBoundStratumForManagerZ(Z); +@@ -1324,48 +1330,6 @@ public: + if(passageProfile)++passageProfile->passageIterations; + const auto& constants=PassageComposerConstants[passageIndex]; + +- const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1* landingChamber=nullptr; +- if(queryStratum&&queryStratum->Resolved) +- { +- if(queryStratum->StrateIndex==passage.upperStrateIndex) +- landingChamber=&passage.upperLandingChamber; +- else if(queryStratum->StrateIndex==passage.lowerStrateIndex) +- landingChamber=&passage.lowerLandingChamber; +- } +- if(landingChamber&&landingChamber->radiusSubvoxels>0 +- &&landingChamber->clearHeightSubvoxels>0) +- { +- const double centerX=static_cast( +- landingChamber->floorCenterSubvoxels[0]); +- const double centerY=static_cast( +- landingChamber->floorCenterSubvoxels[1]); +- const double floorCenterZ=static_cast( +- landingChamber->floorCenterSubvoxels[2]) +- +(landingChamber->floorGradientQ16[0]*(qx-centerX) +- +landingChamber->floorGradientQ16[1]*(qy-centerY))/65536.0; +- const double radialDistance=DeterministicSqrt( +- (qx-centerX)*(qx-centerX)+(qy-centerY)*(qy-centerY)) +- -landingChamber->radiusSubvoxels; +- const double floorDistance=floorCenterZ-qz; +- const double ceilingDistance=qz-(floorCenterZ +- +landingChamber->clearHeightSubvoxels); +- const double chamberDistance=std::max({radialDistance, +- floorDistance,ceilingDistance}); +- const double chamberShellDistance=std::max(-chamberDistance, +- chamberDistance-shellThickness); +- const double chamberFloorShellDistance=std::max( +- std::max(-floorDistance,floorDistance-shellThickness), +- radialDistance); +- unionDistance=std::min(unionDistance,chamberDistance); +- unionShellDistance=std::min(unionShellDistance,chamberShellDistance); +- unionFloorShellDistance=std::min(unionFloorShellDistance, +- chamberFloorShellDistance); +- unionLandingChamberFloorShellDistance=std::min( +- unionLandingChamberFloorShellDistance,chamberFloorShellDistance); +- if(passageTargetsPlannedTunnelNetwork) +- plannedTunnelPassageShellDistance=std::min( +- plannedTunnelPassageShellDistance,chamberShellDistance); +- } + if(passageTargetsNativeCaveField&&!passageTargetsPlannedTunnelNetwork) + continue; + +@@ -1658,12 +1622,9 @@ public: + } + + if(!std::isfinite(unionDistance))return density; ++ if(outTubeOnlyDistance) ++ *outTubeOnlyDistance=static_cast(unionDistance/Q); + const float passageAirDistance=static_cast(-unionDistance/Q); +- // Keep the landing floor shell where it is exposed to the chamber. At +- // the tunnel joint, the passage void must be able to open through that +- // floor shell so its own continuous floor and shell define the throat. +- if(unionLandingChamberFloorShellDistance<0.0&&unionDistance>0.0) +- return static_cast(unionLandingChamberFloorShellDistance/Q); + const auto plannedPortalWalkCorridorContains=[&]() + { + if(!queryStratum||!queryStratum->Resolved +@@ -1746,90 +1707,6 @@ public: + departureTailDistance) + ||nearRouteSegment(*lastTwo,*last,PortalWalkLeadMetres,true,0.0); + }; +- const auto connectedSurfaceLandingShellContains=[&]() +- { +- if(!queryStratum||!queryStratum->Resolved +- ||queryStratum->Resolved->generatorKind!="SurfaceWorld")return false; +- const double queryX=qx/qPerMetre,queryY=qy/qPerMetre,queryZ=qz/qPerMetre; +- constexpr double ChamberShellThicknessMetres=0.75; +- constexpr double RouteClearanceMetres=1.25; +- constexpr double RouteHeightClearanceMetres=1.5; +- constexpr double PortalWalkLeadMetres=9.0; +- for(const auto& passage:ResolvedLayout->passages) +- { +- if(passage.upperStrateIndex!=queryStratum->StrateIndex)continue; +- const auto& chamber=passage.upperLandingChamber; +- if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)continue; +- const double cx=static_cast(chamber.floorCenterSubvoxels[0])/qPerMetre; +- const double cy=static_cast(chamber.floorCenterSubvoxels[1])/qPerMetre; +- const double floorZ=static_cast(chamber.floorCenterSubvoxels[2])/qPerMetre +- +chamber.floorGradientQ16[0]/65536.0*(queryX-cx) +- +chamber.floorGradientQ16[1]/65536.0*(queryY-cy); +- const double radialDistance=std::hypot(queryX-cx,queryY-cy) +- -static_cast(chamber.radiusSubvoxels)/qPerMetre; +- const double floorDistance=floorZ-queryZ; +- const double ceilingDistance=queryZ-(floorZ +- +static_cast(chamber.clearHeightSubvoxels)/qPerMetre); +- const double chamberDistance=std::max({radialDistance, +- floorDistance,ceilingDistance}); +- const double chamberShellDistance=std::max(-chamberDistance, +- chamberDistance-ChamberShellThicknessMetres); +- if(chamberDistance<=0.0||chamberShellDistance>=0.0)continue; +- +- std::vector route; +- for(const auto& anchor:queryStratum->Resolved->anchors) +- if(anchor.kind=="route_point"&&anchor.values.size()>=3) +- route.push_back(&anchor); +- if(route.size()<2)continue; +- const double mouthZ=static_cast(passage.upperMouthSubvoxels[2]) +- /qPerMetre; +- std::size_t endpoint=route.size(); +- double endpointDistanceSquared=1.0; +- for(std::size_t index=0;indexvalues[0]/qPerMetre; +- const double y=route[index]->values[1]/qPerMetre; +- const double z=route[index]->values[2]/qPerMetre; +- const double dx=x-cx,dy=y-cy; +- const double distanceSquared=dx*dx+dy*dy; +- if(distanceSquared routeDistance(route.size(),0.0); +- for(std::size_t index=1;indexvalues[0]-route[index-1]->values[0]) +- /qPerMetre; +- const double dy=(route[index]->values[1]-route[index-1]->values[1]) +- /qPerMetre; +- routeDistance[index]=routeDistance[index-1]+std::hypot(dx,dy); +- } +- for(std::size_t index=1;indexPortalWalkLeadMetres)continue; +- const double routeX=ax+t*dx,routeY=ay+t*dy; +- const double routeZ=az+t*(b.values[2]-a.values[2])/qPerMetre; +- if(std::hypot(queryX-routeX,queryY-routeY)<=RouteClearanceMetres +- &&std::abs(queryZ-routeZ)<=RouteHeightClearanceMetres)return true; +- } +- } +- return false; +- }; + if(plannedTunnelStratum + &&nativeTunnelFieldDensity!=std::numeric_limits::lowest() + &&nativeTunnelFieldDensity<0.0f) +@@ -1893,13 +1770,95 @@ public: + return std::max(density,passageAirDistance); + if(unionDistance>0.0&&unionShellDistance<0.0&&density>=0.0f) + { +- if(connectedSurfaceLandingShellContains()) +- return std::max(density,passageAirDistance); + return static_cast(unionShellDistance/Q); + } + return std::max(density,passageAirDistance); + } + ++ float ApplyResolvedLandingPadStamps(float density,float X,float Y,float Z, ++ float tubeOnlyDistance) const ++ { ++ if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X) ++ ||!std::isfinite(Y)||!std::isfinite(Z))return density; ++ const auto* bound=FindBoundStratumForManagerZ(Z); ++ if(!bound||!bound->Resolved)return density; ++ const double coreMetres=std::max(1, ++ ResolvedLayout->coreVoxelSizeMicrometres)/1000000.0; ++ constexpr double Q=static_cast( ++ VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); ++ const double qPerMetre=Q/coreMetres; ++ const double xMetres=static_cast(X)*coreMetres; ++ const double yMetres=static_cast(Y)*coreMetres; ++ const double zMetres=static_cast(Z)*coreMetres; ++ const double tubeDistanceMetres=static_cast(tubeOnlyDistance)*coreMetres; ++ const auto* tileQueries=FScopedPassageTileQueries::CurrentFor(*this); ++ const auto* candidatePassages=tileQueries ++ ?&tileQueries->routePassageIndices():nullptr; ++ const std::size_t candidateCount=candidatePassages ++ ?candidatePassages->size():ResolvedLayout->passages.size(); ++ float result=density; ++ constexpr double CosTwelveDegrees=0.9781476007338057; ++ constexpr double OuterReachMetres=VF_PassageInfluenceReachMetres; ++ for(std::size_t offset=0;offset=ResolvedLayout->passages.size())continue; ++ const auto& passage=ResolvedLayout->passages[passageIndex]; ++ const auto applyPad=[&]( ++ const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad) ++ { ++ if(pad.radiusSubvoxels<=0)return; ++ const double centerX=static_cast(pad.floorCenterSubvoxels[0]) ++ /qPerMetre; ++ const double centerY=static_cast(pad.floorCenterSubvoxels[1]) ++ /qPerMetre; ++ const double floorP=static_cast(pad.floorCenterSubvoxels[2]) ++ /qPerMetre; ++ const double radius=std::max(3.0, ++ static_cast(std::max(0, ++ passage.mouthRadiusSubvoxels))/qPerMetre+1.0); ++ const double dx=xMetres-centerX,dy=yMetres-centerY; ++ const double radial=DeterministicSqrt(dx*dx+dy*dy); ++ const double outerRadius=radius+VF_LandingPadBlendRadiusMetres; ++ if(radial>outerRadius+OuterReachMetres ++ ||zMetresfloorP+VF_LandingPadHeadroomMetres ++ +VF_LandingPadConeSlope*VF_LandingPadBlendRadiusMetres ++ +OuterReachMetres)return; ++ const double coneRun=std::max(0.0,radial-radius); ++ const double lower=floorP-VF_LandingPadConeSlope*coneRun; ++ const double upper=floorP+VF_LandingPadConeSlope*coneRun; ++ const double fillDistanceMetres=std::max({ ++ CosTwelveDegrees*(zMetres-lower), ++ CosTwelveDegrees*(lower-VF_LandingPadFillDepthMetres-zMetres), ++ radial-outerRadius}); ++ const double airDistanceMetres=std::min({ ++ CosTwelveDegrees*(zMetres-upper), ++ CosTwelveDegrees*(upper+VF_LandingPadHeadroomMetres-zMetres), ++ outerRadius-radial}); ++ const double fillVoxels=fillDistanceMetres/coreMetres; ++ const double airVoxels=airDistanceMetres/coreMetres; ++ const double maskedFill=std::max(fillVoxels,-tubeDistanceMetres/coreMetres); ++ const double smoothWidthMetres=VF_LandingPadSmoothMinMaximumMetres ++ *std::clamp(radial-radius,0.0,1.0); ++ const float next=static_cast(result); ++ const float fill=static_cast(maskedFill); ++ const float width=static_cast(smoothWidthMetres/coreMetres); ++ const float clamped=width>0.0f ++ ?SmoothMinDistance(next,fill,width):std::min(next,fill); ++ result=std::max(clamped,static_cast(airVoxels)); ++ }; ++ if(passage.upperStrateIndex==bound->StrateIndex) ++ applyPad(passage.upperLandingChamber); ++ if(passage.lowerStrateIndex==bound->StrateIndex) ++ applyPad(passage.lowerLandingChamber); ++ } ++ return result; ++ } ++ + float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const + { + float graphDensity=0.0f; +@@ -1930,6 +1889,7 @@ public: + } + float composed=boundary; + float stamped=composed; ++ float tubeOnlyDistance=std::numeric_limits::infinity(); + if(!StampRuntime.empty()) + { + VoxelForgeCore::Linux77Profiler::FScopedPhase l77StampPhase( +@@ -1963,12 +1923,13 @@ public: + const float nativeTunnelFieldDensity=nativeTunnelField + ?graphDensity:std::numeric_limits::lowest(); + stamped=ApplyResolvedStratePassageCarves(stamped,X,Y,Z, +- nativeTunnelFieldDensity,nativeGraphFloorSupport); ++ nativeTunnelFieldDensity,nativeGraphFloorSupport, ++ &tubeOnlyDistance); + } + if(bTraceTnComposition) + std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g native=1\n", + density,graphDensity,boundary,composed,stamped); +- return stamped; ++ return ApplyResolvedLandingPadStamps(stamped,X,Y,Z,tubeOnlyDistance); + } + // The legacy landmark demo retains its compatibility floor cap. Native caves + // already combine each floor with its wall before the single void carve above. +@@ -1985,12 +1946,14 @@ public: + VoxelForgeCore::Linux77Profiler::EPhase::Passage); + FScopedLegacyFieldTermTimer TermTimer( + ELegacyFieldTerm::ComposerPassage); +- final=ApplyResolvedStratePassageCarves(final,X,Y,Z); ++ final=ApplyResolvedStratePassageCarves(final,X,Y,Z, ++ std::numeric_limits::lowest(), ++ std::numeric_limits::lowest(),&tubeOnlyDistance); + } + if(bTraceTnComposition) + std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g final=%.9g native=0\n", + density,graphDensity,boundary,composed,stamped,final); +- return final; ++ return ApplyResolvedLandingPadStamps(final,X,Y,Z,tubeOnlyDistance); + } + + bool ResolvedPassageIntersectsBox(const int32 minimum[3],const int32 maximum[3]) const +@@ -2033,26 +1996,35 @@ public: + } + if(overlaps(low,high))return true; + } +- const auto chamberOverlaps=[&]( +- const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) +- { +- const double cx=static_cast(chamber.floorCenterSubvoxels[0])/Q; +- const double cy=static_cast(chamber.floorCenterSubvoxels[1])/Q; +- const double cz=static_cast(chamber.floorCenterSubvoxels[2])/Q; +- const double radius=static_cast(chamber.radiusSubvoxels)/Q; +- const double floorExtent=radius*std::hypot( +- static_cast(chamber.floorGradientQ16[0])/65536.0, +- static_cast(chamber.floorGradientQ16[1])/65536.0); ++ const auto padOverlaps=[&]( ++ const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad, ++ std::int64_t mouthRadiusSubvoxels) ++ { ++ if(pad.radiusSubvoxels<=0)return false; ++ const double cx=static_cast(pad.floorCenterSubvoxels[0])/Q; ++ const double cy=static_cast(pad.floorCenterSubvoxels[1])/Q; ++ const double pz=static_cast(pad.floorCenterSubvoxels[2])/Q; ++ const double qPerMetre=Q*1000000.0/ ++ std::max(1,ResolvedLayout->coreVoxelSizeMicrometres); ++ const double coreMetres=Q/qPerMetre; ++ const double radius=std::max(3.0, ++ static_cast(std::max(0,mouthRadiusSubvoxels)) ++ /qPerMetre+1.0); ++ const double outerRadius=radius+VF_LandingPadBlendRadiusMetres; + const double extent=reachCoreVoxels+0.01; +- const double low[3]={cx-radius-extent,cy-radius-extent, +- cz-floorExtent-extent}; +- const double high[3]={cx+radius+extent,cy+radius+extent, +- cz+floorExtent+static_cast( +- chamber.clearHeightSubvoxels)/Q+extent}; ++ const double coneHeight=VF_LandingPadConeSlope ++ *VF_LandingPadBlendRadiusMetres; ++ const double low[3]={cx-outerRadius/coreMetres-extent, ++ cy-outerRadius/coreMetres-extent, ++ pz-(VF_LandingPadFillDepthMetres+coneHeight)/coreMetres-extent}; ++ const double high[3]={cx+outerRadius/coreMetres+extent, ++ cy+outerRadius/coreMetres+extent, ++ pz+(VF_LandingPadHeadroomMetres+coneHeight)/coreMetres+extent}; + return overlaps(low,high); + }; +- if(chamberOverlaps(passage.upperLandingChamber) +- ||chamberOverlaps(passage.lowerLandingChamber))return true; ++ if(padOverlaps(passage.upperLandingChamber,passage.mouthRadiusSubvoxels) ++ ||padOverlaps(passage.lowerLandingChamber,passage.mouthRadiusSubvoxels)) ++ return true; + } + return false; + } +@@ -5415,6 +5387,9 @@ private: + if(!ResolvedLayout||ResolvedLayout->passages.empty())return; + constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); + constexpr std::size_t CandidateFineSamplesPerSpan=5; ++ const double coreMetres=std::max(1, ++ ResolvedLayout->coreVoxelSizeMicrometres)/1000000.0; ++ const double qPerMetre=Q/coreMetres; + const double requiredReachCoreVoxels=VF_PassageInfluenceReachCoreVoxels( + ResolvedLayout->coreVoxelSizeMicrometres); + for(std::size_t passageIndex=0;passageIndexpassages.size(); +@@ -5471,40 +5446,47 @@ private: + std::ceil((localMaximum[axis]+localExtent)*Q)); + } + } +- const auto includeLandingChamber=[&]( +- const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) +- { +- const double cx=static_cast(chamber.floorCenterSubvoxels[0])/Q; +- const double cy=static_cast(chamber.floorCenterSubvoxels[1])/Q; +- const double cz=static_cast(chamber.floorCenterSubvoxels[2])/Q; +- const double radius=static_cast(chamber.radiusSubvoxels)/Q; +- const double gx=static_cast(chamber.floorGradientQ16[0])/65536.0; +- const double gy=static_cast(chamber.floorGradientQ16[1])/65536.0; +- const double floorExtent=radius*std::hypot(gx,gy); +- const double height=static_cast( +- chamber.clearHeightSubvoxels)/Q; +- const double extent=requiredReachCoreVoxels+0.01; +- const std::array minimum{ +- cx-radius-extent,cy-radius-extent,cz-floorExtent-extent}; +- const std::array maximum{ +- cx+radius+extent,cy+radius+extent, +- cz+floorExtent+height+extent}; ++ const auto includeLandingPad=[&]( ++ const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad, ++ std::int64_t mouthRadiusSubvoxels) ++ { ++ if(pad.radiusSubvoxels<=0)return; ++ const double cx=static_cast(pad.floorCenterSubvoxels[0])/Q; ++ const double cy=static_cast(pad.floorCenterSubvoxels[1])/Q; ++ const double pz=static_cast(pad.floorCenterSubvoxels[2])/Q; ++ const double radius=std::max(3.0, ++ static_cast(std::max(0,mouthRadiusSubvoxels)) ++ /qPerMetre+1.0); ++ const double outerRadius=radius+VF_LandingPadBlendRadiusMetres; ++ const double coneHeight=VF_LandingPadConeSlope ++ *VF_LandingPadBlendRadiusMetres; ++ const std::array baseMinimum{ ++ cx-outerRadius/coreMetres, ++ cy-outerRadius/coreMetres, ++ pz-(VF_LandingPadFillDepthMetres+coneHeight)/coreMetres}; ++ const std::array baseMaximum{ ++ cx+outerRadius/coreMetres, ++ cy+outerRadius/coreMetres, ++ pz+(VF_LandingPadHeadroomMetres+coneHeight)/coreMetres}; + FPassageProofBound proof; ++ const double extent=requiredReachCoreVoxels+0.01; + for(std::size_t axis=0;axis<3;++axis) + { +- routeMinimum[axis]=std::min(routeMinimum[axis],minimum[axis]); +- routeMaximum[axis]=std::max(routeMaximum[axis],maximum[axis]); +- proof.minimum[axis]=minimum[axis]; +- proof.maximum[axis]=maximum[axis]; ++ routeMinimum[axis]=std::min(routeMinimum[axis],baseMinimum[axis]); ++ routeMaximum[axis]=std::max(routeMaximum[axis],baseMaximum[axis]); ++ proof.minimum[axis]=baseMinimum[axis]-extent; ++ proof.maximum[axis]=baseMaximum[axis]+extent; + } + PassageProofBounds.push_back(proof); + }; + if(segmentIndex==0) +- includeLandingChamber( +- ResolvedLayout->passages[passageIndex].upperLandingChamber); ++ includeLandingPad(ResolvedLayout->passages[passageIndex] ++ .upperLandingChamber,ResolvedLayout->passages[passageIndex] ++ .mouthRadiusSubvoxels); + if(segmentEndIndex==segmentCount) +- includeLandingChamber( +- ResolvedLayout->passages[passageIndex].lowerLandingChamber); ++ includeLandingPad(ResolvedLayout->passages[passageIndex] ++ .lowerLandingChamber,ResolvedLayout->passages[passageIndex] ++ .mouthRadiusSubvoxels); + const double routeExtent=maximumRadiusSubvoxels/Q + +requiredReachCoreVoxels+0.01; + const double tileExtent=routeExtent; +@@ -5629,6 +5611,108 @@ private: + } + } + } ++ for(std::size_t passageIndex=0; ++ passageIndexpassages.size();++passageIndex) ++ { ++ const auto& passage=ResolvedLayout->passages[passageIndex]; ++ const auto sampleLandingPad=[&]( ++ const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad) ++ { ++ if(pad.radiusSubvoxels<=0)return; ++ const double centerX=static_cast( ++ pad.floorCenterSubvoxels[0])/qPerMetre; ++ const double centerY=static_cast( ++ pad.floorCenterSubvoxels[1])/qPerMetre; ++ const double floorP=static_cast( ++ pad.floorCenterSubvoxels[2])/qPerMetre; ++ const double radius=std::max(3.0, ++ static_cast(std::max(0, ++ passage.mouthRadiusSubvoxels))/qPerMetre+1.0); ++ const double probeRadius=radius+VF_LandingPadBlendRadiusMetres-0.25; ++ const std::array probeHeights{ ++ floorP-0.5*VF_LandingPadFillDepthMetres, ++ floorP+0.5*VF_LandingPadHeadroomMetres}; ++ for(const double probeZ:probeHeights) ++ { ++ const double sampleQ[3]={centerX*qPerMetre, ++ centerY*qPerMetre,probeZ*qPerMetre}; ++ const double sampleVoxel[3]={sampleQ[0]/Q, ++ sampleQ[1]/Q,sampleQ[2]/Q}; ++ const float contribution=ApplyResolvedLandingPadStamps( ++ 0.0f,static_cast(sampleVoxel[0]), ++ static_cast(sampleVoxel[1]), ++ static_cast(sampleVoxel[2]), ++ std::numeric_limits::infinity()); ++ ++sampled; ++ if(std::abs(contribution)<=1.0e-6f)continue; ++ ++signRelevant; ++ if(PassageProofNodes.empty() ++ ||!PassageProofBoundsOverlapNode(0,sampleVoxel,sampleVoxel)) ++ ++proofMisses; ++ std::array sampleMinimum{},sampleMaximum{}; ++ for(std::size_t axis=0;axis<3;++axis) ++ { ++ sampleMinimum[axis]=static_cast( ++ std::floor(sampleQ[axis])); ++ sampleMaximum[axis]=static_cast( ++ std::ceil(sampleQ[axis])); ++ } ++ const auto candidates=BuildPassageFeatureTileList( ++ sampleMinimum,sampleMaximum); ++ const bool featureContainsPassage=std::any_of( ++ candidates.begin(),candidates.end(), ++ [&](std::uint32_t featureIndex) ++ { ++ return featureIndex(edgeSampleVoxel[0]), ++ static_cast(edgeSampleVoxel[1]), ++ static_cast(edgeSampleVoxel[2]), ++ std::numeric_limits::infinity()); ++ ++sampled; ++ if(std::abs(edgeContribution)<=1.0e-6f)continue; ++ ++signRelevant; ++ if(PassageProofNodes.empty() ++ ||!PassageProofBoundsOverlapNode(0,edgeSampleVoxel,edgeSampleVoxel)) ++ ++proofMisses; ++ for(std::size_t axis=0;axis<3;++axis) ++ { ++ sampleMinimum[axis]=static_cast( ++ std::floor(edgeSampleQ[axis])); ++ sampleMaximum[axis]=static_cast( ++ std::ceil(edgeSampleQ[axis])); ++ } ++ const auto edgeCandidates=BuildPassageFeatureTileList( ++ sampleMinimum,sampleMaximum); ++ const bool edgeFeatureContainsPassage=std::any_of( ++ edgeCandidates.begin(),edgeCandidates.end(), ++ [&](std::uint32_t featureIndex) ++ { ++ return featureIndex0&&signRelevant>0 + &&proofMisses==0&&featureMisses==0; + std::printf("[L130PassageReachProofAudit] reach_m=%.3f sampled=%llu sign_relevant=%llu proof_misses=%llu feature_misses=%llu status=%s\n", +diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h +index 2fcf4b5..1b502ed 100644 +--- a/Standalone/Source/StreamBenchmarkPipeline.h ++++ b/Standalone/Source/StreamBenchmarkPipeline.h +@@ -4703,78 +4703,6 @@ namespace StandaloneStreamBenchmark + [index](const auto& s){return s.strateIndex==index;}); + return it==ResolvedLayout->strates.end()?nullptr:&*it; + }; +- const auto landingChamberConnectionContains=[&]( +- const VoxelForgeCore::ResolvedLayoutPassageV1& passage,bool upper, +- double xMetres,double yMetres,double zMetres) +- { +- const auto& chamber=upper?passage.upperLandingChamber +- :passage.lowerLandingChamber; +- const auto* stratum=findStratum(upper?passage.upperStrateIndex +- :passage.lowerStrateIndex); +- if(!stratum||chamber.radiusSubvoxels<=0)return false; +- std::vector route; +- for(const auto& anchor:stratum->anchors) +- if(anchor.kind=="route_point"&&anchor.values.size()>=3) +- route.push_back(&anchor); +- if(route.size()<2)return false; +- const double cx=qToMetres(chamber.floorCenterSubvoxels[0]); +- const double cy=qToMetres(chamber.floorCenterSubvoxels[1]); +- const double mouthZ=qToMetres(upper?passage.upperMouthSubvoxels[2] +- :passage.lowerMouthSubvoxels[2]); +- std::size_t endpoint=route.size(); +- double endpointDistanceSquared=1.0; +- for(std::size_t index=0;indexvalues[0])-cx; +- const double dy=qToMetres(route[index]->values[1])-cy; +- const double z=qToMetres(route[index]->values[2]); +- const double distanceSquared=dx*dx+dy*dy; +- if(distanceSquared routeDistance(route.size(),0.0); +- for(std::size_t index=1;indexvalues[0] +- -route[index-1]->values[0]); +- const double dy=qToMetres(route[index]->values[1] +- -route[index-1]->values[1]); +- routeDistance[index]=routeDistance[index-1]+std::hypot(dx,dy); +- } +- for(std::size_t index=1;index9.0)continue; +- const double routeX=ax+t*dx,routeY=ay+t*dy; +- const double routeZ=az+t*qToMetres(b.values[2]-a.values[2]); +- const double lateralDistance=std::hypot(xMetres-routeX, +- yMetres-routeY); +- if(stratum->generatorKind=="TunnelNetwork" +- ||stratum->generatorKind=="VerticalShafts") +- { +- const double routeFloor=routeZ-1.13; +- if(lateralDistance<=0.80&&zMetres>=routeFloor-0.30 +- &&zMetres<=routeFloor+2.25)return true; +- } +- else if(lateralDistance<=1.25 +- &&std::abs(zMetres-routeZ)<=1.5)return true; +- } +- return false; +- }; + const auto controlSnapshot=CoreSnapshot.withoutResolvedLayoutPassages(); + VoxelForgeCore::World blockedWorld(controlSnapshot); + std::string blockedCompileError; +@@ -5153,89 +5081,6 @@ namespace StandaloneStreamBenchmark + if(interior<=0.0f)++audit.interiorViolations; + } + } +- constexpr std::array chamberFloorRadii{{0.0,2.5,5.0}}; +- constexpr std::array chamberWallHeights{{1.13,3.20}}; +- constexpr std::size_t ChamberAngularSamples=16; +- for(const bool upper:{true,false}) +- { +- const auto& chamber=upper?passage.upperLandingChamber +- :passage.lowerLandingChamber; +- if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)continue; +- const double centerX=qToMetres(chamber.floorCenterSubvoxels[0]); +- const double centerY=qToMetres(chamber.floorCenterSubvoxels[1]); +- const double baseFloorZ=qToMetres(chamber.floorCenterSubvoxels[2]); +- const double radius=qToMetres(chamber.radiusSubvoxels); +- const double gx=chamber.floorGradientQ16[0]/65536.0; +- const double gy=chamber.floorGradientQ16[1]/65536.0; +- for(const double radial:chamberFloorRadii) +- { +- if(radial>=radius-1.0)continue; +- const std::size_t angles=radial==0.0?1:ChamberAngularSamples; +- for(std::size_t angleIndex=0;angleIndex(angleIndex)/ChamberAngularSamples; +- const double sampleX=centerX+radial*std::cos(angle); +- const double sampleY=centerY+radial*std::sin(angle); +- const double floorZ=baseFloorZ+gx*(sampleX-centerX) +- +gy*(sampleY-centerY); +- for(const double depth:PassageShellAuditDepthsMetres) +- { +- const double sampleZ=floorZ-depth; +- const VoxelForgeCore::Vec3d position{sampleX/coreMetres, +- sampleY/coreMetres,sampleZ/coreMetres}; +- if(Native.densityAt(position)<0.0f)continue; +- if(landingChamberConnectionContains(passage,upper, +- sampleX,sampleY,sampleZ)) +- {++audit.chamberConnectionSkips;continue;} +- ++audit.chamberFloorSamples; +- const float density=Field.densityAt(position); +- if(density>=PassageShellDensityTolerance) +- { +- ++audit.chamberFloorViolations; +- std::printf("[PassageLandingChamberShellSample] id=%s side=%s kind=floor_missing xyz_m=(%.3f,%.3f,%.3f) native=%.6f density=%.6f\n", +- passage.stableId.c_str(),upper?"upper":"lower", +- sampleX,sampleY,sampleZ,Native.densityAt(position),density); +- } +- } +- } +- } +- for(std::size_t angleIndex=0;angleIndex(angleIndex)/ChamberAngularSamples; +- const double nx=std::cos(angle),ny=std::sin(angle); +- for(const double depth:PassageShellAuditDepthsMetres) +- for(const double height:chamberWallHeights) +- { +- const double sampleX=centerX+(radius+depth)*nx; +- const double sampleY=centerY+(radius+depth)*ny; +- const double floorZ=baseFloorZ+gx*(sampleX-centerX) +- +gy*(sampleY-centerY); +- const double sampleZ=floorZ+height; +- const VoxelForgeCore::Vec3d position{sampleX/coreMetres, +- sampleY/coreMetres,sampleZ/coreMetres}; +- if(Native.densityAt(position)<0.0f)continue; +- if(passageInteriorWinsAt(sampleX,sampleY,sampleZ, +- std::numeric_limits::max(), +- std::numeric_limits::max(),passageIndex) +- ||landingChamberConnectionContains(passage,upper, +- sampleX,sampleY,sampleZ)) +- {++audit.chamberConnectionSkips;continue;} +- ++audit.chamberWallSamples; +- const float density=Field.densityAt(position); +- if(density>=PassageShellDensityTolerance) +- { +- ++audit.chamberWallViolations; +- std::printf("[PassageLandingChamberShellSample] id=%s side=%s kind=wall_missing xyz_m=(%.3f,%.3f,%.3f) native=%.6f density=%.6f radial_m=%.3f height_m=%.3f\n", +- passage.stableId.c_str(),upper?"upper":"lower", +- sampleX,sampleY,sampleZ,Native.densityAt(position),density, +- radius+depth,height); +- } +- } +- } +- } + return audit; + }; + const auto mouthInContent=[&](std::int32_t index, +@@ -5305,10 +5150,257 @@ namespace StandaloneStreamBenchmark + } + return total; + }; ++ struct FLandingPadAudit ++ { ++ std::uint64_t flatSamples=0,flatFailures=0; ++ std::uint64_t supportSamples=0,supportFailures=0; ++ std::uint64_t headroomSamples=0,headroomFailures=0; ++ std::uint64_t blendRootSamples=0,blendMissing=0,blendGradeFailures=0; ++ std::uint64_t negativeControlsDetected=0; ++ double maximumFlatnessMetres=0.0,maximumGradeDegrees=0.0; ++ bool passed=false; ++ }; ++ const auto auditLandingPad=[&](const VoxelForgeCore::World& field, ++ const VoxelForgeCore::ResolvedLayoutPassageV1& passage,bool upper) ++ { ++ constexpr double ConeSlope=0.2125565616700221; ++ constexpr double CosTwelveDegrees=0.9781476007338057; ++ constexpr double FillDepthMetres=1.25,HeadroomMetres=3.0; ++ constexpr double BlendRadiusMetres=8.0; ++ constexpr double FlatnessLimitMetres=0.020; ++ constexpr double GradeLimitDegrees=15.0; ++ constexpr std::size_t RadialDirections=36; ++ constexpr double FloorSearchStepMetres=0.125; ++ FLandingPadAudit audit; ++ const auto& pad=upper?passage.upperLandingChamber ++ :passage.lowerLandingChamber; ++ if(pad.radiusSubvoxels<=0)return audit; ++ const double centerX=qToMetres(pad.floorCenterSubvoxels[0]); ++ const double centerY=qToMetres(pad.floorCenterSubvoxels[1]); ++ const double floorP=qToMetres(pad.floorCenterSubvoxels[2]); ++ const double radius=std::max(3.0, ++ qToMetres(std::max(0,passage.mouthRadiusSubvoxels))+1.0); ++ const auto densityMetres=[&](const VoxelForgeCore::World& source, ++ double x,double y,double z) ++ { ++ const float value=source.densityAt({x/coreMetres,y/coreMetres, ++ z/coreMetres}); ++ return std::isfinite(value)?static_cast(value)*coreMetres ++ :std::numeric_limits::quiet_NaN(); ++ }; ++ struct FFloorRoot ++ {double z=0.0;std::uint32_t crossings=0;bool found=false;}; ++ const auto findFloorRoot=[&](auto&& sampleDensity,double x,double y, ++ double low,double high) ++ { ++ FFloorRoot result; ++ double previousZ=low; ++ double previous=sampleDensity(x,y,previousZ); ++ if(!std::isfinite(previous))return result; ++ for(double z=low+FloorSearchStepMetres; ++ z=0.0) ++ { ++ const double denominator=current-previous; ++ const double t=std::abs(denominator)>1.0e-12 ++ ?std::clamp(-previous/denominator,0.0,1.0):0.0; ++ const double root=std::lerp(previousZ,currentZ,t); ++ ++result.crossings; ++ if(!result.found)result.z=root; ++ result.found=true; ++ } ++ previousZ=currentZ; ++ previous=current; ++ if(currentZ>=high)break; ++ } ++ return result; ++ }; ++ const auto fieldDensity=[&](double x,double y,double z) ++ {return densityMetres(field,x,y,z);}; ++ const auto sourceFieldDensity=[&](double x,double y,double z) ++ { ++ const float value=Shape.passageSourceDensityAt({x/coreMetres, ++ y/coreMetres,z/coreMetres}); ++ return std::isfinite(value)?static_cast(value)*coreMetres ++ :std::numeric_limits::quiet_NaN(); ++ }; ++ struct FFlatProbe {double x=0.0,y=0.0,z=0.0;}; ++ std::vector flatProbes; ++ const auto probeFlatness=[&](double radial,double angle) ++ { ++ const double x=centerX+radial*std::cos(angle); ++ const double y=centerY+radial*std::sin(angle); ++ const auto root=findFloorRoot(fieldDensity,x,y, ++ floorP-0.75,floorP+0.75); ++ ++audit.flatSamples; ++ if(!root.found||root.crossings!=1) ++ {++audit.flatFailures;return;} ++ const double error=std::abs(root.z-floorP); ++ audit.maximumFlatnessMetres=std::max( ++ audit.maximumFlatnessMetres,error); ++ if(error>FlatnessLimitMetres)++audit.flatFailures; ++ flatProbes.push_back({x,y,root.z}); ++ }; ++ probeFlatness(0.0,0.0); ++ for(std::size_t direction=0;direction(direction)/RadialDirections; ++ probeFlatness(0.90*radius,angle); ++ } ++ constexpr std::array SupportRadii{{0.0,0.5,0.9}}; ++ for(const double radialFraction:SupportRadii) ++ for(std::size_t direction=0;direction(direction)/RadialDirections; ++ const double x=centerX+radialFraction*radius*std::cos(angle); ++ const double y=centerY+radialFraction*radius*std::sin(angle); ++ ++audit.supportSamples; ++ if(!(fieldDensity(x,y,floorP-0.10)<0.0)) ++ ++audit.supportFailures; ++ ++audit.headroomSamples; ++ if(!(fieldDensity(x,y,floorP+2.0)>0.0)) ++ ++audit.headroomFailures; ++ } ++ constexpr std::size_t BlendStations=16; ++ const double blendEnd=BlendRadiusMetres-0.25; ++ for(std::size_t direction=0;direction(direction)/RadialDirections; ++ bool previousFound=false; ++ double previousRadius=0.0,previousFloor=0.0; ++ for(std::size_t station=0;station<=BlendStations;++station) ++ { ++ const double radial=radius+blendEnd ++ *static_cast(station)/BlendStations; ++ const double x=centerX+radial*std::cos(angle); ++ const double y=centerY+radial*std::sin(angle); ++ const double coneRun=std::max(0.0,radial-radius); ++ const double coneHeight=ConeSlope*coneRun; ++ const auto root=findFloorRoot(fieldDensity,x,y, ++ floorP-coneHeight-0.75,floorP+coneHeight+0.75); ++ ++audit.blendRootSamples; ++ if(!root.found||root.crossings!=1) ++ { ++ ++audit.blendMissing; ++ previousFound=false; ++ continue; ++ } ++ if(previousFound) ++ { ++ const double grade=std::atan2( ++ std::abs(root.z-previousFloor),radial-previousRadius) ++ *180.0/3.14159265358979323846; ++ audit.maximumGradeDegrees=std::max( ++ audit.maximumGradeDegrees,grade); ++ if(grade>GradeLimitDegrees)++audit.blendGradeFailures; ++ } ++ previousFound=true; ++ previousRadius=radial; ++ previousFloor=root.z; ++ } ++ } ++ const auto sourceField=[&](double x,double y,double z) ++ {return sourceFieldDensity(x,y,z);}; ++ bool padOffRejected=false; ++ for(std::size_t direction=0;direction(direction)/RadialDirections; ++ const double x=centerX+0.90*radius*std::cos(angle); ++ const double y=centerY+0.90*radius*std::sin(angle); ++ const auto root=findFloorRoot(sourceField,x,y, ++ floorP-0.75,floorP+0.75); ++ if(!root.found||root.crossings!=1 ++ ||std::abs(root.z-floorP)>FlatnessLimitMetres) ++ {padOffRejected=true;break;} ++ } ++ const bool tiltRejected=std::any_of(flatProbes.begin(),flatProbes.end(), ++ [&](const FFlatProbe& probe) ++ { ++ return std::abs((probe.z-floorP) ++ +0.02*(probe.x-centerX))>FlatnessLimitMetres; ++ }); ++ const auto controlStamp=[&](double blendRadius,double radial,double z, ++ double native,double tubeDistance) ++ { ++ const double coneRun=std::max(0.0,radial-radius); ++ const double lower=floorP-ConeSlope*coneRun; ++ const double upper=floorP+ConeSlope*coneRun; ++ const double outer=radius+blendRadius; ++ const double fill=std::max({CosTwelveDegrees*(z-lower), ++ CosTwelveDegrees*(lower-FillDepthMetres-z),radial-outer}); ++ const double air=std::min({CosTwelveDegrees*(z-upper), ++ CosTwelveDegrees*(upper+HeadroomMetres-z),outer-radial}); ++ const double masked=std::max(fill,-tubeDistance); ++ const double smoothWidth=0.5*std::clamp(radial-radius,0.0,1.0); ++ double smoothMinimum=std::min(native,masked); ++ if(smoothWidth>0.0) ++ { ++ const double h=std::max(smoothWidth ++ -std::abs(native-masked),0.0)/smoothWidth; ++ smoothMinimum-=h*h*h*(smoothWidth/6.0); ++ } ++ return std::max(smoothMinimum,air); ++ }; ++ const double outerProbe=radius+BlendRadiusMetres-0.25; ++ const double outerProbeZ=floorP-ConeSlope*(outerProbe-radius)-0.05; ++ const double fullBlendProbe=controlStamp(BlendRadiusMetres, ++ outerProbe,outerProbeZ,0.0,std::numeric_limits::infinity()); ++ const double halfBlendProbe=controlStamp(BlendRadiusMetres*0.5, ++ outerProbe,outerProbeZ,0.0,std::numeric_limits::infinity()); ++ const bool halfBlendRejected=fullBlendProbe< -0.01 ++ &&std::abs(halfBlendProbe)<1.0e-6; ++ const double boreMasked=controlStamp(BlendRadiusMetres, ++ radius*0.5,floorP-0.25,0.25,-0.50); ++ const double boreUnmasked=controlStamp(BlendRadiusMetres, ++ radius*0.5,floorP-0.25,0.25, ++ std::numeric_limits::infinity()); ++ const bool boreMaskRejected=boreMasked>0.0&&boreUnmasked<0.0; ++ audit.negativeControlsDetected=static_cast(padOffRejected) ++ +static_cast(tiltRejected) ++ +static_cast(halfBlendRejected) ++ +static_cast(boreMaskRejected); ++ audit.passed=audit.flatSamples>0&&audit.flatFailures==0 ++ &&audit.supportSamples>0&&audit.supportFailures==0 ++ &&audit.headroomSamples>0&&audit.headroomFailures==0 ++ &&audit.blendRootSamples>0&&audit.blendMissing==0 ++ &&audit.blendGradeFailures==0 ++ &&audit.negativeControlsDetected==4; ++ std::printf("[LandingPadAudit] id=%s side=%s radius_m=%.3f blend_m=%.3f flatness_samples=%llu flatness_max_m=%.5f flatness_failures=%llu support=%llu/%llu headroom=%llu/%llu blend_transects=%zu blend_roots=%llu blend_missing=%llu blend_grade_max_deg=%.3f blend_grade_failures=%llu negative_controls=%llu/4 pad_off=%s tilt_2pct=%s blend_B_half=%s bore_mask_removed=%s status=%s\n", ++ passage.stableId.c_str(),upper?"upper":"lower",radius, ++ BlendRadiusMetres,static_cast(audit.flatSamples), ++ audit.maximumFlatnessMetres, ++ static_cast(audit.flatFailures), ++ static_cast(audit.supportSamples ++ -audit.supportFailures), ++ static_cast(audit.supportSamples), ++ static_cast(audit.headroomSamples ++ -audit.headroomFailures), ++ static_cast(audit.headroomSamples), ++ RadialDirections, ++ static_cast(audit.blendRootSamples), ++ static_cast(audit.blendMissing), ++ audit.maximumGradeDegrees, ++ static_cast(audit.blendGradeFailures), ++ static_cast(audit.negativeControlsDetected), ++ padOffRejected?"detected":"MISSED",tiltRejected?"detected":"MISSED", ++ halfBlendRejected?"detected":"MISSED", ++ boreMaskRejected?"detected":"MISSED",audit.passed?"PASS":"FAIL"); ++ return audit; ++ }; + std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0; + std::uint64_t passageInteriorCapsuleSamples=0,passageInteriorSolidSamples=0; + std::uint64_t passageInteriorControls=0; + std::uint64_t landingChamberCount=0,landingChamberNegativeControls=0; ++ std::uint64_t landingPadCount=0,landingPadPasses=0,landingPadControls=0; + std::uint64_t mouthProfilePasses=0,ripplePasses=0,rippleControlsDetected=0; + std::uint64_t floorRippleControlsDetected=0,floorStepRippleControlsDetected=0; + std::uint64_t shellAudits=0,shellFloorSamples=0,shellFloorViolations=0; +@@ -5346,6 +5438,13 @@ namespace StandaloneStreamBenchmark + passage,passageIndex,true); + const FWalkAudit lowerChamberAudit=auditLandingChamber(Shape, + passage,passageIndex,false); ++ const FLandingPadAudit upperPadAudit=auditLandingPad(Shape,passage,true); ++ const FLandingPadAudit lowerPadAudit=auditLandingPad(Shape,passage,false); ++ landingPadCount+=2; ++ landingPadPasses+=static_cast(upperPadAudit.passed) ++ +static_cast(lowerPadAudit.passed); ++ landingPadControls+=upperPadAudit.negativeControlsDetected ++ +lowerPadAudit.negativeControlsDetected; + const bool landingChambersWalkable=upperChamberAudit.passed + &&lowerChamberAudit.passed; + const bool passageInteriorAirPassed= +@@ -5426,6 +5525,7 @@ namespace StandaloneStreamBenchmark + floorRippleControlsDetected+=rippleAudit.floorControlDetected; + floorStepRippleControlsDetected+=rippleAudit.floorStepControlDetected; + passagePasses+=passageAudit.passed&&landingChambersWalkable ++ &&upperPadAudit.passed&&lowerPadAudit.passed + &&passageInteriorAirPassed + &&mouthsInRadius&&mouthsInContent + &&naturalAudit.passed&&mouthProfileAudit.passed&&rippleAudit.passed; +@@ -5889,6 +5989,13 @@ namespace StandaloneStreamBenchmark + static_cast(landingChamberCount), + static_cast(landingChamberCount + -landingChamberNegativeControls)); ++ std::printf("[LandingPadAuditSummary] pads=%llu passed=%llu negative_controls=%llu/%llu status=%s\n", ++ static_cast(landingPadCount), ++ static_cast(landingPadPasses), ++ static_cast(landingPadControls), ++ static_cast(landingPadCount*4), ++ landingPadCount>0&&landingPadPasses==landingPadCount ++ &&landingPadControls==landingPadCount*4?"PASS":"FAIL"); + const bool shellAuditPassed=shellAudits==passageCount + &&shellFloorViolations==0&&shellWallViolations==0 + &&shellChamberFloorViolations==0&&shellChamberWallViolations==0 +@@ -5935,6 +6042,8 @@ namespace StandaloneStreamBenchmark + ripplePasses==passageCount?"PASS":"FAIL"); + const bool auditPassed=passageCount>0&&passagePasses==passageCount + &&passageInteriorAuditPassed ++ &&landingPadCount>0&&landingPadPasses==landingPadCount ++ &&landingPadControls==landingPadCount*4 + &&naturalPasses==passageCount&&l23SmoothWallDetected + &&mouthProfilePasses==passageCount&&l23TerraceControlDetected + &&ripplePasses==passageCount