passages: continuous floor h(s) along the dense route; shell-probe oracle fix (L115, v223)
Floor height from the planner profile h(s) by horizontal projection + arc-length interpolation; adjacent fine segments share endpoint values. Floor zero-crossing offsets max 7.8 mm (L114: up to 73 mm); rock-floor close-ups show no ribs. The 2 L114 shell misses were oracle false positives (points inside an overlapping bore); the oracle now checks every overlapping span; no gate relaxed. Fast tier PASS; Clang/GCC W1/W8/W12 deterministic. Speed regression grows: near-ready +17..+31% vs L109 (fine-segment/sample pairs 49.0M vs 19.8M). Not merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
No files matched your search
@@ -0,0 +1,46 @@
|
||||
# Linux115 report
|
||||
|
||||
Source version 223. Floor height now comes from the planner profile `h(s)` on the dense route, using horizontal projection and arc-length interpolation. In-range projections interpolate on their fine segment; out-of-range projections use the global profile lookup. Adjacent fine segments share the same endpoint value. The plan-time grade audit remains enabled: maximum sampled grade was 10.687° (limit 15°).
|
||||
|
||||
## Floor zero crossings
|
||||
|
||||
Each station has 51 density samples. Offsets below are in millimetres.
|
||||
|
||||
| Passage | Station | Max absolute | Median absolute | P95 absolute |
|
||||
|---|---:|---:|---:|---:|
|
||||
| 0–1 | 25% | 0.088 | 0.009 | 0.066 |
|
||||
| 0–1 | 50% | 0.079 | 0.016 | 0.064 |
|
||||
| 0–1 | 75% | 0.225 | 0.016 | 0.161 |
|
||||
| 1–2 | 25% | 7.475 | 7.259 | 7.459 |
|
||||
| 1–2 | 50% | 7.272 | 7.215 | 7.265 |
|
||||
| 1–2 | 75% | 7.272 | 7.121 | 7.245 |
|
||||
| 2–3 | 25% | 7.849 | 7.712 | 7.844 |
|
||||
| 2–3 | 50% | 7.712 | 7.351 | 7.694 |
|
||||
| 2–3 | 75% | 7.573 | 7.506 | 7.566 |
|
||||
|
||||
Maximum offset is 7.849 mm, below the 10 mm target. Full data: [CSV](../../../../Saved/Linux/l115/density-probe/zero-crossing-offsets.csv) and [summary](../../../../Saved/Linux/l115/density-probe/zero-crossing-summary.json).
|
||||
|
||||
## Shell probes and captures
|
||||
|
||||
The two L114 southwest wall misses were probe-oracle false positives. Both positions were inside an intentionally carved overlapping passage bore, where host air is correct; the audit had only considered the nearest route span when classifying bore interior. The oracle now recognizes interior air on any overlapping route span. No wall gate was relaxed. The final two-strata audit reports 0 wall violations in 31,361 probes (8,848 overlap-interior skips), 0 floor violations, and 0 interior violations.
|
||||
|
||||
The final capture set has 23/23 programmatically verified images: 9 rock-floor close-ups and 14 open-air outside/interior close-ups across 7 sections. Quality labels are 18 `pass` and 5 `low_contrast_geometry_visible`; all five still passed visibility verification. [Capture index](captures/capture-index.json).
|
||||
|
||||
## Speed
|
||||
|
||||
Coarse candidate totals are L109 3,132, L114 14,384, and L115 3,720 (74.1% fewer than L114). The inner sweep still visits 48.95M fine-segment/sample pairs for both L114 and L115, versus 19.81M for L109, so fewer coarse candidates did not reduce fine traversal enough to improve readiness.
|
||||
|
||||
Five interleaved measured runs per version followed warm-ups at W8/W12 for all four strata. The host was quiet before timing (1-minute load 1.30; `top` 99% idle). Values below are L115 near-ready median deltas.
|
||||
|
||||
| Stratum | W8 vs L109 / L114 | W12 vs L109 / L114 |
|
||||
|---|---:|---:|
|
||||
| 0 | +16.8% / +8.7% | +16.8% / +9.2% |
|
||||
| 1 | +30.4% / +13.0% | +31.1% / +14.5% |
|
||||
| 2 | +25.2% / +12.4% | +25.9% / +12.0% |
|
||||
| 3 | +23.8% / +8.6% | +18.1% / +2.9% |
|
||||
|
||||
Across the eight strata/worker combinations, median whole-view deltas were +10.7% versus L109 and +4.9% versus L114. Full run data: [timing summary](../../../../Saved/Linux/l115/timing/summary.json).
|
||||
|
||||
## Verification
|
||||
|
||||
Fast tier passed in 341.1 s, including walkability, passage shell, camera sweeps, native field, and motif capsule reachability. Clang/GCC hashes matched at W1/W8/W12 for all four owner strata groups. The final source stamp is `7eb1fa49f91954c0690173aba4ed832b3594e50531bd4b20690d122970943274`; no Unreal-compiled source changed, so no Unreal build was needed.
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare per-tile passage candidate-list sizes for L109, L114, and L115."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l115/candidate-lists"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"L109": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeCoreHeadless-l109-clang",
|
||||
"L114": ROOT / "Saved/Linux/l114/bin/VoxelForgeCoreHeadless-l114-clang",
|
||||
"L115": ROOT / "Saved/Linux/l115/bin/VoxelForgeCoreHeadless-l115-clang",
|
||||
}
|
||||
CAMERAS = {
|
||||
0: (8.0, 16.0, -372.2626953125),
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
3: (-20.0625, -5.068359375, -900.353515625),
|
||||
}
|
||||
LIST = re.compile(r"^\[PassageComposerTile\].*?features=(\d+) route_candidates=(\d+)", re.M)
|
||||
|
||||
|
||||
def percentile(values: list[int], fraction: float) -> int:
|
||||
ordered = sorted(values)
|
||||
if not ordered:
|
||||
return 0
|
||||
return ordered[min(len(ordered) - 1,
|
||||
max(0, math.ceil(fraction * len(ordered)) - 1))]
|
||||
|
||||
|
||||
def run(strate: int, version: str) -> dict:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
log = OUT / f"{version}-s{strate}.log"
|
||||
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1740s", str(BINARIES[version]), str(DATA),
|
||||
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||
"--workers=12", "--clip-radius=3", "--max-clip-level=8", "--max-level=8",
|
||||
f"--camera={camera}", "--profile-passage-composer", "--stream-scheduler-policy=3",
|
||||
]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
features = [int(row[0]) for row in LIST.findall(output)]
|
||||
routes = [int(row[1]) for row in LIST.findall(output)]
|
||||
if result.returncode or not features:
|
||||
raise SystemExit(f"candidate-list profile missing: {log}; exit={result.returncode}")
|
||||
row = {
|
||||
"strate": strate, "version": version, "profiled_tiles": len(features),
|
||||
"feature_candidates": {"sum": sum(features), "min": min(features),
|
||||
"median": statistics.median(features), "p95": percentile(features, .95),
|
||||
"max": max(features)},
|
||||
"route_segment_candidates": {"sum": sum(routes), "min": min(routes),
|
||||
"median": statistics.median(routes), "p95": percentile(routes, .95),
|
||||
"max": max(routes)},
|
||||
"elapsed_s": round(time.monotonic() - started, 3),
|
||||
"log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
print("[L115CandidateLists] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if any(not path.is_file() for path in (*BINARIES.values(), DATA)):
|
||||
raise SystemExit("missing L109/L114/L115 candidate-list input")
|
||||
rows = [run(strate, version) for strate in range(4)
|
||||
for version in ("L109", "L114", "L115")]
|
||||
(OUT / "summary.json").write_text(json.dumps(rows, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Capture L115 rock floors at three rock stations per passage and open-air walls."""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
RESEARCH = Path(__file__).resolve().parent
|
||||
CAPTURES = RESEARCH / "captures"
|
||||
SAVED = ROOT / "Saved/Linux/l115/captures"
|
||||
L113_RESEARCH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux113"
|
||||
L115_SAVED = ROOT / "Saved/Linux/l115"
|
||||
BINARIES = {
|
||||
"L115": L115_SAVED / "bin/VoxelForgeCoreHeadless-l115-clang",
|
||||
}
|
||||
RENDERERS = {
|
||||
"L115": L115_SAVED / "bin/VoxelForgeOffscreenCapture-l115-clang",
|
||||
}
|
||||
SECTION = re.compile(
|
||||
r"^\[PassageOpenAirSection\] id=(\S+) section=(\d+) start_m=([\d.]+) "
|
||||
r"end_m=([\d.]+) samples=(\d+) midpoint_xyz_m=\(([^)]*)\) "
|
||||
r"midpoint_radius_m=([\d.]+)", re.M)
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
SPEC = importlib.util.spec_from_file_location(
|
||||
"l113_capture_helpers", L113_RESEARCH / "RunL113Captures.py")
|
||||
if SPEC is None or SPEC.loader is None:
|
||||
raise SystemExit("cannot load the verified L113 capture helpers")
|
||||
HELPERS = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(HELPERS)
|
||||
HELPERS.RESEARCH = RESEARCH
|
||||
HELPERS.CAPTURES = CAPTURES / "L115"
|
||||
HELPERS.SAVED = SAVED / "L115"
|
||||
HELPERS.BINARIES = BINARIES
|
||||
HELPERS.RENDERERS = RENDERERS
|
||||
|
||||
|
||||
def parse_sections(log: Path) -> list[dict]:
|
||||
sections = []
|
||||
text = log.read_text(encoding="utf-8", errors="replace")
|
||||
for passage, section, start, end, samples, midpoint, radius in SECTION.findall(text):
|
||||
sections.append({"passage": passage, "section": int(section),
|
||||
"start_m": float(start), "end_m": float(end),
|
||||
"samples": int(samples), "midpoint": HELPERS.vector(midpoint),
|
||||
"radius_m": float(radius)})
|
||||
return sections
|
||||
|
||||
|
||||
def rocky_spans(total: float, sections: list[dict]) -> list[tuple[float, float]]:
|
||||
spans = []
|
||||
cursor = 0.0
|
||||
for section in sorted(sections, key=lambda item: item["start_m"]):
|
||||
start = max(0.0, min(total, section["start_m"]))
|
||||
end = max(start, min(total, section["end_m"]))
|
||||
if start - cursor > 2.0:
|
||||
spans.append((cursor + 1.0, start - 1.0))
|
||||
cursor = max(cursor, end)
|
||||
if total - cursor > 2.0:
|
||||
spans.append((cursor + 1.0, total - 1.0))
|
||||
return [(start, end) for start, end in spans if end - start >= 1.0]
|
||||
|
||||
|
||||
def rock_station_candidates(spans: list[tuple[float, float]]) -> list[float]:
|
||||
total = sum(end - start for start, end in spans)
|
||||
if total < 3.0:
|
||||
raise RuntimeError("passage has less than 3 m of rock route outside open-air spans")
|
||||
stations = []
|
||||
for fraction in (0.25, 0.50, 0.75, 0.10, 0.90, 0.35, 0.65, 0.20, 0.80,
|
||||
0.40, 0.60):
|
||||
remaining = total * fraction
|
||||
for start, end in spans:
|
||||
span = end - start
|
||||
if remaining <= span:
|
||||
stations.append(start + remaining)
|
||||
break
|
||||
remaining -= span
|
||||
return stations
|
||||
|
||||
|
||||
def rock_floor_view(layout: dict, passage: dict, station: float, index: int) -> dict:
|
||||
raw_route = HELPERS.smooth_route(layout, passage)
|
||||
route, body_depth = HELPERS.derived_smooth_route(raw_route, passage, layout)
|
||||
lengths, prefix = HELPERS.route_lengths(route)
|
||||
center, radius = HELPERS.at_distance(route, lengths, prefix, station)
|
||||
floor_depth = min(body_depth, max(0.0, radius - 0.23))
|
||||
planned_floor = (center[0], center[1], center[2] - floor_depth)
|
||||
tangent = HELPERS.tangent_at(route, lengths, prefix, station)
|
||||
grade = math.degrees(math.atan2(abs(tangent[2]),
|
||||
math.hypot(tangent[0], tangent[1])))
|
||||
camera = tuple(planned_floor[axis] - 2.5 * tangent[axis]
|
||||
+ (1.25 if axis == 2 else 0.0) for axis in range(3))
|
||||
yaw, pitch = HELPERS.angles(camera, planned_floor)
|
||||
safe_id = HELPERS.sanitize(passage["stable_id"])
|
||||
return {"name": f"{safe_id}-rock-floor-station-{index}",
|
||||
"passage": passage["stable_id"], "kind": "rock-floor-close",
|
||||
"station": f"rock-floor/{station:.1f}m", "camera": camera,
|
||||
"target": planned_floor, "yaw": yaw, "pitch": pitch,
|
||||
"focus": planned_floor, "center_z": planned_floor[2],
|
||||
"route_distance_m": station, "requested_route_distance_m": station,
|
||||
"route_length_m": prefix[-1], "planned_floor_m": planned_floor[2],
|
||||
"planned_grade_deg": grade}
|
||||
|
||||
|
||||
def capture_rock_floor(layout: dict, passage: dict, station: float, index: int) -> dict:
|
||||
view = rock_floor_view(layout, passage, station, index)
|
||||
row = HELPERS.capture("L115", view, layout)
|
||||
scene = ROOT / row["scene"]
|
||||
anchor = HELPERS.nearest_surface_anchor(scene, view["target"], margin_m=5.0,
|
||||
preferred_normal=(0.0, 0.0, 1.0), minimum_alignment=0.85)
|
||||
alignment = HELPERS.dot(anchor["normal"], (0.0, 0.0, 1.0))
|
||||
camera_distance = math.dist(row["camera_actual_m"], anchor["position_m"])
|
||||
row.update({"variant": "L115", "kind": "rock-floor-close",
|
||||
"planned_floor_m": view["planned_floor_m"],
|
||||
"planned_grade_deg": view["planned_grade_deg"],
|
||||
"floor_anchor_position_m": anchor["position_m"],
|
||||
"floor_anchor_normal": anchor["normal"],
|
||||
"closeup_camera_to_floor_distance_m": camera_distance,
|
||||
"closeup_target_surface_distance_m": anchor["distance_m"],
|
||||
"closeup_floor_normal_alignment": alignment,
|
||||
"verified_upward_floor_normal": alignment >= 0.85})
|
||||
if (not row["verified"] or not 2.0 <= camera_distance <= 3.2
|
||||
or anchor["distance_m"] > 0.25 or alignment < 0.85
|
||||
or view["planned_grade_deg"] < 2.0 or row["drawn_triangles"] <= 0
|
||||
or row["pixel_quality_status"] not in
|
||||
("pass", "low_contrast_geometry_visible")):
|
||||
raise RuntimeError(f"rock-floor close-up failed at {passage['stable_id']} s={station:.2f}")
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
layout_path = L115_SAVED / "audit-layout-candidate.json"
|
||||
audit_log = L115_SAVED / "audit-owner-clang.log"
|
||||
if any(not path.is_file() for path in (*BINARIES.values(), *RENDERERS.values(),
|
||||
layout_path, audit_log)):
|
||||
raise SystemExit("missing L115 binaries, layout, or passage audit log")
|
||||
layout = json.loads(layout_path.read_text(encoding="utf-8"))
|
||||
sections = parse_sections(audit_log)
|
||||
if not sections:
|
||||
raise SystemExit("no L115 open-air sections found")
|
||||
CAPTURES.mkdir(parents=True, exist_ok=True)
|
||||
HELPERS.CAPTURES = CAPTURES / "L115"
|
||||
HELPERS.SAVED = SAVED / "L115"
|
||||
HELPERS.CAPTURES.mkdir(parents=True, exist_ok=True)
|
||||
HELPERS.SAVED.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
rows = []
|
||||
for passage in sorted(layout["passages"], key=lambda item: item["stable_id"]):
|
||||
passage_sections = [item for item in sections
|
||||
if item["passage"] == passage["stable_id"]]
|
||||
raw_route = HELPERS.smooth_route(layout, passage)
|
||||
route, _body_depth = HELPERS.derived_smooth_route(raw_route, passage, layout)
|
||||
_lengths, prefix = HELPERS.route_lengths(route)
|
||||
spans = rocky_spans(prefix[-1], passage_sections)
|
||||
candidates = rock_station_candidates(spans)
|
||||
accepted_stations = []
|
||||
total_rock_length = sum(end - start for start, end in spans)
|
||||
minimum_spacing = max(1.0, total_rock_length * 0.02)
|
||||
for station in candidates:
|
||||
if any(abs(station - prior) < minimum_spacing for prior in accepted_stations):
|
||||
continue
|
||||
index = len(accepted_stations) + 1
|
||||
try:
|
||||
row = capture_rock_floor(layout, passage, station, index)
|
||||
except RuntimeError as error:
|
||||
message = str(error)
|
||||
if not (message.startswith("no nearby surface vertices in mesh scene:")
|
||||
or message.startswith("rock-floor close-up failed at ")):
|
||||
raise
|
||||
print(f"[L115CaptureSkip] passage={passage['stable_id']} "
|
||||
f"station_m={station:.3f} reason=surface-or-anchor-check", flush=True)
|
||||
continue
|
||||
accepted_stations.append(station)
|
||||
rows.append(row)
|
||||
if len(accepted_stations) == 3:
|
||||
break
|
||||
if len(accepted_stations) < 3:
|
||||
for base_station in tuple(accepted_stations):
|
||||
for offset in (-10.0, 10.0, -15.0, 15.0, -20.0, 20.0,
|
||||
-25.0, 25.0, -30.0, 30.0):
|
||||
station = base_station + offset
|
||||
if not any(start < station < end for start, end in spans):
|
||||
continue
|
||||
if any(abs(station - prior) < minimum_spacing
|
||||
for prior in accepted_stations):
|
||||
continue
|
||||
index = len(accepted_stations) + 1
|
||||
try:
|
||||
row = capture_rock_floor(layout, passage, station, index)
|
||||
except RuntimeError as error:
|
||||
message = str(error)
|
||||
if not (message.startswith(
|
||||
"no nearby surface vertices in mesh scene:")
|
||||
or message.startswith("rock-floor close-up failed at ")):
|
||||
raise
|
||||
print(f"[L115CaptureSkip] passage={passage['stable_id']} "
|
||||
f"station_m={station:.3f} reason=nearby-station-check",
|
||||
flush=True)
|
||||
continue
|
||||
accepted_stations.append(station)
|
||||
rows.append(row)
|
||||
if len(accepted_stations) == 3:
|
||||
break
|
||||
if len(accepted_stations) == 3:
|
||||
break
|
||||
if len(accepted_stations) != 3:
|
||||
raise RuntimeError(f"only {len(accepted_stations)} verified rock-floor views for "
|
||||
f"{passage['stable_id']}: {accepted_stations}")
|
||||
|
||||
open_air_views = HELPERS.make_views(layout, sections, use_derived_shape=True,
|
||||
use_spline=True, include_open_air=True)
|
||||
open_air_views = [view for view in open_air_views
|
||||
if view["kind"] == "open-air-close"]
|
||||
if len(open_air_views) != 2 * len(sections):
|
||||
raise RuntimeError("L115 open-air close-up count is not two per section")
|
||||
for view in open_air_views:
|
||||
row = HELPERS.capture("L115", view, layout)
|
||||
if (not row["verified"]
|
||||
or abs(row["closeup_camera_target_distance_m"] - 4.0) > 0.02
|
||||
or row["closeup_target_surface_distance_m"] > 5.0
|
||||
or row["closeup_wall_normal_alignment"] < 0.25
|
||||
or row["drawn_triangles"] <= 0
|
||||
or row["pixel_quality_status"] not in
|
||||
("pass", "low_contrast_geometry_visible")):
|
||||
raise RuntimeError(f"open-air close-up failed: {view['name']}")
|
||||
row["variant"] = "L115"
|
||||
rows.append(row)
|
||||
|
||||
(CAPTURES / "capture-index.json").write_text(
|
||||
json.dumps({"captures": rows}, indent=2) + "\n", encoding="utf-8")
|
||||
floor_rows = [row for row in rows if row["kind"] == "rock-floor-close"]
|
||||
wall_rows = [row for row in rows if row["kind"] == "open-air-close"]
|
||||
passage_ids = {item["stable_id"] for item in layout["passages"]}
|
||||
if (len(floor_rows) != 3 * len(passage_ids)
|
||||
or any(sum(row["passage"] == passage for row in floor_rows) != 3
|
||||
for passage in passage_ids)
|
||||
or len(wall_rows) != 2 * len(sections)):
|
||||
raise RuntimeError("L115 capture count mismatch")
|
||||
lines = ["# L115 captures", "",
|
||||
"Each passage has three floor close-ups sampled outside the audited open-air spans. "
|
||||
"Floor anchors require an upward normal alignment of at least 0.85, a target-to-floor "
|
||||
"distance of at most 0.25 m, and a camera-to-floor distance between 2.0 and 3.2 m. "
|
||||
"Each open-air section has verified outside and interior wall close-ups at 4 m.", ""]
|
||||
for row in rows:
|
||||
lines.append(f"- [{row['passage']} {row['station']}]({row['capture']}) — "
|
||||
f"{row['kind']}; {row.get('drawn_triangles', 0)} triangles; "
|
||||
f"{row['pixel_quality_status']}")
|
||||
(CAPTURES / "INDEX.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
print(f"[L115CaptureSummary] captures={len(rows)} rock_floors={len(floor_rows)} "
|
||||
f"open_air_closeups={len(wall_rows)} sections={len(sections)} status=PASS",
|
||||
flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Measure L115 floor zero crossings at three route stations per passage."""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l115/density-probe"
|
||||
BINARY = ROOT / "Saved/Linux/l115/bin/VoxelForgeCoreHeadless-l115-clang"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
SLOPE = re.compile(
|
||||
r"^\[L115DensitySlopeSample\] passage=(\S+) station=(\S+) s_m=([\d.]+) "
|
||||
r".*?planned_floor_m=(-?[\d.]+) root_floor_m=(-?[\d.]+|nan) "
|
||||
r"residual_m=(-?[\d.]+|nan)", re.M)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
log = OUT / "owner4-clang.log"
|
||||
layout = OUT / "layout.json"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1740s", str(BINARY), str(DATA), "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--workers=12", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", "--camera=8,16,-372.2626953125,0,0",
|
||||
"--audit-resolved-layout-passages", f"--resolved-layout-output={layout}",
|
||||
]
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT,
|
||||
env={**os.environ,
|
||||
"VOXELFORGE_L115_DENSITY_PROFILE": "1"},
|
||||
stdout=stream, stderr=subprocess.STDOUT,
|
||||
check=False, timeout=1740)
|
||||
if result.returncode:
|
||||
raise SystemExit(f"L115 density probe failed: {log}")
|
||||
text = log.read_text(encoding="utf-8", errors="replace")
|
||||
groups: dict[tuple[str, str], list[dict]] = {}
|
||||
for passage, station, distance, planned, root, residual in SLOPE.findall(text):
|
||||
root_value = float(root) if root != "nan" else math.nan
|
||||
residual_value = float(residual) if residual != "nan" else math.nan
|
||||
groups.setdefault((passage, station), []).append({
|
||||
"route_s_m": float(distance), "planned_floor_m": float(planned),
|
||||
"root_floor_m": root_value, "offset_m": residual_value})
|
||||
summary = []
|
||||
with (OUT / "zero-crossing-offsets.csv").open("w", newline="", encoding="utf-8") as stream:
|
||||
writer = csv.DictWriter(stream, fieldnames=("passage", "station", "route_s_m",
|
||||
"planned_floor_m", "root_floor_m", "offset_m"))
|
||||
writer.writeheader()
|
||||
for (passage, station), samples in sorted(groups.items()):
|
||||
for sample in samples:
|
||||
writer.writerow({"passage": passage, "station": station, **sample})
|
||||
finite = [sample["offset_m"] for sample in samples
|
||||
if math.isfinite(sample["offset_m"])]
|
||||
summary.append({"passage": passage, "station": station,
|
||||
"samples": len(samples), "finite_roots": len(finite),
|
||||
"max_abs_offset_m": max(map(abs, finite)) if finite else None,
|
||||
"median_abs_offset_m": statistics.median(map(abs, finite))
|
||||
if finite else None,
|
||||
"p95_abs_offset_m": sorted(map(abs, finite))[
|
||||
min(len(finite)-1, math.ceil(0.95*len(finite))-1)]
|
||||
if finite else None})
|
||||
if not summary or any(item["samples"] != 51 for item in summary):
|
||||
raise RuntimeError("L115 density profile is missing 51-sample route stations")
|
||||
(OUT / "zero-crossing-summary.json").write_text(
|
||||
json.dumps(summary, indent=2) + "\n", encoding="utf-8")
|
||||
print(f"[L115DensityProbe] exit={result.returncode} stations={len(summary)} "
|
||||
f"log={log.relative_to(ROOT)}", flush=True)
|
||||
return result.returncode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check Clang/GCC hash identity at W1/W8/W12 and pin v223 owner hashes."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l115/hashes"
|
||||
DATASETS = {
|
||||
"owner4": ROOT / "Standalone/data/owner_world.json",
|
||||
"owner2": ROOT / "Standalone/data/regression/owner_world_2strate_l61.json",
|
||||
}
|
||||
BINARIES = {
|
||||
"clang": ROOT / "Saved/Linux/l115/bin/VoxelForgeCoreHeadless-l115-clang",
|
||||
"gcc": ROOT / "Saved/Linux/l115/bin/VoxelForgeCoreHeadless-l115-gcc",
|
||||
}
|
||||
HASH = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
|
||||
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M)
|
||||
|
||||
|
||||
def run(dataset: str, recipe: str, workers: int, compiler: str) -> dict:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
label = f"{dataset}-{recipe}-w{workers}-{compiler}"
|
||||
log = OUT / f"{label}.log"
|
||||
layout = OUT / f"{label}.layout.json"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1740s", str(BINARIES[compiler]), str(DATASETS[dataset]),
|
||||
f"--workers={workers}", "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", "--force-generate", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--recipe={recipe}",
|
||||
"--stream-hash", f"--resolved-layout-output={layout}",
|
||||
]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
hashes = HASH.findall(output)
|
||||
if result.returncode or len(hashes) != 12:
|
||||
raise SystemExit(f"hash run failed or incomplete: {label}; exit={result.returncode} "
|
||||
f"hashes={len(hashes)} log={log}")
|
||||
row = {"dataset": dataset, "recipe": recipe, "workers": workers,
|
||||
"compiler": compiler, "exit_code": result.returncode,
|
||||
"hashes": hashes, "source_stamp": (STAMP.search(output).group(1)
|
||||
if STAMP.search(output) else None),
|
||||
"elapsed_s": round(time.monotonic() - started, 3),
|
||||
"log": str(log.relative_to(ROOT))}
|
||||
print("[L115HashRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if any(not path.is_file() for path in (*DATASETS.values(), *BINARIES.values())):
|
||||
raise SystemExit("missing L115 hash input")
|
||||
rows = []
|
||||
recipes = {"owner4": ("surface-world",),
|
||||
"owner2": ("surface-world", "floating-islands")}
|
||||
for dataset, dataset_recipes in recipes.items():
|
||||
for recipe in dataset_recipes:
|
||||
for workers in (1, 8, 12):
|
||||
for compiler in ("clang", "gcc"):
|
||||
rows.append(run(dataset, recipe, workers, compiler))
|
||||
(OUT / "runs-partial.json").write_text(
|
||||
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
|
||||
groups = {}
|
||||
for dataset, dataset_recipes in recipes.items():
|
||||
for recipe in dataset_recipes:
|
||||
key = f"{dataset}/{recipe}"
|
||||
group = [row for row in rows if row["dataset"] == dataset
|
||||
and row["recipe"] == recipe]
|
||||
reference = group[0]["hashes"]
|
||||
groups[key] = {"all_worker_compiler_hashes_match": all(
|
||||
row["hashes"] == reference for row in group),
|
||||
"source_stamps": sorted({row["source_stamp"] for row in group}),
|
||||
"runs": [{"workers": row["workers"], "compiler": row["compiler"],
|
||||
"log": row["log"]} for row in group]}
|
||||
if not groups[key]["all_worker_compiler_hashes_match"]:
|
||||
raise SystemExit(f"determinism mismatch: {key}")
|
||||
if dataset == "owner2":
|
||||
canonical = next(row for row in group if row["workers"] == 12
|
||||
and row["compiler"] == "clang")
|
||||
recipe_name = "floating-islands" if recipe == "floating-islands" else "surface-world"
|
||||
style = 1 if recipe_name == "surface-world" else 2
|
||||
baseline = (ROOT / "Standalone/Tools/verify/baselines/owner-hash/canonical"
|
||||
/ f"owner-hash-{recipe_name}-style{style}-v223.baseline")
|
||||
baseline.write_text("\n".join(canonical["hashes"]) + "\n",
|
||||
encoding="utf-8")
|
||||
summary = {"runs": rows, "groups": groups,
|
||||
"owner4_all_four_strata": "owner4/surface-world",
|
||||
"owner2_canonical_pins": ["owner2/surface-world",
|
||||
"owner2/floating-islands"]}
|
||||
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Five interleaved L80 readiness runs for L109, L114, and L115."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l115/timing"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"L109": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeCoreHeadless-l109-clang",
|
||||
"L114": ROOT / "Saved/Linux/l114/bin/VoxelForgeCoreHeadless-l114-clang",
|
||||
"L115": ROOT / "Saved/Linux/l115/bin/VoxelForgeCoreHeadless-l115-clang",
|
||||
}
|
||||
CAMERAS = {
|
||||
0: (8.0, 16.0, -372.2626953125),
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
3: (-20.0625, -5.068359375, -900.353515625),
|
||||
}
|
||||
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
|
||||
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||
NEAR = re.compile(
|
||||
r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$",
|
||||
re.M,
|
||||
)
|
||||
|
||||
|
||||
def run(strate: int, workers: int, version: str, phase: str, replicate: int) -> dict:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
label = f"s{strate}-w{workers}-{version}-{phase}{replicate}"
|
||||
log = OUT / f"{label}.log"
|
||||
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1740s", str(BINARIES[version]), str(DATA),
|
||||
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||
f"--workers={workers}", "--clip-radius=3", "--max-clip-level=8", "--max-level=8",
|
||||
f"--camera={camera}", "--profile-lod-work", "--stream-scheduler-policy=3",
|
||||
]
|
||||
start = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as output:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=output,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
content = log.read_text(encoding="utf-8", errors="replace")
|
||||
pipeline_match = PIPELINE.search(content)
|
||||
near_match = NEAR.search(content)
|
||||
if result.returncode or pipeline_match is None or near_match is None:
|
||||
raise SystemExit(f"readiness run failed or trace missing: {log}; exit={result.returncode}")
|
||||
pipeline = dict(FIELDS.findall(pipeline_match.group(1)))
|
||||
row = {
|
||||
"strate": strate, "workers": workers, "version": version,
|
||||
"phase": phase, "replicate": replicate, "exit_code": result.returncode,
|
||||
"wall_s": round(time.monotonic() - start, 3),
|
||||
"lod02_ready_s": float(near_match.group(3)),
|
||||
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
|
||||
"whole_tiles": int(pipeline["whole_tiles"].split("/")[0]),
|
||||
"log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
print("[L115TimingRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not DATA.is_file() or any(not binary.is_file() for binary in BINARIES.values()):
|
||||
raise SystemExit("missing L109/L114/L115 data or timing binary")
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
load_1m = os.getloadavg()[0]
|
||||
uptime = subprocess.run(["uptime"], cwd=ROOT, text=True, capture_output=True,
|
||||
check=True).stdout.strip()
|
||||
top = subprocess.run(["ps", "-eo", "pid,pcpu,comm", "--sort=-pcpu"], cwd=ROOT,
|
||||
text=True, capture_output=True, check=True).stdout
|
||||
(OUT / "machine-load-before.txt").write_text(
|
||||
f"uptime: {uptime}\n1-minute load: {load_1m:.2f}\ntop CPU users:\n{top}",
|
||||
encoding="utf-8")
|
||||
print(f"[L115TimingGate] load_1m={load_1m:.2f} uptime={uptime}", flush=True)
|
||||
if load_1m >= 1.5:
|
||||
raise SystemExit(f"timing refused: 1-minute load {load_1m:.2f} is not below 1.5")
|
||||
rows = []
|
||||
versions = ("L109", "L114", "L115")
|
||||
for strate in range(4):
|
||||
for workers in (8, 12):
|
||||
for version in versions:
|
||||
run(strate, workers, version, "warmup", 0)
|
||||
for replicate in range(1, 6):
|
||||
offset = (replicate - 1) % len(versions)
|
||||
order = versions[offset:] + versions[:offset]
|
||||
for version in order:
|
||||
rows.append(run(strate, workers, version, "measured", replicate))
|
||||
(OUT / "runs-partial.json").write_text(
|
||||
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
|
||||
summary = []
|
||||
for strate in range(4):
|
||||
for workers in (8, 12):
|
||||
item = {"strate": strate, "workers": workers, "replicates": 5}
|
||||
medians = {}
|
||||
for version in versions:
|
||||
group = [row for row in rows if row["strate"] == strate
|
||||
and row["workers"] == workers and row["version"] == version]
|
||||
medians[version] = {}
|
||||
for key in ("lod02_ready_s", "whole_view_ready_s"):
|
||||
values = [row[key] for row in group]
|
||||
medians[version][key] = statistics.median(values)
|
||||
item[f"{version}_{key}_values"] = values
|
||||
item[f"{version}_{key}_median"] = medians[version][key]
|
||||
for comparison in ("L109", "L114"):
|
||||
for key in ("lod02_ready_s", "whole_view_ready_s"):
|
||||
item[f"L115_vs_{comparison}_{key}_delta_pct"] = round(
|
||||
100.0 * (medians["L115"][key] / medians[comparison][key] - 1.0), 2)
|
||||
summary.append(item)
|
||||
(OUT / "summary.json").write_text(
|
||||
json.dumps({"runs": rows, "summary": summary}, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,27 @@
|
||||
# L115 captures
|
||||
|
||||
Each passage has three floor close-ups sampled outside the audited open-air spans. Floor anchors require an upward normal alignment of at least 0.85, a target-to-floor distance of at most 0.25 m, and a camera-to-floor distance between 2.0 and 3.2 m. Each open-air section has verified outside and interior wall close-ups at 4 m.
|
||||
|
||||
- [passage/0-1/0 rock-floor/401.7m](captures/L115/L115/passage-0-1-0-rock-floor-station-1.png) — rock-floor-close; 122256 triangles; pass
|
||||
- [passage/0-1/0 rock-floor/792.8m](captures/L115/L115/passage-0-1-0-rock-floor-station-2.png) — rock-floor-close; 585992 triangles; pass
|
||||
- [passage/0-1/0 rock-floor/1634.5m](captures/L115/L115/passage-0-1-0-rock-floor-station-3.png) — rock-floor-close; 137252 triangles; pass
|
||||
- [passage/1-2/0 rock-floor/27.0m](captures/L115/L115/passage-1-2-0-rock-floor-station-1.png) — rock-floor-close; 652088 triangles; pass
|
||||
- [passage/1-2/0 rock-floor/52.9m](captures/L115/L115/passage-1-2-0-rock-floor-station-2.png) — rock-floor-close; 636128 triangles; pass
|
||||
- [passage/1-2/0 rock-floor/78.9m](captures/L115/L115/passage-1-2-0-rock-floor-station-3.png) — rock-floor-close; 580512 triangles; pass
|
||||
- [passage/2-3/0 rock-floor/323.4m](captures/L115/L115/passage-2-3-0-rock-floor-station-1.png) — rock-floor-close; 474896 triangles; low_contrast_geometry_visible
|
||||
- [passage/2-3/0 rock-floor/333.4m](captures/L115/L115/passage-2-3-0-rock-floor-station-2.png) — rock-floor-close; 494436 triangles; pass
|
||||
- [passage/2-3/0 rock-floor/343.4m](captures/L115/L115/passage-2-3-0-rock-floor-station-3.png) — rock-floor-close; 485332 triangles; pass
|
||||
- [passage/0-1/0 section-1/4m-outside](captures/L115/L115/passage-0-1-0-openair-1-4m-outside.png) — open-air-close; 275313 triangles; pass
|
||||
- [passage/0-1/0 section-1/4m-interior-wall](captures/L115/L115/passage-0-1-0-openair-1-4m-interior-wall.png) — open-air-close; 307964 triangles; low_contrast_geometry_visible
|
||||
- [passage/0-1/0 section-2/4m-outside](captures/L115/L115/passage-0-1-0-openair-2-4m-outside.png) — open-air-close; 275313 triangles; pass
|
||||
- [passage/0-1/0 section-2/4m-interior-wall](captures/L115/L115/passage-0-1-0-openair-2-4m-interior-wall.png) — open-air-close; 345024 triangles; pass
|
||||
- [passage/0-1/0 section-3/4m-outside](captures/L115/L115/passage-0-1-0-openair-3-4m-outside.png) — open-air-close; 148970 triangles; pass
|
||||
- [passage/0-1/0 section-3/4m-interior-wall](captures/L115/L115/passage-0-1-0-openair-3-4m-interior-wall.png) — open-air-close; 148032 triangles; pass
|
||||
- [passage/1-2/0 section-1/4m-outside](captures/L115/L115/passage-1-2-0-openair-1-4m-outside.png) — open-air-close; 620866 triangles; low_contrast_geometry_visible
|
||||
- [passage/1-2/0 section-1/4m-interior-wall](captures/L115/L115/passage-1-2-0-openair-1-4m-interior-wall.png) — open-air-close; 609733 triangles; pass
|
||||
- [passage/1-2/0 section-2/4m-outside](captures/L115/L115/passage-1-2-0-openair-2-4m-outside.png) — open-air-close; 584622 triangles; pass
|
||||
- [passage/1-2/0 section-2/4m-interior-wall](captures/L115/L115/passage-1-2-0-openair-2-4m-interior-wall.png) — open-air-close; 572238 triangles; pass
|
||||
- [passage/2-3/0 section-1/4m-outside](captures/L115/L115/passage-2-3-0-openair-1-4m-outside.png) — open-air-close; 104612 triangles; low_contrast_geometry_visible
|
||||
- [passage/2-3/0 section-1/4m-interior-wall](captures/L115/L115/passage-2-3-0-openair-1-4m-interior-wall.png) — open-air-close; 94962 triangles; low_contrast_geometry_visible
|
||||
- [passage/2-3/0 section-2/4m-outside](captures/L115/L115/passage-2-3-0-openair-2-4m-outside.png) — open-air-close; 475838 triangles; pass
|
||||
- [passage/2-3/0 section-2/4m-interior-wall](captures/L115/L115/passage-2-3-0-openair-2-4m-interior-wall.png) — open-air-close; 486512 triangles; pass
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 103 KiB |