Files
VoxelForge/Standalone/Research/WorldAssemblerM3/Round2/RenderFloatingIslandRound2.py
Fr0zkaandClaude Opus 5.5 4afea7c802 wip(assembler): M3 round 2 -- islands get real mass: deep crust, separate tapered roots, arched spans
Pinch cause: SmoothStep taper drove the underside radius to zero at one point. Version 2: depth 0.75x
diameter (thin-pad control at 0.30 fails), 3-4 seeded roots, bounded contour warp + roughness, one
off-route side island, 3 m rock spans. Bounds/route/W1-W8-W16 pass. Open: legacy probe metadata says
25 cm but production legacy is 12.5 cm -- round 3 must fix the import scale contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-10-10 12:24:35 +02:00

287 lines
13 KiB
Python

#!/usr/bin/env python3
"""Small CPU-only matched views of the legacy and native FloatingIslands fields."""
from __future__ import annotations
import ctypes
import json
import math
import struct
from pathlib import Path
from PIL import Image, ImageDraw
ROOT = Path(__file__).resolve().parent
RESULTS = ROOT / "results"
CAPTURES = RESULTS / "captures"
CAPTURES.mkdir(parents=True, exist_ok=True)
WIDTH, HEIGHT = 320, 226
BACKGROUND = (236, 240, 242)
LEGACY_COLOR = (133, 127, 105)
NATIVE_COLOR = (78, 135, 151)
ROUTE_COLOR = (223, 72, 45)
TILE_COLOR = (178, 60, 52)
LOD_COLOR = (211, 136, 45)
Q = 256.0
def lower_priority() -> None:
if hasattr(ctypes, "windll"):
ctypes.windll.kernel32.SetPriorityClass(ctypes.windll.kernel32.GetCurrentProcess(), 0x00004000)
def read_mesh(path: Path):
raw = path.read_bytes()
if len(raw) < 8 or raw[:4] != b"VFMI":
raise RuntimeError(f"invalid field mesh: {path}")
count = struct.unpack_from("<I", raw, 4)[0]
stride = struct.calcsize("<12f")
if len(raw) != 8 + count * stride:
raise RuntimeError(f"truncated field mesh: {path}")
triangles = [struct.unpack_from("<12f", raw, 8 + i * stride) for i in range(count)]
vertices = [tri[0:3] for tri in triangles]
vertices.extend(tri[3:6] for tri in triangles)
vertices.extend(tri[6:9] for tri in triangles)
return triangles, vertices
def normalize(vector):
size = math.sqrt(sum(value * value for value in vector))
return tuple(value / size for value in vector) if size > 1.0e-12 else (0.0, 0.0, 1.0)
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
def cross(a, b):
return (a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0])
def project(points, target, view_dir, scale=None):
direction = normalize(view_dir)
forward = tuple(-value for value in direction)
reference_up = (0.0, 1.0, 0.0) if abs(forward[2]) > 0.98 else (0.0, 0.0, 1.0)
right = normalize(cross(forward, reference_up))
up = normalize(cross(right, forward))
camera_points = []
for point in points:
delta = tuple(point[i] - target[i] for i in range(3))
camera_points.append((dot(delta, right), dot(delta, up), dot(delta, forward)))
if scale is None:
span_x = max((p[0] for p in camera_points), default=1.0) - min((p[0] for p in camera_points), default=0.0)
span_y = max((p[1] for p in camera_points), default=1.0) - min((p[1] for p in camera_points), default=0.0)
scale = 0.86 * min((WIDTH - 30) / max(span_x, 1.0e-6), (HEIGHT - 30) / max(span_y, 1.0e-6))
pixels = [
(WIDTH * 0.5 + point[0] * scale, 31 + (HEIGHT - 31) * 0.5 - point[1] * scale, point[2])
for point in camera_points
]
return pixels, scale, right, up, forward
def common_scale(mesh_pair, target, view_dir):
vertices = mesh_pair[0][1] + mesh_pair[1][1]
_, scale, _, _, _ = project(vertices, target, view_dir)
return scale
def projected_line(a, b, target, basis, scale):
right, up, forward = basis
points = []
for point in (a, b):
delta = tuple(point[i] - target[i] for i in range(3))
points.append((WIDTH * 0.5 + dot(delta, right) * scale,
31 + (HEIGHT - 31) * 0.5 - dot(delta, up) * scale))
return points
def render(mesh, mesh_pair, method, target, view_dir, title, subtitle, *, route=None,
guide=None, camera_eye=None, framing_radius=None):
triangles, vertices = mesh
perspective = camera_eye is not None
if perspective:
forward = normalize(tuple(target[i] - camera_eye[i] for i in range(3)))
reference_up = (0.0, 1.0, 0.0) if abs(forward[2]) > 0.98 else (0.0, 0.0, 1.0)
right = normalize(cross(forward, reference_up))
up = normalize(cross(right, forward))
scale = WIDTH * 0.91
origin = camera_eye
else:
if framing_radius is None:
scale = common_scale(mesh_pair, target, view_dir)
else:
scale = 0.86 * min((WIDTH - 30) / (2.0 * framing_radius),
(HEIGHT - 30) / (2.0 * framing_radius))
_, _, right, up, forward = project((), target, view_dir, scale)
origin = target
def screen(point):
delta = tuple(point[i] - origin[i] for i in range(3))
cx, cy, depth = dot(delta, right), dot(delta, up), dot(delta, forward)
if perspective:
if depth <= 1.0:
return None
return (WIDTH * 0.5 + cx * scale / depth,
31 + (HEIGHT - 31) * 0.5 - cy * scale / depth, depth)
return (WIDTH * 0.5 + cx * scale,
31 + (HEIGHT - 31) * 0.5 - cy * scale, depth)
background = Image.new("RGB", (WIDTH, HEIGHT + 32), BACKGROUND)
draw = ImageDraw.Draw(background)
draw.rectangle((0, 0, WIDTH - 1, 31), fill=(34, 46, 54))
draw.text((8, 4), title, fill=(250, 252, 253))
draw.text((8, 17), subtitle, fill=(190, 207, 215))
pixels = background.load()
zbuffer = [float("inf")] * (WIDTH * (HEIGHT - 31))
camera_fill = forward if perspective else normalize(view_dir)
light = normalize((-0.25 + 1.2 * camera_fill[0],
-0.35 + 1.2 * camera_fill[1],
0.45 + 1.2 * camera_fill[2]))
base = LEGACY_COLOR if method == "legacy" else NATIVE_COLOR
for tri in triangles:
points = [screen(tri[offset:offset + 3]) for offset in (0, 3, 6)]
if any(point is None for point in points):
continue
x0 = max(0, int(math.floor(min(p[0] for p in points))))
x1 = min(WIDTH - 1, int(math.ceil(max(p[0] for p in points))))
y0 = max(31, int(math.floor(min(p[1] for p in points))))
y1 = min(HEIGHT - 1, int(math.ceil(max(p[1] for p in points))))
if x0 > x1 or y0 > y1:
continue
(ax, ay, az), (bx, by, bz), (cx, cy, cz) = points
denominator = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy)
if abs(denominator) < 1.0e-10:
continue
normal = normalize(tri[9:12])
shade = 0.50 + 0.50 * max(0.0, dot(normal, light))
color = tuple(max(0, min(255, int(channel * shade))) for channel in base)
for y in range(y0, y1 + 1):
row = (y - 31) * WIDTH
for x in range(x0, x1 + 1):
sx, sy = x + 0.5, y + 0.5
u = ((by - cy) * (sx - cx) + (cx - bx) * (sy - cy)) / denominator
v = ((cy - ay) * (sx - cx) + (ax - cx) * (sy - cy)) / denominator
w = 1.0 - u - v
if u < -1e-8 or v < -1e-8 or w < -1e-8:
continue
if perspective:
depth = 1.0 / (u / az + v / bz + w / cz)
else:
depth = u * az + v * bz + w * cz
index = row + x
if depth < zbuffer[index]:
zbuffer[index] = depth
pixels[x, y] = color
basis = (right, up, forward)
if guide:
origin = guide["origin_voxels"]
top = guide["top_voxels"]
for x_delta, color, label in ((32.0, TILE_COLOR, "32v tile"), (64.0, LOD_COLOR, "64v LOD1")):
line = projected_line((origin[0] + x_delta, origin[1] - 190.0, top - 65.0),
(origin[0] + x_delta, origin[1] + 190.0, top + 14.0),
target, basis, scale)
draw.line(line, fill=color, width=2)
draw.text((max(2, min(WIDTH - 55, int(line[1][0]) + 2)),
max(33, min(HEIGHT - 11, int(line[1][1]) - 12))), label, fill=color)
if route:
points = [screen(point) for point in route]
points = [point[:2] for point in points if point is not None]
if len(points) >= 2:
draw.line(points, fill=(250, 248, 239), width=5, joint="curve")
draw.line(points, fill=ROUTE_COLOR, width=3, joint="curve")
for index in (0, len(points) - 1):
x, y = points[index]
draw.ellipse((x - 4, y - 4, x + 4, y + 4), fill=(250, 248, 239), outline=ROUTE_COLOR, width=2)
return background
def titled_pair(left, right, category):
pair = Image.new("RGB", (WIDTH * 2, HEIGHT + 32), (220, 226, 229))
pair.paste(left, (0, 0))
pair.paste(right, (WIDTH, 0))
draw = ImageDraw.Draw(pair)
draw.rectangle((0, 0, WIDTH * 2 - 1, HEIGHT + 31), outline=(72, 84, 91), width=1)
draw.text((8, HEIGHT + 14), f"{category} | same target, camera and scale", fill=(45, 55, 61))
return pair
def main():
lower_priority()
data = json.loads((RESULTS / "audit.json").read_text(encoding="utf-8"))
anchor = data["comparison_anchor_core_voxels"]
route = data["route_points_core_voxels"]
profiles = data["island_profiles"]
middle_island = profiles[1]
cluster = {
"legacy": read_mesh(RESULTS / "cluster_legacy.vfmesh"),
"native": read_mesh(RESULTS / "cluster_native.vfmesh"),
}
detail = {
"legacy": read_mesh(RESULTS / "detail_legacy.vfmesh"),
"native": read_mesh(RESULTS / "detail_native.vfmesh"),
}
a = anchor
scenes = (
("overview", "Cluster overview", cluster, (a[0], a[1], a[2] - 28.0), (8.0, -10.0, 7.0), None, None),
("underside", "Cluster underside and lobed roots", detail,
(middle_island["center_voxels"][0], middle_island["center_voxels"][1],
middle_island["bottom_voxels"] - 6.0), (0.12, -0.08, -1.0), None, None),
("edge_cliff", "Thick crust and bounded rock detail", detail,
(middle_island["center_voxels"][0], middle_island["center_voxels"][1],
middle_island["top_voxels"] - middle_island["radius_voxels"] * 0.65),
(1.0, -0.25, 0.18), None, None),
("route", "Supported route across the cluster", cluster,
(middle_island["center_voxels"][0], middle_island["center_voxels"][1],
middle_island["top_voxels"] - 18.0), (8.0, -10.0, 7.0), route, None),
)
pairs = []
for key, title, meshes, target, camera, path, grid in scenes:
framing_radius = 28.0 if key in ("underside", "edge_cliff") else None
left = render(meshes["legacy"], (meshes["legacy"], meshes["native"]), "legacy",
target, camera, title, "Legacy bridge | exact owner field", route=None,
guide=grid, framing_radius=framing_radius)
route_caption = "Core native | planner route" if path else "Core native | matched field view"
right = render(meshes["native"], (meshes["legacy"], meshes["native"]), "native",
target, camera, title, route_caption, route=path, guide=grid,
framing_radius=framing_radius)
pair = titled_pair(left, right, title)
out = CAPTURES / f"{key}_matched.jpg"
pair.save(out, quality=82, optimize=True)
pairs.append(pair)
print(f"capture={out.name} bytes={out.stat().st_size} size={pair.size[0]}x{pair.size[1]}")
eye_index = 5
eye_route = route[eye_index]
eye = (eye_route[0], eye_route[1], eye_route[2] + 3.68)
next_island = profiles[1]
look = (next_island["center_voxels"][0], next_island["center_voxels"][1],
next_island["top_voxels"] + 5.0)
perspective_pair = []
for method, meshes in (("legacy", cluster), ("native", cluster)):
perspective_pair.append(render(meshes[method], (meshes["legacy"], meshes["native"]), method,
look, (0.0, 0.0, -1.0), "Island-edge player view", "Eye at 1.8 m | route span to next island",
route=None, camera_eye=eye))
eye_pair = titled_pair(perspective_pair[0], perspective_pair[1], "Island-edge player view")
eye_path = CAPTURES / "player_eye_matched.jpg"
eye_pair.save(eye_path, quality=80, optimize=True)
pairs.insert(3, eye_pair)
print(f"capture={eye_path.name} bytes={eye_path.stat().st_size} size={eye_pair.size[0]}x{eye_pair.size[1]}")
sheet = Image.new("RGB", (WIDTH * 2, (HEIGHT + 32) * len(pairs)), (218, 225, 228))
for row, pair in enumerate(pairs):
sheet.paste(pair, (0, row * (HEIGHT + 32)))
sheet_path = CAPTURES / "contact_sheet.jpg"
sheet.save(sheet_path, quality=76, optimize=True)
oversized = [(path.name, path.stat().st_size) for path in sorted(CAPTURES.glob("*.jpg"))
if path.stat().st_size >= 300_000]
for path in sorted(CAPTURES.glob("*.jpg")):
print(f"capture_size={path.name} bytes={path.stat().st_size}")
if oversized:
raise RuntimeError(f"captures exceed the 300 KB limit: {oversized}")
print(f"contact_sheet={sheet_path.name} bytes={sheet_path.stat().st_size} views=5 matched_pairs=5")
if __name__ == "__main__":
main()