feat(core): profiled heightfields, slice 1: periodicHills, mirroredRelief, flatField (L53); research R22
WorldAssemblySurfaceProfileV1 on the native SurfaceWorld recipe and plan; version 0 'natural' is omitted from serialized identity, so the owner world is unchanged. periodicHills (seeded cosine hills, plan-time grade <= 15 deg), mirroredRelief (cap mirrors the floor by lambda, clearance checked) and flatField share one profile function for points, lattice classification and exact cosine-extrema intervals (0 interval misses in brute-force audits). Style-override fixtures with planned passages. Cold whole view about 1.33 s for each profile vs 1.74 s natural SW (Clang). Owner hashes identical to L52; fast/medium/full PASS. Research R22: SurfaceWorld critical path to about 1 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
408c14baff
commit
20f6c98ec5
17 files changed
+1620
-23
No files matched your search
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the three named style-override inputs for the profiled heightfield slice."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OWNER = ROOT / "Standalone/data/owner_world.json"
|
||||
OUT_DEFAULT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l53"))
|
||||
PROFILES = {
|
||||
"periodicHills": {
|
||||
"version": 1,
|
||||
"kind": "periodicHills",
|
||||
"amplitude_x_m": 1.5,
|
||||
"amplitude_y_m": 1.5,
|
||||
"wavelength_x_m": 96.0,
|
||||
"wavelength_y_m": 96.0,
|
||||
"minimum_arena_clearance_m": 0.0,
|
||||
},
|
||||
"mirroredRelief": {
|
||||
"version": 1,
|
||||
"kind": "mirroredRelief",
|
||||
"amplitude_x_m": 1.5,
|
||||
"amplitude_y_m": 1.5,
|
||||
"wavelength_x_m": 128.0,
|
||||
"wavelength_y_m": 128.0,
|
||||
"mirror_lambda": 0.875,
|
||||
"minimum_arena_clearance_m": 8.0,
|
||||
},
|
||||
"flatField": {
|
||||
"version": 1,
|
||||
"kind": "flatField",
|
||||
"minimum_arena_clearance_m": 0.0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--out", type=Path, default=OUT_DEFAULT / "fixtures")
|
||||
args = parser.parse_args()
|
||||
args.out.mkdir(parents=True, exist_ok=True)
|
||||
base = json.loads(OWNER.read_text(encoding="utf-8"))
|
||||
for name, override in PROFILES.items():
|
||||
fixture = dict(base)
|
||||
fixture["surface_profile_override"] = override
|
||||
path = args.out / f"style-override-profile-{name}.json"
|
||||
path.write_text(json.dumps(fixture, indent=2) + "\n", encoding="utf-8")
|
||||
print(f"[L53ProfileFixture] name={path.stem} profile={name} path={path} status=PASS")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Capture eye-height and overview views for the three L53 heightfield profiles."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT_DEFAULT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l53"))
|
||||
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
|
||||
PROFILES = ("periodicHills", "mirroredRelief", "flatField")
|
||||
Q_PER_METRE = 1024.0
|
||||
|
||||
sys.path.insert(0, str(ROOT / "Standalone/Tools/verify"))
|
||||
from runner import bounded_run, current_stamp, first_stamp
|
||||
|
||||
|
||||
def fmt(values: tuple[float, ...]) -> str:
|
||||
return ",".join(f"{value:.6f}" for value in values)
|
||||
|
||||
|
||||
def route_frame(manifest_path: Path) -> tuple[tuple[float, float, float],
|
||||
tuple[float, float, float]]:
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
# Core profile coordinates are 1/256 of a Core voxel. In this world that is 1024 q/m.
|
||||
scale = 1.0 / Q_PER_METRE
|
||||
candidates = [entry for entry in manifest["plan"]["strates"]
|
||||
if entry.get("route") and len(entry["route"]) >= 2]
|
||||
if not candidates:
|
||||
raise RuntimeError(f"no planned route points in {manifest_path}")
|
||||
route = candidates[0]["route"]
|
||||
points = [tuple(float(value) * scale for value in item["position_q"])
|
||||
for item in route]
|
||||
lengths = [math.dist(a, b) for a, b in zip(points, points[1:])]
|
||||
total = sum(lengths)
|
||||
if total <= 1.0e-6:
|
||||
raise RuntimeError(f"degenerate planned route in {manifest_path}")
|
||||
|
||||
def at(distance: float) -> tuple[float, float, float]:
|
||||
left = max(0.0, min(total, distance))
|
||||
for a, b, length in zip(points, points[1:], lengths):
|
||||
if left <= length or (a, b) == (points[-2], points[-1]):
|
||||
if length <= 1.0e-9:
|
||||
return b
|
||||
t = left / length
|
||||
return tuple(a[axis] + (b[axis] - a[axis]) * t for axis in range(3))
|
||||
left -= length
|
||||
return points[-1]
|
||||
|
||||
center = at(total * 0.5)
|
||||
before, after = at(total * 0.5 - 1.0), at(total * 0.5 + 1.0)
|
||||
dx, dy = after[0] - before[0], after[1] - before[1]
|
||||
length = math.hypot(dx, dy)
|
||||
if length <= 1.0e-9:
|
||||
raise RuntimeError(f"route tangent is degenerate in {manifest_path}")
|
||||
return center, (dx / length, dy / length, 0.0)
|
||||
|
||||
|
||||
def run_step(name: str, command: list[str], log: Path, *, env: dict[str, str],
|
||||
stamped: bool = False) -> None:
|
||||
expected = current_stamp() if stamped else None
|
||||
if stamped:
|
||||
binary = Path(command[0])
|
||||
probe = bounded_run([str(binary), "--print-source-stamp"], timeout=15)
|
||||
if probe is None or probe.returncode != 0 or first_stamp(probe.stdout or "") != expected:
|
||||
raise RuntimeError(f"{name} refused an unstamped or stale executable: {binary}")
|
||||
started = time.monotonic()
|
||||
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)
|
||||
elapsed = time.monotonic() - started
|
||||
print(f"[L53CaptureRun] step={name} elapsed={elapsed:.1f}s exit={result.returncode} log={log}",
|
||||
flush=True)
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
if stamped and (first_stamp(output) != expected or current_stamp() != expected):
|
||||
raise RuntimeError(f"{name} source stamp mismatch; see {log}")
|
||||
if name.endswith("-quality"):
|
||||
for line in output.splitlines():
|
||||
if line.startswith("[CaptureQuality]"):
|
||||
print(line, flush=True)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"{name} failed; see {log}")
|
||||
|
||||
|
||||
def camera_angles(camera: tuple[float, float, float],
|
||||
target: tuple[float, float, float]) -> 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 main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--out", type=Path, default=OUT_DEFAULT)
|
||||
parser.add_argument("--fixture-dir", type=Path)
|
||||
parser.add_argument("--artifact-dir", type=Path,
|
||||
default=ROOT / "Standalone/Research/WorldAssemblerM3/Linux53/captures")
|
||||
parser.add_argument("--headless", type=Path, required=True)
|
||||
parser.add_argument("--renderer", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
out = args.out.resolve()
|
||||
fixture_dir = (args.fixture_dir or out / "fixtures").resolve()
|
||||
artifact_dir = args.artifact_dir.resolve()
|
||||
png_dir = out / "capture-png"
|
||||
mesh_dir = out / "profile-meshes"
|
||||
for directory in (out, png_dir, mesh_dir, artifact_dir):
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
env = os.environ.copy()
|
||||
env["LP_NUM_THREADS"] = "12"
|
||||
env["XDG_CACHE_HOME"] = str(out / "xdg-cache")
|
||||
|
||||
for profile in PROFILES:
|
||||
fixture = fixture_dir / f"style-override-profile-{profile}.json"
|
||||
manifest = fixture_dir / f"style-override-profile-{profile}-manifest.json"
|
||||
if not fixture.is_file() or not manifest.is_file():
|
||||
raise RuntimeError(f"missing profile fixture or manifest for {profile}")
|
||||
center, tangent = route_frame(manifest)
|
||||
cx, cy, cz = center
|
||||
tx, ty, _ = tangent
|
||||
eye = (cx, cy, cz + 0.72)
|
||||
eye_target = (cx + tx * 15.0, cy + ty * 15.0, cz + 0.72)
|
||||
overview_camera = (cx - tx * 48.0 - ty * 25.0,
|
||||
cy - ty * 48.0 + tx * 25.0, cz + 43.0)
|
||||
overview_target = (cx + tx * 22.0, cy + ty * 22.0, cz + 0.4)
|
||||
|
||||
for view, camera, target, clip_radius in (
|
||||
("eye", eye, eye_target, 3),
|
||||
("overview", overview_camera, overview_target, 6),
|
||||
):
|
||||
stem = f"{profile}-{view}"
|
||||
mesh = mesh_dir / f"{stem}.vfmesh"
|
||||
png = png_dir / f"{stem}.png"
|
||||
image = artifact_dir / f"{stem}.jpg"
|
||||
yaw, pitch = camera_angles(camera, target)
|
||||
camera_spec = (*camera, yaw, pitch)
|
||||
headless_command = [args.headless, fixture, "--workers=12",
|
||||
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||
"--force-generate", f"--clip-radius={clip_radius}",
|
||||
"--max-clip-level=0", "--max-level=0", "--render-distance-chunks=0",
|
||||
"--recipe=surface-world", f"--camera={fmt(camera_spec)}",
|
||||
f"--mesh-export={mesh}",
|
||||
f"--world-assembler-manifest-output={manifest}"]
|
||||
run_step(f"{stem}-export", headless_command,
|
||||
out / f"capture-{stem}-export.log", env=env, stamped=True)
|
||||
if not mesh.is_file() or mesh.stat().st_size == 0:
|
||||
raise RuntimeError(f"headless export produced no mesh for {stem}")
|
||||
focus = (*target, 54.0 if view == "overview" else 26.0)
|
||||
renderer_command = [args.renderer, mesh, png, "--size=1600x900",
|
||||
"--fov-deg=72", 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", f"--height-center-m={cz:.6f}",
|
||||
"--height-range-m=10", "--tile-edges=off", f"--focus-region={fmt(focus)}"]
|
||||
run_step(f"{stem}-render", renderer_command,
|
||||
out / f"capture-{stem}-render.log", env=env, stamped=True)
|
||||
run_step(f"{stem}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner",
|
||||
"-loglevel", "error", "-y", "-i", png, "-frames:v", "1", "-q:v", "4", image],
|
||||
out / f"capture-{stem}-jpeg.log", env=env)
|
||||
quality = ROOT / "Standalone/Tools/verify/capture_quality.py"
|
||||
run_step(f"{stem}-quality", [sys.executable, quality, image],
|
||||
out / f"capture-{stem}-quality.log", env=env)
|
||||
if not image.is_file() or not 0 < image.stat().st_size <= 300_000:
|
||||
raise RuntimeError(f"capture size is invalid for {stem}")
|
||||
print(f"[L53HeightfieldCapture] profile={profile} view={view} file={image} "
|
||||
f"camera_m=({fmt(camera)}) target_m=({fmt(target)}) "
|
||||
f"bytes={image.stat().st_size} status=PASS", flush=True)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as error:
|
||||
print(f"[L53HeightfieldCapture] status=FAIL error={error}", file=sys.stderr, flush=True)
|
||||
raise SystemExit(1)
|
||||
@@ -0,0 +1,53 @@
|
||||
# Linux 53 — profiled heightfields, slice 1
|
||||
|
||||
## Profile API
|
||||
|
||||
`WorldAssemblySurfaceProfileV1` is attached to the native SurfaceWorld recipe and stratum plan. Version 0 is the all-zero `natural` profile; it is omitted from serialized identity, preserving the existing owner world. Non-default profiles use version 1, serialize into recipe and plan JSON, and contribute to canonical hashes. Dimensions use fixed metre-derived subvoxels, and periodic phases are derived deterministically from the recipe seed and axis.
|
||||
|
||||
## Profiles and bounds
|
||||
|
||||
- **natural** retains today's noise heightfield.
|
||||
- **periodicHills** uses `h = h0 + Ax cos(2πx/Lx + φx) + Ay cos(2πy/Ly + φy)`. The fixture uses `Ax=Ay=1.5 m`, `Lx=Ly=96 m`; seeded phases are Q0.32 turns `1258571205` and `82173144`. Its maximum analytic grade is 7.90° (the plan-time limit is 15°). Supported ranges follow R20: amplitudes 0.5–3 m and wavelengths 80–160 m.
|
||||
- **mirroredRelief** uses `g = Ax cos(...) + Ay cos(...)`, floor `h0+g`, and ceiling `c0−λg`. The fixture uses 1.5 m amplitudes, 128 m wavelengths, and `λ=0.875`. R20 ranges are 0.5–2 m, 80–160 m, and `λ=0.75–1`. The plan checks `c0−h0−(1+λ)(Ax+Ay)` against the required clearance; the fixture requests 8 m. Its maximum grade is 5.94°.
|
||||
- **flatField** sets the floor to `h0` and retains the plan's flat cap.
|
||||
|
||||
For each horizontal box, the cosine range checks its endpoints and all included stationary points; spans of a full period use `[-1,1]`. The two axis ranges are combined for floor and mirrored-cap bounds. Fixed-point rounding is padded outward by two subvoxels; flat-field bounds are constant. The same profile function feeds point evaluation, lattice classification, and intervals. Each profile audit checked 96 boxes against 23,328 brute-force density samples: zero interval misses and zero classifier mismatches. Each also certified 24 air and 24 solid boxes; the separate bound audit checked 265 boxes and 7,155 samples. The density sign remains negative for solid and nonnegative for air.
|
||||
|
||||
## Fixtures, routes, and captures
|
||||
|
||||
The fixtures are named `style-override-profile-periodicHills.json`, `style-override-profile-mirroredRelief.json`, and `style-override-profile-flatField.json` under `Saved/Linux/l53/fixtures/`. Each plans an inter-stratum arrival/departure passage and a 32-segment supported route. Profile proofs and passage audits pass, including interface-band and negative-thickness controls. Measured route grades are 7.904°, 5.945°, and 0° respectively; mirrored clearance is at least the requested 8 m.
|
||||
|
||||
Eye-height and overview captures all pass quality gates (6/6):
|
||||
|
||||
- Periodic hills: [eye](captures/periodicHills-eye.jpg), [overview](captures/periodicHills-overview.jpg)
|
||||
- Mirrored relief: [eye](captures/mirroredRelief-eye.jpg), [overview](captures/mirroredRelief-overview.jpg)
|
||||
- Flat field: [eye](captures/flatField-eye.jpg), [overview](captures/flatField-overview.jpg)
|
||||
|
||||
## Timings and hashes
|
||||
|
||||
Cold whole-view readiness, seconds:
|
||||
|
||||
| Compiler | Natural SurfaceWorld | periodicHills | mirroredRelief | flatField |
|
||||
|---|---:|---:|---:|---:|
|
||||
| Clang 19 | 1.742 | 1.336 | 1.323 | 1.330 |
|
||||
| GCC | 1.911 | 1.436 | 1.409 | 1.382 |
|
||||
|
||||
Both compilers were freshly rebuilt from this source stamp with FP contraction off and 12 jobs. Every profile is under the 1.75 s budget; periodic hills and flat field are faster than their natural SurfaceWorld reference. Profile layout IDs are `bf3b90bc4678497e` (periodic), `bd45e0817b6338fa` (mirrored), and `dea9d3b61447f9b1` (flat).
|
||||
|
||||
Floating Islands and natural SurfaceWorld owner hashes each match L52 exactly: 12/12 canonical rows on Clang and GCC at W12. Profile worker hashes match at W1/W8/W12, and seeded random-camera geometry/material hashes match across Clang and GCC. Source stamp: `99389e29037f59841b46b31f16899355b718ba8def14ff73c43f8900dbb75c0b`.
|
||||
|
||||
## Verification and failed runs
|
||||
|
||||
Fast passed in 149.6 s; medium passed in 606.2 s; full passed once in 1,088.3 s. Full included 30-cell determinism and five paired baseline cost measurements. The median candidate changes were +0.87% for SurfaceWorld and +0.84% for Floating Islands.
|
||||
|
||||
Earlier trials exposed a lower-landing capsule-support failure and a transition-audit harness short-circuit; route radius conversion was corrected and transition checks were split. One medium attempt then failed its speed gate because passage audits contaminated `whole_view_ready` (21.666 s). The final tier measures cold views separately without audit flags and passes at the times above. No final tier gates failed.
|
||||
|
||||
## Orchestrator review (Claude)
|
||||
|
||||
Accepted as code. The stamp matches the final Clang and GCC builds (`99389e29…`); the owner hashes are identical to
|
||||
L52; profile proofs show 0 misses. **All three profiles run faster than natural SW** (about 1.33 s vs 1.74 s on Clang).
|
||||
**The captures don't show the profiles.** The eye views are a single soft horizon against black: the cap is never
|
||||
visible, so mirrored relief can't be judged. The "periodicHills overview" frames the FI anchor spiral column from the
|
||||
upper strate, which still shows its old shard and terrace artifacts, instead of the hills. The next profile round
|
||||
must frame the hill field and the mirrored ceiling, for example a wide eye-level view with the cap in frame and a
|
||||
high oblique overview over several periods.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Reference in new issue
Block a user