Files
VoxelForge/Standalone/Research/WorldAssemblerM3/Linux149/MouthAudit.py
T
Fr0zkaandClaude Opus 5.5 b8382eed84 research: one continuous tube through the mouth (L149, 0/16; kept as patch)
Mouth edges matched passage position, radius, tangent (dot 0.99999) and floor slope, but the cubic departure edge
still curls ~1 m back alongside the passage within 5 m; shell floor violations remain; composed sweeps 0/16.
Sixth mouth-join round (L144-L149): stop and take the design question to the owner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-01 15:24:49 +01:00

170 lines
7.9 KiB
Python

#!/usr/bin/env python3
"""Structural mouth continuity and forward-extension checks for L149 layouts."""
from __future__ import annotations
import json
import math
from pathlib import Path
from typing import Any
def _q_to_um(value: int, core_voxel_size_um: int) -> int:
return int(value * core_voxel_size_um / 256)
def _mouth(layout: dict[str, Any], edge: dict[str, Any]) -> tuple[dict[str, Any], int,
list[int], int] | None:
anchor = edge.get("start_anchor_stable_id", "")
if not anchor.endswith(("/lower-mouth", "/upper-mouth")):
return None
side = anchor.rsplit("/", 1)[1]
passage_id = anchor.rsplit("/", 1)[0]
passage = next((row for row in layout.get("passages", [])
if row.get("stable_id") == passage_id), None)
if passage is None:
return None
key = "lower_mouth_q" if side == "lower-mouth" else "upper_mouth_q"
mouth_q = passage.get(key)
controls = passage.get("control_points", [])
if not mouth_q or not controls:
return None
if side == "upper-mouth":
index = 0
else:
center_height_q = round(1.13 * 256_000_000 / layout["core_voxel_size_um"])
matches = [i for i, point in enumerate(controls)
if point["position_q"][0] == mouth_q[0]
and point["position_q"][1] == mouth_q[1]
and point["position_q"][2] - point["radius_q"] + center_height_q
== mouth_q[2]]
if not matches:
return None
index = matches[-1]
return passage, index, mouth_q, _q_to_um(controls[index]["radius_q"],
layout["core_voxel_size_um"])
def _dot_direction(a: list[float], b: list[float]) -> float:
al, bl = math.sqrt(sum(x * x for x in a)), math.sqrt(sum(x * x for x in b))
return sum(x * y for x, y in zip(a, b)) / (al * bl) if al > 0 and bl > 0 else -1.0
def _bezier(points: list[list[int]], t: float) -> list[float]:
u = 1.0 - t
return [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 _metrics(layout: dict[str, Any], edge: dict[str, Any]) -> dict[str, Any]:
found = _mouth(layout, edge)
if found is None:
return {"bound": False, "tangent_dot": -1.0, "radius_match": False,
"position_match": False, "floor_slope_delta": math.inf,
"forward_extension": False}
passage, index, _, expected_radius = found
controls = passage["control_points"]
route = edge.get("control_points_um", [])
if len(route) != 4 or len(controls) < 2:
return {"bound": False, "tangent_dot": -1.0, "radius_match": False,
"position_match": False, "floor_slope_delta": math.inf,
"forward_extension": False}
side = edge["start_anchor_stable_id"].rsplit("/", 1)[1]
neighbor = 1 if side == "upper-mouth" else index - 1
if neighbor < 0 or neighbor >= len(controls):
return {"bound": False, "tangent_dot": -1.0, "radius_match": False,
"position_match": False, "floor_slope_delta": math.inf,
"forward_extension": False}
mouth_point = controls[index]
neighbor_point = controls[neighbor]
core = layout["core_voxel_size_um"]
expected = [_q_to_um(v, core) for v in mouth_point["position_q"]]
expected[2] -= expected_radius
expected_dir = [_q_to_um(mouth_point["position_q"][axis]
- neighbor_point["position_q"][axis], core)
for axis in range(2)]
edge_dir = [route[1][0] - route[0][0], route[1][1] - route[0][1]]
position_match = all(abs(route[0][axis] - expected[axis]) <= 2 for axis in range(3))
radius_match = edge.get("bore_radius_um") == expected_radius
tangent_dot = _dot_direction([*edge_dir, 0.0], [*expected_dir, 0.0])
floor_mouth = mouth_point["position_q"][2] - mouth_point["radius_q"]
floor_neighbor = neighbor_point["position_q"][2] - neighbor_point["radius_q"]
expected_slope = _q_to_um(floor_mouth - floor_neighbor, core) / max(
1.0, math.hypot(*expected_dir))
if side == "upper-mouth":
expected_slope = -expected_slope
route_slope = (route[1][2] - route[0][2]) / max(1.0, math.hypot(*edge_dir))
floor_slope_delta = abs(route_slope - expected_slope)
tangent_length = math.hypot(*expected_dir)
tangent = [v / tangent_length for v in expected_dir] if tangent_length else [0.0, 0.0]
points = [list(p) for p in route]
previous = _bezier(points, 0.0)
distance = 0.0
prior_progress = 0.0
forward = True
for step in range(1, 1025):
current = _bezier(points, step / 1024.0)
dx, dy = current[0] - previous[0], current[1] - previous[1]
segment = math.hypot(dx, dy)
next_distance = distance + segment
if distance < 12_000_000:
progress = ((current[0] - route[0][0]) * tangent[0]
+ (current[1] - route[0][1]) * tangent[1])
if progress < -100_000 or progress < prior_progress - 100_000:
forward = False
prior_progress = progress
previous, distance = current, next_distance
if distance >= 12_000_000:
break
return {"bound": True, "tangent_dot": tangent_dot,
"radius_match": radius_match, "position_match": position_match,
"floor_slope_delta": floor_slope_delta, "forward_extension": forward}
def audit_layout(layout_path: Path) -> dict[str, Any]:
layout = json.loads(layout_path.read_text(encoding="utf-8"))
mouth_edges = [edge for edge in layout.get("cavern_edges", [])
if edge.get("strate_index") in (2, 3)
and edge.get("stable_id", "").endswith(("route-edge-0", "departure-edge"))
and edge.get("start_anchor_stable_id", "").endswith(
("/lower-mouth", "/upper-mouth"))]
rows = [_metrics(layout, edge) for edge in mouth_edges]
negative_controls = 0
for edge in mouth_edges:
found = _mouth(layout, edge)
if found is None:
continue
passage, index, _, _ = found
side = edge["start_anchor_stable_id"].rsplit("/", 1)[1]
neighbor = 1 if side == "upper-mouth" else index - 1
mouth_point = passage["control_points"][index]
neighbor_point = passage["control_points"][neighbor]
core = layout["core_voxel_size_um"]
expected_dir = [_q_to_um(mouth_point["position_q"][axis]
- neighbor_point["position_q"][axis], core)
for axis in range(2)]
altered = dict(edge)
points = [list(p) for p in edge["control_points_um"]]
length = max(1.0, math.hypot(*expected_dir))
points[1][0] = points[0][0] - round(expected_dir[0] / length * 1_000_000)
points[1][1] = points[0][1] - round(expected_dir[1] / length * 1_000_000)
altered["control_points_um"] = points
control = _metrics(layout, altered)
if control["tangent_dot"] < 0.9:
negative_controls += 1
continuity = bool(rows) and all(
row["bound"] and row["position_match"] and row["radius_match"]
and row["tangent_dot"] >= 0.9 and row["floor_slope_delta"] <= 0.15
for row in rows)
no_backtrack = bool(rows) and all(row["forward_extension"] for row in rows)
controls_ok = bool(mouth_edges) and negative_controls == len(mouth_edges)
return {"edges": len(mouth_edges), "continuity": continuity,
"no_backtrack_12m": no_backtrack, "negative_controls": negative_controls,
"negative_controls_expected": len(mouth_edges), "negative_controls_pass": controls_ok,
"minimum_tangent_dot": min((row["tangent_dot"] for row in rows), default=-1.0),
"maximum_floor_slope_delta": max(
(row["floor_slope_delta"] for row in rows), default=math.inf),
"rows": rows}