Author SHA1 Message Date
Fr0zkaandClaude Opus 5.5 0d1140c117 research: tunnel reach study (L129, no-go; found a units mismatch in v225 bounds)
BuildPassageSpatialIndices adds the 4.335 m reach in core-voxel units, so the broadphase reaches ~1.08 m while
the sweep cutoff says 4 m. Physically-correct 2 m bounds are larger than v225's and 4.9-9.9% slower; captures at
all L125 poses pixel-identical. Patch kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 23:09:27 +01:00
49 changed files with 4091 additions and 0 deletions

No files matched your search

@@ -0,0 +1,294 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
struct FSegment
{
int passage=0,index=0;
std::array<double,3> a{},b{};
double radiusA=0.0,radiusB=0.0;
};
struct FTile
{
int strate=0,x=0,y=0,z=0,lod=0;
std::uint64_t baselineFeatures=0,baselineCandidates=0,baselinePairs=0;
};
struct FTotals
{
std::uint64_t tiles=0,featureTiles=0,proofTiles=0,groups=0,segments=0;
std::uint64_t pairs=0,contributingPairs=0,touched=0,support=0;
std::uint64_t overlapPairs=0,crossPassageOverlap=0;
std::uint64_t baselineFeatureTiles=0,baselineFeatures=0,baselineCandidates=0;
std::uint64_t baselinePairs=0;
};
static bool Intersects(const std::array<double,3>& amin,
const std::array<double,3>& amax,const std::array<double,3>& bmin,
const std::array<double,3>& bmax)
{
for(int axis=0;axis<3;++axis)
if(amax[axis]<bmin[axis]||amin[axis]>bmax[axis])return false;
return true;
}
static int GridFirst(double bound,const double gridMin[3],double spacing,int axis)
{
const auto boundSub=static_cast<std::int64_t>(std::floor(bound*1024.0));
const auto gridMinSub=static_cast<std::int64_t>(std::llround(gridMin[axis]*1024.0));
const auto stepSub=static_cast<std::int64_t>(std::llround(spacing*1024.0));
const auto numerator=boundSub-gridMinSub;
auto quotient=numerator/stepSub;
if(numerator%stepSub>0)++quotient;
return static_cast<int>(quotient);
}
static int GridLast(double bound,const double gridMin[3],double spacing,int axis)
{
const auto boundSub=static_cast<std::int64_t>(std::ceil(bound*1024.0));
const auto gridMinSub=static_cast<std::int64_t>(std::llround(gridMin[axis]*1024.0));
const auto stepSub=static_cast<std::int64_t>(std::llround(spacing*1024.0));
const auto numerator=boundSub-gridMinSub;
auto quotient=numerator/stepSub;
if(numerator%stepSub<0)--quotient;
return static_cast<int>(quotient);
}
int main(int argc,char** argv)
{
if(argc!=3)
{
std::cerr<<"usage: CountReachCandidates <input.tsv> <output.csv>\n";
return 2;
}
std::ifstream input(argv[1]);
if(!input){std::cerr<<"cannot open input\n";return 2;}
std::vector<std::vector<FSegment>> passages(3);
std::vector<FTile> tiles;
std::string line;
while(std::getline(input,line))
{
std::istringstream row(line);
char kind=0;row>>kind;
if(kind=='S')
{
FSegment s;row>>s.passage>>s.index
>>s.a[0]>>s.a[1]>>s.a[2]>>s.b[0]>>s.b[1]>>s.b[2]
>>s.radiusA>>s.radiusB;
if(!row||s.passage<0||s.passage>=static_cast<int>(passages.size()))
{std::cerr<<"bad segment row\n";return 2;}
passages[static_cast<std::size_t>(s.passage)].push_back(s);
}
else if(kind=='T')
{
FTile t;row>>t.strate>>t.x>>t.y>>t.z>>t.lod
>>t.baselineFeatures>>t.baselineCandidates>>t.baselinePairs;
if(!row){std::cerr<<"bad tile row\n";return 2;}
tiles.push_back(t);
}
else if(kind!='#'&&kind!='\0')
{std::cerr<<"unknown row\n";return 2;}
}
for(auto& p:passages)
std::sort(p.begin(),p.end(),[](const auto& a,const auto& b)
{return a.index<b.index;});
std::ofstream output(argv[2]);
if(!output){std::cerr<<"cannot open output\n";return 2;}
output<<"reach_m,bounds_mode,strate,lod,tiles,feature_tiles,proof_tiles,feature_groups,segment_pairs,contributing_pairs,touched_samples,round_support_samples,overlap_pairs,cross_passage_overlaps,baseline_feature_tiles,baseline_features,baseline_candidates,baseline_pairs\n";
constexpr std::array<double,4> reaches={4.0,3.0,2.0,1.5};
const std::array<std::pair<const char*,double>,2> boundsModes={{{"v225_core_voxel_units",0.25},
{"physical_metres",1.0}}};
constexpr double coreVoxelMetres=0.25;
constexpr double marginMetres=0.01;
for(const auto& [boundsMode,boundsScale]:boundsModes)
for(const double reach:reaches)
{
const double requiredReach=std::max(reach+0.21+0.125,0.75+0.21);
std::map<std::pair<int,int>,FTotals> totals;
for(const auto& tile:tiles)
{
const int stepVoxels=1<<tile.lod;
const double spacing=stepVoxels*coreVoxelMetres;
const int dim=32+3;
const double tileOrigin[3]={tile.x*32.0*spacing,tile.y*32.0*spacing,
tile.z*32.0*spacing};
const double gridMin[3]={tileOrigin[0]-spacing,tileOrigin[1]-spacing,
tileOrigin[2]-spacing};
const double gridMax[3]={gridMin[0]+(dim-1)*spacing,
gridMin[1]+(dim-1)*spacing,gridMin[2]+(dim-1)*spacing};
const std::array<double,3> queryMin={gridMin[0],gridMin[1],gridMin[2]};
const std::array<double,3> queryMax={gridMax[0],gridMax[1],gridMax[2]};
const std::array<double,3> proofMin={tileOrigin[0],tileOrigin[1],tileOrigin[2]};
const std::array<double,3> proofMax={tileOrigin[0]+32.0*spacing,
tileOrigin[1]+32.0*spacing,tileOrigin[2]+32.0*spacing};
const std::size_t sampleCount=static_cast<std::size_t>(dim)*dim*dim;
std::vector<std::vector<std::uint8_t>> touchedMasks(passages.size(),
std::vector<std::uint8_t>(sampleCount,0));
std::vector<std::vector<std::uint8_t>> supportMasks(passages.size(),
std::vector<std::uint8_t>(sampleCount,0));
std::vector<std::uint8_t> unionMask(sampleCount,0);
FTotals local;
bool anyFeature=false,anyProof=false;
for(std::size_t p=0;p<passages.size();++p)
{
const auto& segments=passages[p];
const std::size_t groupCount=(segments.size()+4)/5;
for(std::size_t group=0;group<groupCount;++group)
{
const std::size_t begin=group*5,end=std::min(begin+5,segments.size());
std::array<double,3> routeMin={
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity()};
std::array<double,3> routeMax={
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
double maxRadius=0.0;
for(std::size_t i=begin;i<end;++i)
{
const auto& s=segments[i];
maxRadius=std::max({maxRadius,s.radiusA,s.radiusB});
for(int axis=0;axis<3;++axis)
{
routeMin[axis]=std::min({routeMin[axis],s.a[axis],s.b[axis]});
routeMax[axis]=std::max({routeMax[axis],s.a[axis],s.b[axis]});
}
}
// The v225 implementation divides positions and radii by Q
// (Core voxels), then adds its *_Metres constants directly.
// Reproduce that code-unit behavior here for the baseline check.
const double featureExtent=maxRadius
+(requiredReach+marginMetres)*boundsScale;
for(int axis=0;axis<3;++axis)
{routeMin[axis]-=featureExtent;routeMax[axis]+=featureExtent;}
if(!Intersects(routeMin,routeMax,queryMin,queryMax))continue;
anyFeature=true;++local.groups;
for(std::size_t i=begin;i<end;++i)
{
const auto& s=segments[i];
const double extent=std::max(s.radiusA,s.radiusB)
+(requiredReach+marginMetres)*boundsScale;
std::array<double,3> low{},high{};
int first[3]{},last[3]{};
bool valid=true;
for(int axis=0;axis<3;++axis)
{
low[axis]=std::min(s.a[axis],s.b[axis])-extent;
high[axis]=std::max(s.a[axis],s.b[axis])+extent;
first[axis]=std::max(0,GridFirst(low[axis],gridMin,spacing,axis));
last[axis]=std::min(dim-1,GridLast(high[axis],gridMin,spacing,axis));
valid=valid&&first[axis]<=last[axis];
}
// Proof bounds are per segment and include the same conservative
// reach; they test the full tile box without the sample halo.
if(Intersects(low,high,proofMin,proofMax))anyProof=true;
if(!valid)continue;
++local.segments;
for(int axis=0;axis<3;++axis)
{
const auto subLo=static_cast<std::int64_t>(std::floor(low[axis]*1024.0));
const auto subHi=static_cast<std::int64_t>(std::ceil(high[axis]*1024.0));
const auto gridLo=static_cast<std::int64_t>(std::llround(gridMin[axis]*1024.0));
const auto subStep=static_cast<std::int64_t>(std::llround(spacing*1024.0));
const auto expectedFirst=std::max<std::int64_t>(0,
(subLo-gridLo+subStep-1)/subStep);
const auto expectedLast=std::min<std::int64_t>(dim-1,
(subHi-gridLo)/subStep);
first[axis]=static_cast<int>(expectedFirst);
last[axis]=static_cast<int>(expectedLast);
}
if(first[0]>last[0]||first[1]>last[1]||first[2]>last[2])continue;
const std::uint64_t nx=static_cast<std::uint64_t>(last[0]-first[0]+1);
const std::uint64_t ny=static_cast<std::uint64_t>(last[1]-first[1]+1);
const std::uint64_t nz=static_cast<std::uint64_t>(last[2]-first[2]+1);
local.pairs+=nx*ny*nz;
for(int gz=first[2];gz<=last[2];++gz)
for(int gy=first[1];gy<=last[1];++gy)
for(int gx=first[0];gx<=last[0];++gx)
{
const double q[3]={gridMin[0]+gx*spacing,
gridMin[1]+gy*spacing,gridMin[2]+gz*spacing};
const double dx=s.b[0]-s.a[0],dy=s.b[1]-s.a[1],dz=s.b[2]-s.a[2];
const double lengthSq=dx*dx+dy*dy+dz*dz;
if(!(lengthSq>0.0))continue;
const double projection=(q[0]-s.a[0])*dx+(q[1]-s.a[1])*dy
+(q[2]-s.a[2])*dz;
const double t=std::clamp(projection/lengthSq,0.0,1.0);
const double ex=q[0]-(s.a[0]+t*dx);
const double ey=q[1]-(s.a[1]+t*dy);
const double ez=q[2]-(s.a[2]+t*dz);
const double radius=s.radiusA+t*(s.radiusB-s.radiusA);
const double distance=std::sqrt(ex*ex+ey*ey+ez*ez)-radius;
const double oneSubvoxel=1.0/1024.0;
if(distance<reach+oneSubvoxel)
{
++local.contributingPairs;
const std::size_t index=(static_cast<std::size_t>(gz)*dim+gy)*dim+gx;
touchedMasks[p][index]=1;
unionMask[index]=1;
if(distance<=reach)supportMasks[p][index]=1;
}
}
}
}
}
local.tiles=1;
local.featureTiles=anyFeature?1:0;
local.proofTiles=anyProof?1:0;
local.baselineFeatureTiles=tile.baselineFeatures?1:0;
local.baselineFeatures=tile.baselineFeatures;
local.baselineCandidates=tile.baselineCandidates;
local.baselinePairs=tile.baselinePairs;
for(const auto& mask:touchedMasks)
local.touched+=static_cast<std::uint64_t>(
std::count(mask.begin(),mask.end(),static_cast<std::uint8_t>(1)));
for(std::size_t p=0;p<passages.size();++p)
{
const auto& mask=supportMasks[p];
local.support+=static_cast<std::uint64_t>(
std::count(mask.begin(),mask.end(),static_cast<std::uint8_t>(1)));
}
const std::uint64_t unique=static_cast<std::uint64_t>(
std::count(unionMask.begin(),unionMask.end(),static_cast<std::uint8_t>(1)));
local.crossPassageOverlap=local.touched-unique;
local.overlapPairs=local.contributingPairs-local.touched;
auto& total=totals[{tile.strate,tile.lod}];
total.tiles+=local.tiles;total.featureTiles+=local.featureTiles;
total.proofTiles+=local.proofTiles;total.groups+=local.groups;
total.segments+=local.segments;total.pairs+=local.pairs;
total.contributingPairs+=local.contributingPairs;total.touched+=local.touched;
total.support+=local.support;total.overlapPairs+=local.overlapPairs;
total.crossPassageOverlap+=local.crossPassageOverlap;
total.baselineFeatureTiles+=local.baselineFeatureTiles;
total.baselineFeatures+=local.baselineFeatures;
total.baselineCandidates+=local.baselineCandidates;
total.baselinePairs+=local.baselinePairs;
}
for(const auto& [key,t]:totals)
output<<std::fixed<<std::setprecision(2)<<reach<<','<<boundsMode<<','
<<key.first<<','<<key.second
<<','<<t.tiles<<','<<t.featureTiles<<','<<t.proofTiles<<','<<t.groups
<<','<<t.pairs<<','<<t.contributingPairs<<','<<t.touched<<','<<t.support
<<','<<t.overlapPairs<<','<<t.crossPassageOverlap<<','
<<t.baselineFeatureTiles<<','<<t.baselineFeatures<<','
<<t.baselineCandidates<<','<<t.baselinePairs<<'\n';
std::cerr<<"[L129Precount] reach_m="<<reach<<" required_reach_m="<<requiredReach
<<" grouped_rows="<<totals.size()<<"\n";
}
return 0;
}
@@ -0,0 +1,310 @@
#!/usr/bin/env python3
"""Count reach-bound work from the saved v225 layout and L125 tile profiles."""
from __future__ import annotations
import json
import math
import re
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
HERE = Path(__file__).resolve().parent
OUT = ROOT / "Saved/Linux/l129/precount"
LAYOUT = ROOT / "Saved/Linux/l125/hashes/owner4-surface-world-w8-clang.layout.json"
BASELINE_LOGS = [ROOT / f"Saved/Linux/l125/counts/s{s}-w8-profile1.log" for s in range(4)]
CORE_VOXEL_METRES = 0.25
SUBVOXELS_PER_METRE = 1024.0
PROFILE = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
TILE_KEY = re.compile(r"\((-?\d+),(-?\d+),(-?\d+),lod(\d+)\)")
def knot_span(a: list[float], b: list[float], units_per_metre: float) -> float:
dx, dy = (b[0] - a[0]) / units_per_metre, (b[1] - a[1]) / units_per_metre
return max(1.0e-6, math.sqrt(math.sqrt(dx * dx + dy * dy)))
def evaluate_raw(controls: list[list[float]], span: int, t: float,
units_per_metre: float) -> list[float]:
p1, p2 = controls[span], controls[span + 1]
p0, p3 = controls[max(0, span - 1)], controls[min(span + 2, len(controls) - 1)]
dt0, dt1, dt2 = (knot_span(p0, p1, units_per_metre),
knot_span(p1, p2, units_per_metre),
knot_span(p2, p3, units_per_metre))
t2, t3 = t * t, t * t * t
result = [0.0, 0.0, 0.0, 0.0]
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)
result[axis] = ((2.0 * t3 - 3.0 * t2 + 1.0) * p1[axis]
+ (-2.0 * t3 + 3.0 * t2) * p2[axis]
+ (t3 - 2.0 * t2 + t) * m1
+ (t3 - t2) * m2)
result[2] = p1[2] + (p2[2] - p1[2]) * t
result[3] = min(max(result[3], min(p1[3], p2[3])), max(p1[3], p2[3]))
return result
def build_raw_segments(controls: list[list[float]], units_per_metre: float,
spacing_metres: float = 8.0,
chord_error_metres: float = 0.1) -> list[dict]:
result: list[dict] = []
along_metres = 0.0
for span in range(len(controls) - 1):
horizontal_arc = [0.0] * 33
previous = evaluate_raw(controls, span, 0.0, units_per_metre)
for step in range(1, 33):
current = evaluate_raw(controls, span, step / 32.0, units_per_metre)
dx = (current[0] - previous[0]) / units_per_metre
dy = (current[1] - previous[1]) / units_per_metre
horizontal_arc[step] = horizontal_arc[step - 1] + math.hypot(dx, dy)
previous = current
span_length = horizontal_arc[-1]
base_count = max(1, math.ceil(span_length / spacing_metres))
def parameter_at_arc(target: float) -> float:
upper = 1
while upper < len(horizontal_arc) and horizontal_arc[upper] < target:
upper += 1
upper = min(upper, 32)
lower = upper - 1
arc_span = horizontal_arc[upper] - horizontal_arc[lower]
arc_t = ((target - horizontal_arc[lower]) / arc_span
if arc_span > 1.0e-12 else 0.0)
return (lower + min(max(arc_t, 0.0), 1.0)) / 32.0
parameters = [0.0]
def append_adaptive(t_a: float, t_b: float, a: list[float], b: list[float],
depth: int) -> None:
fractions = (0.25, 0.5, 0.75)
maximum_error, polyline_length = 0.0, 0.0
previous_point = a
for fraction in fractions:
current = evaluate_raw(controls, span, t_a + (t_b - t_a) * fraction,
units_per_metre)
dx = (current[0] - (a[0] + (b[0] - a[0]) * fraction)) / units_per_metre
dy = (current[1] - (a[1] + (b[1] - a[1]) * fraction)) / units_per_metre
dr = (current[3] - (a[3] + (b[3] - a[3]) * fraction)) / units_per_metre
maximum_error = max(maximum_error, math.sqrt(dx * dx + dy * dy + dr * dr))
sx = (current[0] - previous_point[0]) / units_per_metre
sy = (current[1] - previous_point[1]) / units_per_metre
polyline_length += math.hypot(sx, sy)
previous_point = current
sx = (b[0] - previous_point[0]) / units_per_metre
sy = (b[1] - previous_point[1]) / units_per_metre
polyline_length += math.hypot(sx, sy)
split = ((polyline_length > spacing_metres * (1.0 + 1.0e-9)
or maximum_error > chord_error_metres) and depth < 12)
if split:
t_mid = (t_a + t_b) * 0.5
middle = evaluate_raw(controls, span, t_mid, units_per_metre)
append_adaptive(t_a, t_mid, a, middle, depth + 1)
append_adaptive(t_mid, t_b, middle, b, depth + 1)
else:
parameters.append(t_b)
for segment in range(1, base_count + 1):
target_a = span_length * (segment - 1) / base_count
target_b = span_length * segment / base_count
t_a, t_b = parameter_at_arc(target_a), parameter_at_arc(target_b)
append_adaptive(t_a, t_b,
evaluate_raw(controls, span, t_a, units_per_metre),
evaluate_raw(controls, span, t_b, units_per_metre), 0)
points = [evaluate_raw(controls, span, t, units_per_metre) for t in parameters]
sampled_arc = [0.0] * len(points)
for index in range(1, len(points)):
dx = (points[index][0] - points[index - 1][0]) / units_per_metre
dy = (points[index][1] - points[index - 1][1]) / units_per_metre
sampled_arc[index] = sampled_arc[index - 1] + math.hypot(dx, dy)
sampled_span_length = sampled_arc[-1]
for index, point in enumerate(points):
progress = (sampled_arc[index] / sampled_span_length
if sampled_span_length > 1.0e-12 else parameters[index])
point[2] = controls[span][2] + (controls[span + 1][2] - controls[span][2]) * progress
for index in range(1, len(points)):
dx = (points[index][0] - points[index - 1][0]) / units_per_metre
dy = (points[index][1] - points[index - 1][1]) / units_per_metre
dz = (points[index][2] - points[index - 1][2]) / units_per_metre
length = math.sqrt(dx * dx + dy * dy + dz * dz)
progress_a = (sampled_arc[index - 1] / sampled_span_length
if sampled_span_length > 1.0e-12 else parameters[index - 1])
progress_b = (sampled_arc[index] / sampled_span_length
if sampled_span_length > 1.0e-12 else parameters[index])
floor_a = controls[span][2] - controls[span][3]
floor_b = controls[span + 1][2] - controls[span + 1][3]
result.append({"a": points[index - 1], "b": points[index],
"along": along_metres, "length": length, "span": span,
"floor_a": floor_a + (floor_b - floor_a) * progress_a,
"floor_b": floor_a + (floor_b - floor_a) * progress_b})
along_metres += length
return result
def closest_route_height(route: list[list[float]], x: float, y: float) -> tuple[float, float]:
best_height, best_distance_sq = 0.0, float("inf")
for a, b in zip(route, route[1:]):
dx, dy = b[0] - a[0], b[1] - a[1]
length_sq = dx * dx + dy * dy
t = min(max(((x - a[0]) * dx + (y - a[1]) * dy) / length_sq, 0.0), 1.0) \
if length_sq > 1.0e-12 else 0.0
px, py = a[0] + t * dx, a[1] + t * dy
distance_sq = (x - px) ** 2 + (y - py) ** 2
if distance_sq < best_distance_sq:
best_distance_sq = distance_sq
best_height = a[2] + (b[2] - a[2]) * t
return best_height, best_distance_sq
def prepared_segments(layout: dict) -> list[list[dict]]:
qpm = SUBVOXELS_PER_METRE
strata = {item["strate_index"]: item for item in layout["strates"]}
all_passages: list[list[dict]] = []
for passage in layout["passages"]:
controls_q = [[float(value) for value in point["position_q"]]
+ [float(point["radius_q"])] for point in passage["control_points"]]
controls = [point.copy() for point in controls_q]
upper_mouth = [value / qpm for value in passage["upper_mouth_q"]]
upper_depth = max(0.0, (controls[0][2] / qpm) - upper_mouth[2] + 1.13)
upper_stratum = strata.get(passage["upper_strate_index"], {})
if upper_stratum.get("generator_kind") == "FloatingIslands":
route = []
for anchor in upper_stratum.get("anchors", []):
if anchor.get("kind") == "route_point" and len(anchor.get("values", [])) >= 3:
route.append([value / qpm for value in anchor["values"][:3]])
if route:
route_height, distance_sq = closest_route_height(
route, controls[0][0] / qpm, controls[0][1] / qpm)
if distance_sq <= (controls[0][3] / qpm + 2.0) ** 2:
upper_depth = max(upper_depth,
controls[0][2] / qpm - route_height + 1.13)
controls[0][3] = max(controls[0][3], upper_depth * qpm)
lower_mouth = [value for value in passage["lower_mouth_q"]]
player_center_q = round(1.13 * qpm)
route_end = len(controls) - 1
for index in range(1, len(controls_q)):
p = controls_q[index]
if (int(p[0]) == lower_mouth[0] and int(p[1]) == lower_mouth[1]
and int(p[2] - p[3] + player_center_q) == lower_mouth[2]):
route_end = index
raw = build_raw_segments(controls, qpm)
total_path = 0.0
for segment in raw:
if segment["span"] + 1 == route_end:
total_path = segment["along"] + segment["length"]
radii = [(0.0, controls[0][3] / qpm)]
for segment in raw:
if segment["span"] < route_end:
radii.append((segment["along"] + segment["length"],
segment["b"][3] / qpm))
body_radii = [radius for along, radius in radii
if along >= 10.0 and total_path - along >= 10.0]
minimum_body = min(body_radii) if body_radii else min(radius for _, radius in radii)
floor_depth = max(0.5, minimum_body - 0.23)
prepared = []
for segment in raw:
route_segment = segment["span"] < route_end
along_b = segment["along"] + segment["length"]
def effective_z(raw_z: float, radius: float, planned_floor: float,
along: float) -> float:
raw_z /= qpm
radius /= qpm
planned_floor /= qpm
local_depth = min(floor_depth, max(0.0, radius - 0.23))
if route_segment or abs(along - total_path) <= 1.0e-9:
return planned_floor + local_depth
route_depth = max(0.0, radius - 0.23)
return raw_z - route_depth + local_depth
a = [segment["a"][0] / qpm, segment["a"][1] / qpm,
effective_z(segment["a"][2], segment["a"][3],
segment["floor_a"], segment["along"])]
b = [segment["b"][0] / qpm, segment["b"][1] / qpm,
effective_z(segment["b"][2], segment["b"][3],
segment["floor_b"], along_b)]
prepared.append({"a": a, "b": b, "radius_a": segment["a"][3] / qpm,
"radius_b": segment["b"][3] / qpm})
all_passages.append(prepared)
return all_passages
def profile_tiles() -> list[tuple[int, int, int, int, int, int, int, int]]:
result = []
for strate, path in enumerate(BASELINE_LOGS):
if not path.is_file():
raise SystemExit(f"missing baseline profile: {path}")
seen = set()
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
if not line.startswith("[PassageComposerTile] "):
continue
row = dict(PROFILE.findall(line))
match = TILE_KEY.search(row.get("key", ""))
if not match:
continue
x, y, z, lod = map(int, match.groups())
if lod > 2:
continue
key = (x, y, z, lod)
if key in seen:
continue
seen.add(key)
result.append((strate, x, y, z, lod, int(row.get("features", 0)),
int(row.get("route_candidates", 0)),
int(row.get("sweep_pairs", 0))))
return result
def main() -> int:
if not LAYOUT.is_file():
raise SystemExit(f"missing resolved layout: {LAYOUT}")
OUT.mkdir(parents=True, exist_ok=True)
layout = json.loads(LAYOUT.read_text(encoding="utf-8"))
passages = prepared_segments(layout)
tiles = profile_tiles()
input_path = OUT / "reach-input.tsv"
with input_path.open("w", encoding="utf-8") as output:
for passage_index, segments in enumerate(passages):
for segment_index, segment in enumerate(segments):
values = [*segment["a"], *segment["b"],
segment["radius_a"], segment["radius_b"]]
output.write("S {} {} {}\n".format(passage_index, segment_index,
" ".join(f"{value:.15g}" for value in values)))
for tile in tiles:
output.write("T {}\n".format(" ".join(str(value) for value in tile)))
binary = OUT / "CountReachCandidates"
compile_log = OUT / "compile.log"
with compile_log.open("w", encoding="utf-8") as log:
built = subprocess.run(["g++", "-std=c++20", "-O3", "-DNDEBUG",
str(HERE / "CountReachCandidates.cpp"), "-o", str(binary)],
cwd=ROOT, stdout=log, stderr=subprocess.STDOUT,
check=False)
if built.returncode:
raise SystemExit(f"counter compile failed: {compile_log}")
result_path = OUT / "results.csv"
run_log = OUT / "counter.log"
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
str(binary), str(input_path), str(result_path)]
with run_log.open("w", encoding="utf-8") as log:
result = subprocess.run(command, cwd=ROOT, stdout=log, stderr=subprocess.STDOUT,
check=False, timeout=1740)
if result.returncode:
raise SystemExit(f"counter failed: {run_log}; exit={result.returncode}")
print(f"[L129Precount] segments={[len(x) for x in passages]} "
f"profile_tiles={len(tiles)} input={input_path.relative_to(ROOT)} "
f"results={result_path.relative_to(ROOT)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,54 @@
# L129 — tunnel reach
**Decision: no-go for performance.** The 2 m candidate passed the geometry checks, but it was slower than both timing references and failed every UE movement comparison. The count pass also found a units mismatch in v225's reach bounds, so the requested physical reach cannot be cut cheaply against the source as it stands.
## What the reach includes
The 4.0 m term is the signed-distance influence cutoff outside the bore. The bound adds 0.21 m for wall relief and reserves 0.125 m for the 0.50 m smooth corner (`width/6` is about 0.083 m). The plain shell is 0.75 m thick, so its minimum reach with relief is 0.96 m. The larger term wins: `r + max(R + 0.21 + 0.125, 0.75 + 0.21) + 0.05 m`. At `R=4 m` and the maximum `r=3.5 m`, that is 7.885 m.
The mouth blend is 5 m **along the route**, not an added radial reach. The open-air half-pipe's side wall is 1 m high; its rounded crest uses the same reach. L129 leaves the 0.75 m shell rule, smooth floor `h(s)`, rounded crest, and grade limit unchanged. The highest audited grade was 10.695°.
## Counts before the build
Counts cover the four owner strata at LOD0–2, with 993 route segments. “Pairs” are route-segment/sample checks; “support” is unique per-passage samples inside the distance cutoff. The v225 model's 29.87M pairs is close to the measured source profile's 29.41M.
| Reach and bounds | Pairs (M) | Support samples (M) | Overlap pairs (M) | Feature / proof-active tiles |
|---|---:|---:|---:|---:|
| v225 as coded, nominal 4 m | 29.87 | 8.57 | 20.90 | 721 / 677 |
| 4 m, physical bounds | 124.97 | 11.72 | 46.64 | 826 / 791 |
| 3 m, physical bounds | 87.06 | 8.84 | 30.42 | 787 / 764 |
| 2 m, physical bounds | 56.86 | 6.32 | 18.48 | 755 / 729 |
| 1.5 m, physical bounds | 44.74 | 5.21 | 13.93 | 743 / 718 |
Against a correctly bounded 4 m model, 2 m removes 54.5% of pairs and 60.4% of overlap pairs; 71 feature tiles and 62 proof-active tiles become free. Against the v225-as-coded count model, however, physical 2 m adds 90.3% of raw pairs and leaves 34 more feature tiles and 52 more proof-active tiles. Physical 1.5 m still adds 49.8% of pairs and leaves 22 more feature tiles and 41 more proof-active tiles. So the smaller reach does **not** unlock proofs or tile skips versus the shipped source.
The cause is in `BuildPassageSpatialIndices`: positions are converted to core voxels before the reach is added, but v225 adds the metre value directly. At 0.25 m per core voxel, the nominal 4.335 m bound is only 1.084 m per axis; the sweep cutoff itself remains 4 m. L129 converts the smaller physical bounds to core voxels. That makes its broadphase boxes larger than v225's, even though its actual influence cutoff is smaller. The extra segment/sample checks erase the predicted saving. The 2 m setting was selected from the pre-build counts and is the only reach fully timed.
## Timing and quality
Five interleaved L80 runs per W8/W12 cell were completed on a quiet machine (load 0.71, CPU idle 97.3%). L125 is the `b96dd73` source. Every near-ready cell and every whole-view cell was slower than L109 and b96dd73:
| Stratum / workers | Near vs L109 / base | Whole view vs L109 / base |
|---|---:|---:|
| S0 W8 | +11.1% / +9.3% | +7.5% / +7.2% |
| S0 W12 | +11.2% / +6.6% | +5.5% / +3.3% |
| S1 W8 | +16.7% / +7.8% | +8.7% / +5.3% |
| S1 W12 | +17.9% / +9.9% | +9.5% / +5.6% |
| S2 W8 | +12.8% / +8.4% | +5.3% / +5.2% |
| S2 W12 | +17.1% / +9.1% | +10.3% / +5.7% |
| S3 W8 | +13.4% / +4.9% | +6.3% / +2.8% |
| S3 W12 | +18.2% / +6.9% | +8.9% / +4.2% |
Five UE runs at 30 m/s also missed the b96dd73 gate: near coverage 38.05% vs 40.22% (−2.17 points), far holes 4.03% vs 3.46% (+0.58 points), tick p99 11.54 vs 10.80 ms (+0.74 ms), and worst tick 14.12 vs 13.27 ms (+0.85 ms). The Linux UE host build passed after timing with `-NoUBA -MaxParallelActions=4`; Unreal was not run under `prlimit`.
Fast verification passed (259 checks; 5 historical diagnostics), walkability passed at 21,425/21,425, all three passage grades stayed below 15°, and the seam audit passed 4 joins / 4,900 samples with no mismatches. Clang and GCC hashes matched for W1/W8/W12 across all three tested owner layouts (18 cases). Evaluator version 226 and three canonical owner hash baselines were added.
The 23 L125 pose captures and six paired mouth close-ups are verified. All before/after PNGs are pixel-identical; these views show no visible change. The floor probe peaked at 7.9 mm, above a strict “few millimetres” reading.
## Captures and records
- [L125 poses: before / L129 comparison](captures/COMPARE.html)
- [Mouth close-ups: before / L129 comparison](captures/L129-mouth/COMPARE.html)
- [L80 timing results](../../../../Saved/Linux/l129/timing/summary.json)
- [UE movement results](../../../../Saved/Linux/l129/movement/summary.json)
- [Pre-build count results](../../../../Saved/Linux/l129/precount/results.csv)
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Render the 23 L125 camera poses with the L129 candidate."""
from __future__ import annotations
import importlib.util
import json
import math
import os
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RESEARCH = Path(__file__).resolve().parent
CAPTURES = RESEARCH / "captures"
SAVED = ROOT / "Saved/Linux/l129/captures"
BASELINE_INDEX = (ROOT / "Standalone/Research/WorldAssemblerM3/Linux125"
/ "captures/L125/capture-index.json")
LAYOUT = ROOT / "Saved/Linux/l129/hashes/owner4-surface-world-w12-clang.layout.json"
CLANG = ROOT / os.environ.get("L129_CAPTURE_CLANG",
"Saved/Linux/l129/bin/VoxelForgeCoreHeadless-l129-clang")
RENDERER = ROOT / os.environ.get("L129_CAPTURE_RENDERER",
"Saved/Linux/l129/bin/VoxelForgeOffscreenCapture-l129-clang")
HELPER_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux113/RunL113Captures.py"
SPEC = importlib.util.spec_from_file_location("l113_capture_helpers", HELPER_PATH)
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
HELPERS.SAVED = SAVED
HELPERS.BINARIES = {"L129": CLANG}
HELPERS.RENDERERS = {"L129": RENDERER}
def main() -> int:
if any(not path.is_file() for path in (BASELINE_INDEX, LAYOUT, CLANG, RENDERER)):
raise SystemExit("missing L125 camera set, L129 layout, or capture binary")
baseline = json.loads(BASELINE_INDEX.read_text(encoding="utf-8"))
layout = json.loads(LAYOUT.read_text(encoding="utf-8"))
rows = []
CAPTURES.mkdir(parents=True, exist_ok=True)
SAVED.mkdir(parents=True, exist_ok=True)
for view in baseline["captures"]:
row = HELPERS.capture("L129", view, layout)
row["variant"] = "L129-v226"
if view["kind"] == "rock-floor-close":
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({"floor_anchor_position_m": anchor["position_m"],
"floor_anchor_normal": anchor["normal"],
"closeup_camera_to_floor_distance_m": camera_distance,
"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 row["drawn_triangles"] <= 0
or row["pixel_quality_status"] not in
("pass", "low_contrast_geometry_visible")):
raise RuntimeError(f"L129 floor close-up failed at {view['name']}")
rows.append(row)
if len(rows) != len(baseline["captures"]) or len(rows) != 23:
raise RuntimeError(f"expected all 23 L125 views, captured {len(rows)}")
index = CAPTURES / "L129" / "capture-index.json"
index.parent.mkdir(parents=True, exist_ok=True)
index.write_text(json.dumps({"captures": rows}, indent=2) + "\n",
encoding="utf-8")
html = ["<!doctype html><meta charset='utf-8'>",
"<title>L125 / L129 tunnel capture comparison</title>",
"<style>body{font:14px system-ui;margin:24px;background:#17191d;color:#eee}"
"section{margin:28px 0}h2{font-size:18px}div{display:grid;"
"grid-template-columns:1fr 1fr;gap:12px}img{width:100%;height:auto}"
"p{color:#bbb}</style>", "<h1>L125 / L129 v226: identical views</h1>"]
old_by_name = {Path(row["capture"]).name: row for row in baseline["captures"]}
for row in rows:
name = Path(row["capture"]).name
old = old_by_name[name]
old_path = os.path.relpath(ROOT / "Standalone/Research/WorldAssemblerM3/Linux125"
/ old["capture"], CAPTURES)
new_path = os.path.relpath(RESEARCH / row["capture"], CAPTURES)
html.extend(["<section>", f"<h2>{row['passage']} — {row['station']}</h2>",
f"<p>{row['kind']} · L125 {old['pixel_quality_status']} · "
f"L129 {row['pixel_quality_status']}</p>", "<div>",
f"<figure><figcaption>L125</figcaption><img src='{old_path}'></figure>",
f"<figure><figcaption>L129 v226</figcaption><img src='{new_path}'></figure>",
"</div>", "</section>"])
(CAPTURES / "COMPARE.html").write_text("\n".join(html) + "\n", encoding="utf-8")
print(f"[L129Captures] rendered={len(rows)} views index={index.relative_to(ROOT)}",
flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Probe reconstructed tunnel floor roots at the nine L119 stations."""
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 / os.environ.get("L129_PROBE_DIR", "Saved/Linux/l129/density-probe")
BINARY = ROOT / os.environ.get("L129_PROBE_BINARY",
"Saved/Linux/l129/bin/VoxelForgeCoreHeadless-l129-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, layout = OUT / "owner4-clang.log", 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"density probe failed: {log}; exit={result.returncode}")
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("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"[L129DensityProbe] 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,119 @@
#!/usr/bin/env python3
"""Check Clang/GCC W1/W8/W12 hashes and compare the candidate with L119."""
from __future__ import annotations
import json
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / os.environ.get("L129_HASH_DIR", "Saved/Linux/l129/hashes")
DATASETS = {
"owner4": ROOT / "Standalone/data/owner_world.json",
"owner2": ROOT / "Standalone/data/regression/owner_world_2strate_l61.json",
}
BINARIES = {
"clang": ROOT / os.environ.get("L129_HASH_CLANG",
"Saved/Linux/l129/bin/VoxelForgeCoreHeadless-l129-clang"),
"gcc": ROOT / os.environ.get("L129_HASH_GCC",
"Saved/Linux/l129/bin/VoxelForgeCoreHeadless-l129-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("[L129HashRun] " + 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 L129 hash input")
previous_path = ROOT / "Saved/Linux/l119/hashes/summary.json"
previous_doc = json.loads(previous_path.read_text(encoding="utf-8"))
previous = {(row["dataset"], row["recipe"], row["workers"], row["compiler"]): row
for row in previous_doc["runs"]}
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"]
stable = all(row["hashes"] == reference for row in group)
old = previous[(dataset, recipe, 12, "clang")]["hashes"]
groups[key] = {
"all_worker_compiler_hashes_match": stable,
"matches_l119_v224": next(row for row in group
if row["workers"] == 12 and row["compiler"] == "clang")["hashes"] == old,
"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 stable:
raise SystemExit(f"determinism mismatch: {key}")
summary = {"runs": rows, "groups": groups}
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
encoding="utf-8")
baseline_dir = ROOT / "Standalone/Tools/verify/baselines/owner-hash/canonical"
for dataset, recipe, style, baseline_name in (
("owner4", "surface-world", 1,
"owner-hash-owner4-surface-world-style1-v226.baseline"),
("owner2", "surface-world", 1,
"owner-hash-surface-world-style1-v226.baseline"),
("owner2", "floating-islands", 2,
"owner-hash-floating-islands-style2-v226.baseline"),
):
canonical = next(row for row in rows if row["dataset"] == dataset
and row["recipe"] == recipe and row["workers"] == 12
and row["compiler"] == "clang")
baseline = baseline_dir / baseline_name
baseline.write_text("\n".join(canonical["hashes"]) + "\n",
encoding="utf-8")
print("[L129HashSummary] " + json.dumps(groups, sort_keys=True), flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Render matching baseline and L129 close views of each tunnel mouth."""
from __future__ import annotations
import hashlib
import importlib.util
import json
import os
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RESEARCH = Path(__file__).resolve().parent
CAPTURES = RESEARCH / "captures/L129-mouth"
SAVED = ROOT / "Saved/Linux/l129/captures/mouth-closeups"
BASE_LAYOUT = ROOT / "Saved/Linux/l125/hashes/owner4-surface-world-w12-clang.layout.json"
AFTER_LAYOUT = ROOT / "Saved/Linux/l129/hashes/owner4-surface-world-w12-clang.layout.json"
HELPER_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux113/RunL113Captures.py"
SPEC = importlib.util.spec_from_file_location("l113_capture_helpers", HELPER_PATH)
if SPEC is None or SPEC.loader is None:
raise SystemExit("cannot load verified capture helpers")
HELPERS = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(HELPERS)
HELPERS.RESEARCH = RESEARCH
HELPERS.CAPTURES = CAPTURES
HELPERS.SAVED = SAVED
HELPERS.BINARIES = {
"before": ROOT / "Saved/Linux/l125/bin/VoxelForgeCoreHeadless-l125-clang",
"after": ROOT / "Saved/Linux/l129/bin/VoxelForgeCoreHeadless-l129-clang",
}
HELPERS.RENDERERS = {
"before": ROOT / "Saved/Linux/l125/bin/VoxelForgeOffscreenCapture-l125-clang",
"after": ROOT / "Saved/Linux/l129/bin/VoxelForgeOffscreenCapture-l129-clang",
}
def main() -> int:
required = [BASE_LAYOUT, AFTER_LAYOUT, *HELPERS.BINARIES.values(),
*HELPERS.RENDERERS.values()]
if any(not path.is_file() for path in required):
raise SystemExit("missing baseline/candidate layout or capture binary")
before_layout = json.loads(BASE_LAYOUT.read_text(encoding="utf-8"))
after_layout = json.loads(AFTER_LAYOUT.read_text(encoding="utf-8"))
if before_layout["passages"] != after_layout["passages"]:
raise RuntimeError("baseline and candidate passage routes differ")
views = [view for view in HELPERS.make_views(before_layout, [],
use_derived_shape=True, use_spline=True, include_open_air=False)
if view["kind"] == "mouth"]
if len(views) != 2 * len(before_layout["passages"]):
raise RuntimeError("unexpected mouth camera count")
rows = []
CAPTURES.mkdir(parents=True, exist_ok=True)
SAVED.mkdir(parents=True, exist_ok=True)
for view in views:
for variant, layout in (("before", before_layout), ("after", after_layout)):
row = HELPERS.capture(variant, view, layout)
row["variant"] = "L125-v225" if variant == "before" else "L129-v226"
row["view"] = view["name"]
image = ROOT / "Standalone/Research/WorldAssemblerM3/Linux129" / row["capture"]
row["image_sha256"] = hashlib.sha256(image.read_bytes()).hexdigest()
rows.append(row)
index = CAPTURES / "capture-index.json"
index.write_text(json.dumps({"captures": rows}, indent=2) + "\n", encoding="utf-8")
old_rows = {row["view"]: row for row in rows if row["variant"] == "L125-v225"}
new_rows = {row["view"]: row for row in rows if row["variant"] == "L129-v226"}
html = ["<!doctype html><meta charset='utf-8'>",
"<title>L125 / L129 mouth close-ups</title>",
"<style>body{font:14px system-ui;margin:24px;background:#17191d;color:#eee}"
"section{margin:28px 0}div{display:grid;grid-template-columns:1fr 1fr;gap:12px}"
"img{width:100%;height:auto}p{color:#bbb}</style>",
"<h1>L125 v225 / L129 v226 tunnel mouths</h1>"]
for name in old_rows:
old, new = old_rows[name], new_rows[name]
old_path = os.path.relpath(RESEARCH / old["capture"], CAPTURES)
new_path = os.path.relpath(RESEARCH / new["capture"], CAPTURES)
equality = "pixel-identical" if old["image_sha256"] == new["image_sha256"] else "pixels differ"
html.extend(["<section>", f"<h2>{old['passage']} — {old['station']}</h2>",
f"<p>{equality}; {old['pixel_quality_status']} / {new['pixel_quality_status']}</p>",
"<div>", f"<figure><figcaption>L125 v225</figcaption><img src='{old_path}'></figure>",
f"<figure><figcaption>L129 v226</figcaption><img src='{new_path}'></figure>",
"</div>", "</section>"])
(CAPTURES / "COMPARE.html").write_text("\n".join(html) + "\n", encoding="utf-8")
print(f"[L129MouthCaptures] views={len(views)} paired={len(rows)//2} "
f"index={index.relative_to(ROOT)}", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""Five interleaved L80 readiness runs for L109, L125 and L129."""
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 / os.environ.get("L129_TIMING_DIR", "Saved/Linux/l129/timing")
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"L109": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeCoreHeadless-l109-clang",
"L125": ROOT / "Saved/Linux/l125/bin/VoxelForgeCoreHeadless-l125-clang",
"L129": ROOT / os.environ.get("L129_CANDIDATE_BINARY",
"Saved/Linux/l129/bin/VoxelForgeCoreHeadless-l129-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"{value:.12f}" for value 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",
]
started = 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, near_match = PIPELINE.search(content), NEAR.search(content)
if result.returncode or pipeline_match is None or near_match is None:
raise SystemExit(f"readiness run failed or incomplete: {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() - started, 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("[L129TimingRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def cpu_idle_percent(interval_s: float = 2.0) -> float:
def totals() -> tuple[int, int]:
fields = Path("/proc/stat").read_text(encoding="utf-8").splitlines()[0].split()
values = [int(value) for value in fields[1:]]
return sum(values), values[3] + (values[4] if len(values) > 4 else 0)
before_total, before_idle = totals()
time.sleep(interval_s)
after_total, after_idle = totals()
return 100.0 * (after_idle-before_idle) / max(1, after_total-before_total)
def main() -> int:
if not DATA.is_file() or any(not binary.is_file() for binary in BINARIES.values()):
raise SystemExit("missing L109/L125/L129 timing input")
OUT.mkdir(parents=True, exist_ok=True)
load_1m, idle_pct = os.getloadavg()[0], cpu_idle_percent()
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}\n"
f"2-second CPU idle: {idle_pct:.1f}%\ntop CPU users:\n{top}", encoding="utf-8")
print(f"[L129TimingGate] load_1m={load_1m:.2f} cpu_idle_2s={idle_pct:.1f}%",
flush=True)
if load_1m >= 0.9 or idle_pct < 97.0:
raise SystemExit("timing refused: machine is above the quiet-load gate")
rows = []
versions = ("L109", "L125", "L129")
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", "L125"):
for key in ("lod02_ready_s", "whole_view_ready_s"):
item[f"L129_vs_{comparison}_{key}_delta_pct"] = round(
100.0 * (medians["L129"][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,257 @@
#!/usr/bin/env python3
"""Five 30 m/s UE runs for the L129 tunnel reach candidate."""
from __future__ import annotations
import argparse
import json
import math
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
HOST = Path("/home/jahni/VFHost")
EDITOR = Path("/home/jahni/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd")
SPAWN_TN_M = (19.166015625, -14.2001953125, -753.3740234375)
SPAWN_FI_M = (25.8369140625, 3.19921875, -737.0)
def percentile(values: list[float], fraction: float) -> float | None:
if not values:
return None
ordered = sorted(values)
return ordered[max(0, min(len(ordered) - 1, math.ceil(len(ordered) * fraction) - 1))]
def read_events(path: Path) -> list[dict]:
rows = []
if path.is_file():
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
if not line.startswith("{"):
continue
try:
rows.append(json.loads(line))
except json.JSONDecodeError:
pass
return rows
def run(scenario: str, condition: str, repetition: int, turn: bool = False) -> dict:
lookahead = int(condition in ("lookahead", "turn_lookahead"))
if scenario == "movement":
speed, start, duration, exit_s, slot, spawn = (
3000.0, 6.0, 12.0, 23.0, 0, SPAWN_TN_M)
else:
speed, start, duration, exit_s, slot, spawn = (
140.0, 20.0, 12.0, 37.0, 1, SPAWN_FI_M)
label = f"{scenario}-{condition}-r{repetition}"
output_group = "turn" if turn else scenario
out = ROOT / "Saved/Linux/l129" / output_group / condition / "unreal"
host_out = HOST / "Plugins/VoxelForge/Saved/Linux/l129" / output_group / condition
host_saved = HOST / "Saved/Linux/l129"
trace = out / f"{label}.trace.jsonl"
abslog = out / f"{label}.log"
stdout = out / f"{label}.stdout.log"
export = host_out / "output" / label
userdir = host_saved / "user" / label
zendata = host_saved / "zen" / label
ddc = host_saved / "DDC"
for path in (out, export, userdir, zendata, ddc):
path.mkdir(parents=True, exist_ok=True)
trace.unlink(missing_ok=True)
spawn_cm = ",".join(f"{value * 100.0:.3f}" for value in spawn)
command = [
str(EDITOR), str(HOST / "VFHost.uproject"), "-nullrhi", "-unattended",
"-nop4", "-nosplash", "-NoSound", "-corelimit=12", "-NoZenAutoLaunch",
"-notraceserver", "-nocrashreports", "-DisablePlugins=AndroidFileServer",
"-ddc=NoZenLocalFallback", f"-userdir={userdir}", f"-ZenDataPath={zendata}",
f"-LocalDataCachePath={ddc}", "-run=VoxelForgeExplore", "-seed=0",
"-archetype=TunnelNetwork", f"-slot={slot}", "-modes=export", "-gameconfig=1",
"-coreworldtest=1", "-ownerfourstrateplan=1", "-corevoxelsizecm=25",
"-coreislandtest=0", "-corefallbacktest=0", "-coreofftest=0", "-coreplantest=0",
"-settingsref=/Game/VoxelForge/DA_Settings.DA_Settings", "-corehybridmesher=0",
f"-out={export}", "-voxel.StreamingCoarseFirst=1",
"-voxel.StreamingDynamicNearReserve=1", "-voxel.StreamingLegacyPendingResultCap=0",
"-voxel.CoreRegionConcurrentAdmission=1", "-voxel.StreamingResultBacklogCap=256",
"-voxel.StreamingReadyQueueDepth=8", "-voxel.StreamingBackgroundWorkerCap=1",
"-voxel.CoreColumnCacheDirectMapped=0", "-voxel.StreamingFarApplySupplement=1",
"-voxel.CoreMCGradientMemoization=1", "-voxel.StreamingCollisionLookahead=1",
f"-voxel.CoreRegionLookahead={lookahead}", "-voxel.TestMove=1",
f"-voxel.TestMoveSpeedCmPerSecond={speed:.1f}",
f"-voxel.TestMoveStartSeconds={start:.1f}",
f"-voxel.TestMoveDurationSeconds={duration:.1f}",
f"-voxel.TestExitSeconds={exit_s:.1f}", "-voxel.TestMoveDirection=1,0,0",
"-voxel.StreamingSchedulerPolicy=3", "-voxel.TestExitOnSteady=0",
"-coreworldstreamtrace=1", f"-voxel.StartupTraceSpawnCm={spawn_cm}",
"-voxel.StartupTraceUntilEndPlay=1", "-voxel.StartupTraceThroughCrossing=1",
"-voxel.StartupTraceMaxTiles=16384", f"-voxel.StartupTraceFile={trace}",
f"-abslog={abslog}",
]
if turn:
command.extend(["-voxel.TestMoveTurnAfterSeconds=6.0",
"-voxel.TestMoveTurnDirection=0,1,0"])
started = time.monotonic()
wrapped = ["taskset", "-c", "0-11", "timeout", "1740s", *command]
with stdout.open("w", encoding="utf-8") as stream:
process = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1740)
events = read_events(trace)
samples = [event for event in events if event.get("type") == "streaming_sample"]
moving = [event for event in samples if float(event.get("speed_cm_s", 0.0)) >= 50.0]
stop_event = next((event for event in events
if event.get("type") == "streaming_test_move_stopped"), {})
stop_s = float(stop_event.get("t_s", 0.0))
move_start = min((float(event.get("t_s", 0.0)) for event in moving), default=float("inf"))
dwell = [event for event in samples if float(event.get("t_s", 0.0)) >= stop_s
and float(event.get("speed_cm_s", 0.0)) < 50.0]
backend = next((event for event in events if event.get("type") == "worker_backend_config"), {})
cap = next((event for event in events if event.get("type") == "streaming_worker_cap_readback"), {})
if process.returncode or len(moving) < 40 or len(dwell) < 16:
raise RuntimeError(f"incomplete UE run {label}: exit={process.returncode}, "
f"move={len(moving)}, dwell={len(dwell)}, trace={trace}")
if (int(backend.get("streaming_far_apply_supplement", -1)) != 1
or int(cap.get("streaming_background_worker_cap", -1)) != 1
or int(backend.get("effective_tile_slots", -1)) != 8):
raise RuntimeError(f"configuration readback mismatch: {backend}; {cap}")
moving_rows = []
for event in moving:
desired = event.get("desired_by_lod", [0] * 9)
resident = event.get("resident_by_lod", [0] * 9)
covered = event.get("covered_by_lod", resident)
moving_rows.append({
"near_ready_pct": 100.0 * sum(resident[:3]) / max(1, sum(desired[:3])),
"lod0_ready_pct": 100.0 * resident[0] / max(1, desired[0]),
"lod1_ready_pct": 100.0 * resident[1] / max(1, desired[1]),
"lod2_ready_pct": 100.0 * resident[2] / max(1, desired[2]),
"far_hole_pct": 100.0 * (sum(desired[3:]) - sum(covered[3:]))
/ max(1, sum(desired[3:])),
})
work = [event for event in events if event.get("type") == "game_thread_work_frame"
and move_start <= float(event.get("t_s", 0.0)) < stop_s]
ticks = [float(event.get("tick_ms", 0.0)) for event in work]
updates = [float(event.get("update_chunks_ms", 0.0)) for event in work]
arrival = [event for event in samples if float(event.get("t_s", 0.0)) < start
and float(event.get("speed_cm_s", 0.0)) < 50.0]
def settled(event: dict, end: int) -> bool:
desired = event.get("desired_by_lod", [0] * 9)
actual = event.get("settled_by_lod", [0] * 9)
return sum(desired[:end]) > 0 and sum(actual[:end]) >= sum(desired[:end])
def far_holes(event: dict) -> float | None:
desired = event.get("desired_by_lod", [0] * 9)[3:]
covered = event.get("covered_by_lod", [0] * 9)[3:]
return 100.0 * (sum(desired) - sum(covered)) / sum(desired) if sum(desired) else None
near_settled = next((event for event in arrival if settled(event, 3)), None)
whole_settled = next((event for event in arrival if settled(event, 9)), None)
far_clear = next((event for event in arrival if far_holes(event) == 0.0), None)
compile_done = [event for event in events if event.get("type") == "core_region_compile_complete"
and move_start <= float(event.get("t_s", 0.0)) < stop_s]
compile_requests = [event for event in events
if event.get("type") == "core_region_compile_request"
and move_start <= float(event.get("t_s", 0.0)) < stop_s]
lookahead_turns = [event for event in events
if event.get("type") == "core_region_lookahead_turn"]
row = {
"scenario": scenario, "condition": condition, "repetition": repetition,
"speed_mps": speed / 100.0, "core_region_lookahead": lookahead,
"exit_code": process.returncode, "wall_s": round(time.monotonic() - started, 3),
"moving_samples": len(moving), "dwell_samples": len(dwell),
"near_ready_moving_mean_pct": statistics.mean(r["near_ready_pct"] for r in moving_rows),
"lod0_ready_moving_mean_pct": statistics.mean(r["lod0_ready_pct"] for r in moving_rows),
"lod1_ready_moving_mean_pct": statistics.mean(r["lod1_ready_pct"] for r in moving_rows),
"lod2_ready_moving_mean_pct": statistics.mean(r["lod2_ready_pct"] for r in moving_rows),
"far_holes_moving_mean_pct": statistics.mean(r["far_hole_pct"] for r in moving_rows),
"game_thread_tick_p99_ms": percentile(ticks, 0.99),
"game_thread_tick_max_ms": max(ticks, default=None),
"stream_update_p99_ms": percentile(updates, 0.99),
"stream_update_max_ms": max(updates, default=None),
"lod02_settled_t_s": float(near_settled["t_s"]) if near_settled else None,
"whole_view_settled_t_s": float(whole_settled["t_s"]) if whole_settled else None,
"far_clear_t_s": float(far_clear["t_s"]) if far_clear else None,
"far_holes_at_pre_move_pct": far_holes(arrival[-1]) if arrival else None,
"pending_gate_p50_ms": percentile(
[float(e.get("pending_gate_elapsed_ms", 0.0)) for e in compile_done], 0.50),
"compile_queue_p95_ms": percentile(
[float(e.get("compile_queue_ms", 0.0)) for e in compile_done], 0.95),
"compile_task_p95_ms": percentile(
[float(e.get("compile_task_ms", 0.0)) for e in compile_done], 0.95),
"compiled_tiles_total": sum(int(e.get("compiled_tiles", 0)) for e in compile_done),
"lookahead_tiles_peak": max((int(e.get("lookahead_region_tiles", 0))
for e in compile_requests), default=0),
"wasted_prefetch_tiles": sum(int(e.get("wasted_prefetch_tiles", 0))
for e in compile_done),
"lookahead_turn_events": lookahead_turns,
"compile_completion_count": len(compile_done),
"trace": str(trace.relative_to(ROOT)), "log": str(abslog.relative_to(ROOT)),
}
with (ROOT / "Saved/Linux/l129" / "runs.jsonl").open("a", encoding="utf-8") as stream:
stream.write(json.dumps(row, sort_keys=True) + "\n")
print("[L129Pair] " + json.dumps(row, sort_keys=True), flush=True)
return row
def pairs_for(runs: list[dict], scenario: str) -> list[dict]:
pairs = []
for repetition in range(1, 6):
baseline = next(row for row in runs if row["scenario"] == scenario
and row["condition"] == "baseline" and row["repetition"] == repetition)
candidate = next(row for row in runs if row["scenario"] == scenario
and row["condition"] == "lookahead" and row["repetition"] == repetition)
pairs.append({"repetition": repetition,
"near_ready_delta_pct": candidate["near_ready_moving_mean_pct"]
- baseline["near_ready_moving_mean_pct"],
"far_holes_delta_pct": candidate["far_holes_moving_mean_pct"]
- baseline["far_holes_moving_mean_pct"],
"tick_p99_delta_ms": candidate["game_thread_tick_p99_ms"]
- baseline["game_thread_tick_p99_ms"],
"tick_max_delta_ms": candidate["game_thread_tick_max_ms"]
- baseline["game_thread_tick_max_ms"]})
return pairs
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--scenario", choices=("movement",), default="movement")
args = parser.parse_args()
if not EDITOR.is_file():
raise SystemExit(f"missing Unreal editor: {EDITOR}")
root_out = ROOT / "Saved/Linux/l129" / args.scenario
root_out.mkdir(parents=True, exist_ok=True)
(ROOT / "Saved/Linux/l129").mkdir(parents=True, exist_ok=True)
runs = []
for repetition in range(1, 6):
runs.append(run(args.scenario, "candidate", repetition))
(root_out / "runs-partial.json").write_text(
json.dumps(runs, indent=2) + "\n", encoding="utf-8")
baseline_path = ROOT / "Saved/Linux/l127/ue/movement-pairs-summary.json"
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))["summary"]["merge"]["metrics"]
metric_names = ("near_ready_moving_mean_pct", "far_holes_moving_mean_pct",
"game_thread_tick_p99_ms", "game_thread_tick_max_ms")
candidate = {name: statistics.median(row[name] for row in runs) for name in metric_names}
reference = {name: baseline[name]["median"] for name in metric_names}
deltas = {name: candidate[name] - reference[name] for name in metric_names}
bar_pass = (candidate["near_ready_moving_mean_pct"] > reference["near_ready_moving_mean_pct"]
and candidate["far_holes_moving_mean_pct"] <= reference["far_holes_moving_mean_pct"]
and candidate["game_thread_tick_p99_ms"] <= reference["game_thread_tick_p99_ms"]
and candidate["game_thread_tick_max_ms"] <= reference["game_thread_tick_max_ms"])
summary = {
"design": "L129 R=2.0 m passage influence; five runs at 30 m/s.",
"scenario": args.scenario, "runs": runs,
"baseline": {"commit": "b96dd73", "source_summary": str(baseline_path),
"metrics": reference},
"candidate_medians": candidate, "deltas": deltas,
"bar_pass": bar_pass,
}
(root_out / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
encoding="utf-8")
print("[L129MovementSummary] " + json.dumps(summary, sort_keys=True), flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,188 @@
<!doctype html><meta charset='utf-8'>
<title>L125 / L129 tunnel capture comparison</title>
<style>body{font:14px system-ui;margin:24px;background:#17191d;color:#eee}section{margin:28px 0}h2{font-size:18px}div{display:grid;grid-template-columns:1fr 1fr;gap:12px}img{width:100%;height:auto}p{color:#bbb}</style>
<h1>L125 / L129 v226: identical views</h1>
<section>
<h2>passage/0-1/0 — rock-floor/401.7m</h2>
<p>rock-floor-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-rock-floor-station-1.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-0-1-0-rock-floor-station-1.png'></figure>
</div>
</section>
<section>
<h2>passage/0-1/0 — rock-floor/792.8m</h2>
<p>rock-floor-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-rock-floor-station-2.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-0-1-0-rock-floor-station-2.png'></figure>
</div>
</section>
<section>
<h2>passage/0-1/0 — rock-floor/1634.5m</h2>
<p>rock-floor-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-rock-floor-station-3.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-0-1-0-rock-floor-station-3.png'></figure>
</div>
</section>
<section>
<h2>passage/1-2/0 — rock-floor/27.0m</h2>
<p>rock-floor-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-rock-floor-station-1.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-1-2-0-rock-floor-station-1.png'></figure>
</div>
</section>
<section>
<h2>passage/1-2/0 — rock-floor/52.9m</h2>
<p>rock-floor-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-rock-floor-station-2.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-1-2-0-rock-floor-station-2.png'></figure>
</div>
</section>
<section>
<h2>passage/1-2/0 — rock-floor/78.9m</h2>
<p>rock-floor-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-rock-floor-station-3.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-1-2-0-rock-floor-station-3.png'></figure>
</div>
</section>
<section>
<h2>passage/2-3/0 — rock-floor/323.4m</h2>
<p>rock-floor-close · L125 low_contrast_geometry_visible · L129 low_contrast_geometry_visible</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-rock-floor-station-1.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-2-3-0-rock-floor-station-1.png'></figure>
</div>
</section>
<section>
<h2>passage/2-3/0 — rock-floor/333.4m</h2>
<p>rock-floor-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-rock-floor-station-2.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-2-3-0-rock-floor-station-2.png'></figure>
</div>
</section>
<section>
<h2>passage/2-3/0 — rock-floor/343.4m</h2>
<p>rock-floor-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-rock-floor-station-3.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-2-3-0-rock-floor-station-3.png'></figure>
</div>
</section>
<section>
<h2>passage/0-1/0 — section-1/4m-outside</h2>
<p>open-air-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-1-4m-outside.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-0-1-0-openair-1-4m-outside.png'></figure>
</div>
</section>
<section>
<h2>passage/0-1/0 — section-1/4m-interior-wall</h2>
<p>open-air-close · L125 low_contrast_geometry_visible · L129 low_contrast_geometry_visible</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-1-4m-interior-wall.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-0-1-0-openair-1-4m-interior-wall.png'></figure>
</div>
</section>
<section>
<h2>passage/0-1/0 — section-2/4m-outside</h2>
<p>open-air-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-2-4m-outside.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-0-1-0-openair-2-4m-outside.png'></figure>
</div>
</section>
<section>
<h2>passage/0-1/0 — section-2/4m-interior-wall</h2>
<p>open-air-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-2-4m-interior-wall.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-0-1-0-openair-2-4m-interior-wall.png'></figure>
</div>
</section>
<section>
<h2>passage/0-1/0 — section-3/4m-outside</h2>
<p>open-air-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-3-4m-outside.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-0-1-0-openair-3-4m-outside.png'></figure>
</div>
</section>
<section>
<h2>passage/0-1/0 — section-3/4m-interior-wall</h2>
<p>open-air-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-3-4m-interior-wall.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-0-1-0-openair-3-4m-interior-wall.png'></figure>
</div>
</section>
<section>
<h2>passage/1-2/0 — section-1/4m-outside</h2>
<p>open-air-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-openair-1-4m-outside.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-1-2-0-openair-1-4m-outside.png'></figure>
</div>
</section>
<section>
<h2>passage/1-2/0 — section-1/4m-interior-wall</h2>
<p>open-air-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-openair-1-4m-interior-wall.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-1-2-0-openair-1-4m-interior-wall.png'></figure>
</div>
</section>
<section>
<h2>passage/1-2/0 — section-2/4m-outside</h2>
<p>open-air-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-openair-2-4m-outside.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-1-2-0-openair-2-4m-outside.png'></figure>
</div>
</section>
<section>
<h2>passage/1-2/0 — section-2/4m-interior-wall</h2>
<p>open-air-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-openair-2-4m-interior-wall.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-1-2-0-openair-2-4m-interior-wall.png'></figure>
</div>
</section>
<section>
<h2>passage/2-3/0 — section-1/4m-outside</h2>
<p>open-air-close · L125 low_contrast_geometry_visible · L129 low_contrast_geometry_visible</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-openair-1-4m-outside.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-2-3-0-openair-1-4m-outside.png'></figure>
</div>
</section>
<section>
<h2>passage/2-3/0 — section-1/4m-interior-wall</h2>
<p>open-air-close · L125 low_contrast_geometry_visible · L129 low_contrast_geometry_visible</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-openair-1-4m-interior-wall.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-2-3-0-openair-1-4m-interior-wall.png'></figure>
</div>
</section>
<section>
<h2>passage/2-3/0 — section-2/4m-outside</h2>
<p>open-air-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-openair-2-4m-outside.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-2-3-0-openair-2-4m-outside.png'></figure>
</div>
</section>
<section>
<h2>passage/2-3/0 — section-2/4m-interior-wall</h2>
<p>open-air-close · L125 pass · L129 pass</p>
<div>
<figure><figcaption>L125</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-openair-2-4m-interior-wall.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='L129/passage-2-3-0-openair-2-4m-interior-wall.png'></figure>
</div>
</section>
@@ -0,0 +1,52 @@
<!doctype html><meta charset='utf-8'>
<title>L125 / L129 mouth close-ups</title>
<style>body{font:14px system-ui;margin:24px;background:#17191d;color:#eee}section{margin:28px 0}div{display:grid;grid-template-columns:1fr 1fr;gap:12px}img{width:100%;height:auto}p{color:#bbb}</style>
<h1>L125 v225 / L129 v226 tunnel mouths</h1>
<section>
<h2>passage/0-1/0 — upper-mouth</h2>
<p>pixel-identical; pass / pass</p>
<div>
<figure><figcaption>L125 v225</figcaption><img src='before/passage-0-1-0-upper-mouth.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='after/passage-0-1-0-upper-mouth.png'></figure>
</div>
</section>
<section>
<h2>passage/0-1/0 — lower-mouth</h2>
<p>pixel-identical; pass / pass</p>
<div>
<figure><figcaption>L125 v225</figcaption><img src='before/passage-0-1-0-lower-mouth.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='after/passage-0-1-0-lower-mouth.png'></figure>
</div>
</section>
<section>
<h2>passage/1-2/0 — upper-mouth</h2>
<p>pixel-identical; pass / pass</p>
<div>
<figure><figcaption>L125 v225</figcaption><img src='before/passage-1-2-0-upper-mouth.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='after/passage-1-2-0-upper-mouth.png'></figure>
</div>
</section>
<section>
<h2>passage/1-2/0 — lower-mouth</h2>
<p>pixel-identical; pass / pass</p>
<div>
<figure><figcaption>L125 v225</figcaption><img src='before/passage-1-2-0-lower-mouth.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='after/passage-1-2-0-lower-mouth.png'></figure>
</div>
</section>
<section>
<h2>passage/2-3/0 — upper-mouth</h2>
<p>pixel-identical; pass / pass</p>
<div>
<figure><figcaption>L125 v225</figcaption><img src='before/passage-2-3-0-upper-mouth.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='after/passage-2-3-0-upper-mouth.png'></figure>
</div>
</section>
<section>
<h2>passage/2-3/0 — lower-mouth</h2>
<p>pixel-identical; low_contrast_geometry_visible / low_contrast_geometry_visible</p>
<div>
<figure><figcaption>L125 v225</figcaption><img src='before/passage-2-3-0-lower-mouth.png'></figure>
<figure><figcaption>L129 v226</figcaption><img src='after/passage-2-3-0-lower-mouth.png'></figure>
</div>
</section>
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

@@ -0,0 +1,676 @@
{
"captures": [
{
"name": "passage-0-1-0-upper-mouth",
"passage": "passage/0-1/0",
"kind": "mouth",
"station": "upper-mouth",
"camera": [
8.324777182667596,
20.450707805615995,
-371.842578125
],
"target": [
7.855654585481068,
14.021907641948447,
-371.942578125
],
"yaw": -175.82640818958697,
"pitch": -0.8888012261216656,
"focus": [
8.0,
16.0,
-372.1224609375
],
"center_z": -372.1224609375,
"variant": "L125-v225",
"capture": "captures/L129-mouth/before/passage-0-1-0-upper-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/before/passage-0-1-0-upper-mouth.vfmesh",
"drawn_triangles": 243380,
"source_triangles": 287904,
"verified": true,
"pixel_quality_status": "pass",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
8.324777182667596,
20.450707805615995,
-371.842578125
],
"target_actual_m": [
7.855654585481068,
14.021907641948447,
-371.942578125
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/before/passage-0-1-0-upper-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-0-1-0-upper-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-0-1-0-upper-mouth-quality.log"
],
"view": "passage-0-1-0-upper-mouth",
"image_sha256": "20a56bb0419cc9c0ccb6a3c3f6a79e7f1614d095a8b233ecc65dc836fee9eb3b"
},
{
"name": "passage-0-1-0-upper-mouth",
"passage": "passage/0-1/0",
"kind": "mouth",
"station": "upper-mouth",
"camera": [
8.324777182667596,
20.450707805615995,
-371.842578125
],
"target": [
7.855654585481068,
14.021907641948447,
-371.942578125
],
"yaw": -175.82640818958697,
"pitch": -0.8888012261216656,
"focus": [
8.0,
16.0,
-372.1224609375
],
"center_z": -372.1224609375,
"variant": "L129-v226",
"capture": "captures/L129-mouth/after/passage-0-1-0-upper-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/after/passage-0-1-0-upper-mouth.vfmesh",
"drawn_triangles": 243380,
"source_triangles": 287904,
"verified": true,
"pixel_quality_status": "pass",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
8.324777182667596,
20.450707805615995,
-371.842578125
],
"target_actual_m": [
7.855654585481068,
14.021907641948447,
-371.942578125
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/after/passage-0-1-0-upper-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-0-1-0-upper-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-0-1-0-upper-mouth-quality.log"
],
"view": "passage-0-1-0-upper-mouth",
"image_sha256": "20a56bb0419cc9c0ccb6a3c3f6a79e7f1614d095a8b233ecc65dc836fee9eb3b"
},
{
"name": "passage-0-1-0-lower-mouth",
"passage": "passage/0-1/0",
"kind": "mouth",
"station": "lower-mouth",
"camera": [
32.68834531966199,
9.553722879000034,
-735.4805084803569
],
"target": [
24.394507482044844,
1.8614284070526241,
-736.6798828125
],
"yaw": -132.84502956130385,
"pitch": -6.05232406255234,
"focus": [
25.8369140625,
3.19921875,
-736.2672500132622
],
"center_z": -736.2672500132622,
"variant": "L125-v225",
"capture": "captures/L129-mouth/before/passage-0-1-0-lower-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/before/passage-0-1-0-lower-mouth.vfmesh",
"drawn_triangles": 549170,
"source_triangles": 582732,
"verified": true,
"pixel_quality_status": "pass",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
32.68834531966199,
9.553722879000034,
-735.4805084803569
],
"target_actual_m": [
24.394507482044844,
1.8614284070526241,
-736.6798828125
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/before/passage-0-1-0-lower-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-0-1-0-lower-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-0-1-0-lower-mouth-quality.log"
],
"view": "passage-0-1-0-lower-mouth",
"image_sha256": "01e5f52b7c9080cb34ed9fb92b60360426ef21bc8934309542cd8736a116fc29"
},
{
"name": "passage-0-1-0-lower-mouth",
"passage": "passage/0-1/0",
"kind": "mouth",
"station": "lower-mouth",
"camera": [
32.68834531966199,
9.553722879000034,
-735.4805084803569
],
"target": [
24.394507482044844,
1.8614284070526241,
-736.6798828125
],
"yaw": -132.84502956130385,
"pitch": -6.05232406255234,
"focus": [
25.8369140625,
3.19921875,
-736.2672500132622
],
"center_z": -736.2672500132622,
"variant": "L129-v226",
"capture": "captures/L129-mouth/after/passage-0-1-0-lower-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/after/passage-0-1-0-lower-mouth.vfmesh",
"drawn_triangles": 549170,
"source_triangles": 582732,
"verified": true,
"pixel_quality_status": "pass",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
32.68834531966199,
9.553722879000034,
-735.4805084803569
],
"target_actual_m": [
24.394507482044844,
1.8614284070526241,
-736.6798828125
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/after/passage-0-1-0-lower-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-0-1-0-lower-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-0-1-0-lower-mouth-quality.log"
],
"view": "passage-0-1-0-lower-mouth",
"image_sha256": "01e5f52b7c9080cb34ed9fb92b60360426ef21bc8934309542cd8736a116fc29"
},
{
"name": "passage-1-2-0-upper-mouth",
"passage": "passage/1-2/0",
"kind": "mouth",
"station": "upper-mouth",
"camera": [
23.39180762797774,
-15.509524798921928,
-731.7810546875
],
"target": [
17.28788584589878,
-13.618271096312476,
-731.8810546875
],
"yaw": -72.7848029211395,
"pitch": -0.8965458221544697,
"focus": [
19.166015625,
-14.2001953125,
-730.5610546875
],
"center_z": -730.5610546875,
"variant": "L125-v225",
"capture": "captures/L129-mouth/before/passage-1-2-0-upper-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/before/passage-1-2-0-upper-mouth.vfmesh",
"drawn_triangles": 593026,
"source_triangles": 638590,
"verified": true,
"pixel_quality_status": "pass",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
23.39180762797774,
-15.509524798921928,
-731.7810546875
],
"target_actual_m": [
17.28788584589878,
-13.618271096312476,
-731.8810546875
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/before/passage-1-2-0-upper-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-1-2-0-upper-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-1-2-0-upper-mouth-quality.log"
],
"view": "passage-1-2-0-upper-mouth",
"image_sha256": "8314a919e0c4b16390622371be96c89459ddef922c20711db281c55227e29de2"
},
{
"name": "passage-1-2-0-upper-mouth",
"passage": "passage/1-2/0",
"kind": "mouth",
"station": "upper-mouth",
"camera": [
23.39180762797774,
-15.509524798921928,
-731.7810546875
],
"target": [
17.28788584589878,
-13.618271096312476,
-731.8810546875
],
"yaw": -72.7848029211395,
"pitch": -0.8965458221544697,
"focus": [
19.166015625,
-14.2001953125,
-730.5610546875
],
"center_z": -730.5610546875,
"variant": "L129-v226",
"capture": "captures/L129-mouth/after/passage-1-2-0-upper-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/after/passage-1-2-0-upper-mouth.vfmesh",
"drawn_triangles": 593026,
"source_triangles": 638590,
"verified": true,
"pixel_quality_status": "pass",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
23.39180762797774,
-15.509524798921928,
-731.7810546875
],
"target_actual_m": [
17.28788584589878,
-13.618271096312476,
-731.8810546875
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/after/passage-1-2-0-upper-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-1-2-0-upper-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-1-2-0-upper-mouth-quality.log"
],
"view": "passage-1-2-0-upper-mouth",
"image_sha256": "8314a919e0c4b16390622371be96c89459ddef922c20711db281c55227e29de2"
},
{
"name": "passage-1-2-0-lower-mouth",
"passage": "passage/1-2/0",
"kind": "mouth",
"station": "lower-mouth",
"camera": [
27.129937681588373,
-19.073450175278364,
-751.8771866414096
],
"target": [
17.48940045519192,
-13.174246920336135,
-753.05390625
],
"yaw": -58.53685078395012,
"pitch": -5.943870452561027,
"focus": [
19.166015625,
-14.2001953125,
-751.73390625
],
"center_z": -751.73390625,
"variant": "L125-v225",
"capture": "captures/L129-mouth/before/passage-1-2-0-lower-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/before/passage-1-2-0-lower-mouth.vfmesh",
"drawn_triangles": 544808,
"source_triangles": 576830,
"verified": true,
"pixel_quality_status": "pass",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
27.129937681588373,
-19.073450175278364,
-751.8771866414096
],
"target_actual_m": [
17.48940045519192,
-13.174246920336135,
-753.05390625
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/before/passage-1-2-0-lower-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-1-2-0-lower-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-1-2-0-lower-mouth-quality.log"
],
"view": "passage-1-2-0-lower-mouth",
"image_sha256": "dc1e5b79bb9afa6e1aa66edac7e0d84e1161ece0147b1ea58e4e60b93dca399b"
},
{
"name": "passage-1-2-0-lower-mouth",
"passage": "passage/1-2/0",
"kind": "mouth",
"station": "lower-mouth",
"camera": [
27.129937681588373,
-19.073450175278364,
-751.8771866414096
],
"target": [
17.48940045519192,
-13.174246920336135,
-753.05390625
],
"yaw": -58.53685078395012,
"pitch": -5.943870452561027,
"focus": [
19.166015625,
-14.2001953125,
-751.73390625
],
"center_z": -751.73390625,
"variant": "L129-v226",
"capture": "captures/L129-mouth/after/passage-1-2-0-lower-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/after/passage-1-2-0-lower-mouth.vfmesh",
"drawn_triangles": 544808,
"source_triangles": 576830,
"verified": true,
"pixel_quality_status": "pass",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
27.129937681588373,
-19.073450175278364,
-751.8771866414096
],
"target_actual_m": [
17.48940045519192,
-13.174246920336135,
-753.05390625
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/after/passage-1-2-0-lower-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-1-2-0-lower-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-1-2-0-lower-mouth-quality.log"
],
"view": "passage-1-2-0-lower-mouth",
"image_sha256": "dc1e5b79bb9afa6e1aa66edac7e0d84e1161ece0147b1ea58e4e60b93dca399b"
},
{
"name": "passage-2-3-0-upper-mouth",
"passage": "passage/2-3/0",
"kind": "mouth",
"station": "upper-mouth",
"camera": [
-24.455025526892644,
-5.631774473341657,
-811.975390625
],
"target": [
-18.110266432492157,
-4.81795266462593,
-812.075390625
],
"yaw": 82.6907653180847,
"pitch": -0.8956299428824654,
"focus": [
-20.0625,
-5.068359375,
-810.755390625
],
"center_z": -810.755390625,
"variant": "L125-v225",
"capture": "captures/L129-mouth/before/passage-2-3-0-upper-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/before/passage-2-3-0-upper-mouth.vfmesh",
"drawn_triangles": 59554,
"source_triangles": 71494,
"verified": true,
"pixel_quality_status": "pass",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
-24.455025526892644,
-5.631774473341657,
-811.975390625
],
"target_actual_m": [
-18.110266432492157,
-4.81795266462593,
-812.075390625
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/before/passage-2-3-0-upper-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-2-3-0-upper-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-2-3-0-upper-mouth-quality.log"
],
"view": "passage-2-3-0-upper-mouth",
"image_sha256": "954c7be05d77c4ccf5d3a7a486cc7cd1e32f108120e02daac2bb2cf18ed2a325"
},
{
"name": "passage-2-3-0-upper-mouth",
"passage": "passage/2-3/0",
"kind": "mouth",
"station": "upper-mouth",
"camera": [
-24.455025526892644,
-5.631774473341657,
-811.975390625
],
"target": [
-18.110266432492157,
-4.81795266462593,
-812.075390625
],
"yaw": 82.6907653180847,
"pitch": -0.8956299428824654,
"focus": [
-20.0625,
-5.068359375,
-810.755390625
],
"center_z": -810.755390625,
"variant": "L129-v226",
"capture": "captures/L129-mouth/after/passage-2-3-0-upper-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/after/passage-2-3-0-upper-mouth.vfmesh",
"drawn_triangles": 59554,
"source_triangles": 71494,
"verified": true,
"pixel_quality_status": "pass",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
-24.455025526892644,
-5.631774473341657,
-811.975390625
],
"target_actual_m": [
-18.110266432492157,
-4.81795266462593,
-812.075390625
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/after/passage-2-3-0-upper-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-2-3-0-upper-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-2-3-0-upper-mouth-quality.log"
],
"view": "passage-2-3-0-upper-mouth",
"image_sha256": "954c7be05d77c4ccf5d3a7a486cc7cd1e32f108120e02daac2bb2cf18ed2a325"
},
{
"name": "passage-2-3-0-lower-mouth",
"passage": "passage/2-3/0",
"kind": "mouth",
"station": "lower-mouth",
"camera": [
-28.589162628136368,
-1.22631300593053,
-898.8116843726575
],
"target": [
-18.26741313091866,
-5.87721124217252,
-900.0333984375
],
"yaw": 114.25594846721253,
"pitch": -6.159177031829821,
"focus": [
-20.0625,
-5.068359375,
-898.7133984375
],
"center_z": -898.7133984375,
"variant": "L125-v225",
"capture": "captures/L129-mouth/before/passage-2-3-0-lower-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/before/passage-2-3-0-lower-mouth.vfmesh",
"drawn_triangles": 293832,
"source_triangles": 314658,
"verified": true,
"pixel_quality_status": "low_contrast_geometry_visible",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
-28.589162628136368,
-1.22631300593053,
-898.8116843726575
],
"target_actual_m": [
-18.26741313091866,
-5.87721124217252,
-900.0333984375
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/before/passage-2-3-0-lower-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-2-3-0-lower-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/before/passage-2-3-0-lower-mouth-quality.log"
],
"view": "passage-2-3-0-lower-mouth",
"image_sha256": "295bc7662d9b699212110c61e60c7bd19e99d13ebe94d41201fa93e109bf707e"
},
{
"name": "passage-2-3-0-lower-mouth",
"passage": "passage/2-3/0",
"kind": "mouth",
"station": "lower-mouth",
"camera": [
-28.589162628136368,
-1.22631300593053,
-898.8116843726575
],
"target": [
-18.26741313091866,
-5.87721124217252,
-900.0333984375
],
"yaw": 114.25594846721253,
"pitch": -6.159177031829821,
"focus": [
-20.0625,
-5.068359375,
-898.7133984375
],
"center_z": -898.7133984375,
"variant": "L129-v226",
"capture": "captures/L129-mouth/after/passage-2-3-0-lower-mouth.png",
"scene": "Saved/Linux/l129/captures/mouth-closeups/after/passage-2-3-0-lower-mouth.vfmesh",
"drawn_triangles": 293832,
"source_triangles": 314658,
"verified": true,
"pixel_quality_status": "low_contrast_geometry_visible",
"route_distance_actual_m": null,
"route_station_adjustment_m": 0.0,
"closeup_camera_target_distance_m": null,
"closeup_target_surface_distance_m": null,
"closeup_predicted_target_offset_m": null,
"closeup_wall_normal_alignment": null,
"fallback_kind": null,
"fallback_surface_distance_m": null,
"camera_actual_m": [
-28.589162628136368,
-1.22631300593053,
-898.8116843726575
],
"target_actual_m": [
-18.26741313091866,
-5.87721124217252,
-900.0333984375
],
"logs": [
"Saved/Linux/l129/captures/mouth-closeups/after/passage-2-3-0-lower-mouth-mesh.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-2-3-0-lower-mouth-render.log",
"Saved/Linux/l129/captures/mouth-closeups/after/passage-2-3-0-lower-mouth-quality.log"
],
"view": "passage-2-3-0-lower-mouth",
"image_sha256": "295bc7662d9b699212110c61e60c7bd19e99d13ebe94d41201fa93e109bf707e"
}
]
}
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

@@ -0,0 +1,174 @@
diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
index dc4ca32..aac34ff 100644
--- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
+++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
@@ -494,7 +494,8 @@ namespace VoxelForgeCore
// Version 223 projects the floor onto one continuous arc-length profile and groups
// dense passage samples into coarse tile candidates. Version 224 keeps the same
// continuous floor while relaxing the spline chord tolerance and bounding shell work.
- inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 225;
+ // Version 226 bounds smooth-passage influence in physical metres and narrows its reach.
+ inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 226;
enum class FieldChannel : std::uint32_t
{
diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h
index 517f375..dc258a1 100644
--- a/Standalone/Core/src/ShapeWorldInternal.h
+++ b/Standalone/Core/src/ShapeWorldInternal.h
@@ -72,11 +72,11 @@ static bool VF_IsL98PassagePrepareTraceEnabled()
return bEnabled;
}
-static double VF_L93PassageCarveReachMetres()
+static double VF_L129PassageCarveReachMetres()
{
- // The generated world uses a fixed bounded tube influence. An explicit diagnostic
- // option can still select the legacy point-evaluation path for comparisons.
- return 4.0;
+ // Keep the sweep cutoff and its spatial bounds on one physical reach. Diagnostic
+ // comparisons can still select another bounded reach or the point-evaluation path.
+ return 2.0;
}
enum class EPassageDensityQueryCaller : std::size_t
@@ -306,7 +306,7 @@ public:
profileStats_(profileStats),profileLod_(profileLod),
passageCarveReachMetres_(std::isfinite(passageCarveReachMetres)
&&passageCarveReachMetres>=0.0?passageCarveReachMetres
- :VF_L93PassageCarveReachMetres())
+ :VF_L129PassageCarveReachMetres())
{
mayContainResolvedBoundaryBand_=shape.ResolvedBoundaryBandsIntersectSubvoxelRange(
minimumSubvoxels_[2],maximumSubvoxels_[2]);
@@ -4813,11 +4813,14 @@ private:
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
constexpr double PassageShellMetres=0.75;
constexpr double ReliefMetres=0.21;
- constexpr double SweepReachMetres=4.0;
+ const double SweepReachMetres=VF_L129PassageCarveReachMetres();
constexpr double CornerBlendMetres=0.50;
constexpr std::size_t CandidateFineSamplesPerSpan=5;
+ const double qPerMetre=Q*1000000.0/
+ std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
const double requiredReach=std::max(SweepReachMetres+ReliefMetres
+CornerBlendMetres*0.25,PassageShellMetres+ReliefMetres);
+ const double requiredReachCoreVoxels=requiredReach*qPerMetre/Q;
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
++passageIndex)
{
@@ -4854,7 +4857,7 @@ private:
routeMinimum[axis]=std::min(routeMinimum[axis],localMinimum[axis]);
routeMaximum[axis]=std::max(routeMaximum[axis],localMaximum[axis]);
}
- const double localExtent=requiredReach
+ const double localExtent=requiredReachCoreVoxels
+std::max(prepared.radiusA,prepared.radiusB)/Q+0.01;
FPassageProofBound proof;
for(std::size_t axis=0;axis<3;++axis)
@@ -4872,7 +4875,8 @@ private:
std::ceil((localMaximum[axis]+localExtent)*Q));
}
}
- const double routeExtent=maximumRadiusSubvoxels/Q+requiredReach+0.01;
+ const double routeExtent=maximumRadiusSubvoxels/Q
+ +requiredReachCoreVoxels+0.01;
const double tileExtent=routeExtent;
std::array<std::int64_t,3> featureMinimum{},featureMaximum{};
for(std::size_t axis=0;axis<3;++axis)
@@ -11442,7 +11446,7 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
const FIntVector& origin,std::int32_t step,std::int32_t cells,std::uint32_t seed)
{
constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
- constexpr double InfluenceMetres=4.0;
+ const double InfluenceMetres=VF_L129PassageCarveReachMetres();
constexpr double OpenAirSideWallHeightMetres=1.0;
if(!shape_||step<=0||cells<=0||!std::isfinite(passageCarveReachMetres_)
||std::abs(passageCarveReachMetres_-InfluenceMetres)>1.0e-9)return;
diff --git a/Standalone/Tools/verify/baselines/README.md b/Standalone/Tools/verify/baselines/README.md
index 45fbb9f..fc7a59a 100644
--- a/Standalone/Tools/verify/baselines/README.md
+++ b/Standalone/Tools/verify/baselines/README.md
@@ -4,7 +4,7 @@ These inputs are byte-for-byte copies of the historical files consumed by the
verification gates. The gates now read this tracked directory instead of
`Saved/Linux`, which is generated and is not present in a fresh checkout.
-- `owner-hash/`: L46, L52 and L53 diagnostics plus the pinned v212/v214 owner hash baselines.
+- `owner-hash/`: L46, L52 and L53 diagnostics plus the pinned v212/v214 and L129 v226 owner hash baselines.
- `historical-controls/`: L35, L36 and L38 regression-control logs.
- `fixtures/`: Linux25 owner JSON inputs and L33 style-override fixtures.
- `landmarks/`: L45–L48 landmark mesh and layout controls.
diff --git a/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-v226.baseline b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-v226.baseline
new file mode 100644
index 0000000..9d3b87b
--- /dev/null
+++ b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-v226.baseline
@@ -0,0 +1,12 @@
+[PipelineFullDetailHash] tiles=960 density=0x91931a57a067fd92 mesh=0xa818654be89790ac payload=0x370ff80451105a39 topology=0x06e507557aa70173
+[PipelineWholeViewHash] tiles=2370 density=0x1573b570e1cbbf7e mesh=0xd88bf8bcf69ce1ce payload=0x347541780c9d8d71 topology=0x6b8ff8640be6e9d4
+[PipelineLODHash] lod=0 tiles=512 density=0x2fd4c78685a43cfe mesh=0x50478cff1c1f8ca0 payload=0x54e48e57e0af26de topology=0x860c011cfdb9833a
+[PipelineLODHash] lod=1 tiles=448 density=0x4b8d9a6dcd2942ad mesh=0x17e89b7087bfe711 payload=0x2389c109eb780db6 topology=0x7eccd4cf560d0630
+[PipelineLODHash] lod=2 tiles=320 density=0x600ce5633a8f9e83 mesh=0xcd40f163b4cf3e0e payload=0xbe81dc276737a33a topology=0x942806963fedb92d
+[PipelineLODHash] lod=3 tiles=208 density=0xf778be0581c49ed8 mesh=0x9aacf9e2a23034f8 payload=0xc1d4a985f2db3df5 topology=0xcdfdb412d3784023
+[PipelineLODHash] lod=4 tiles=240 density=0xfb11591772a3d18e mesh=0xebf2baee8764591b payload=0xe491d7ac9c0061ad topology=0xd618ac33b95ce494
+[PipelineLODHash] lod=5 tiles=144 density=0x2f8b7b7fb1e67258 mesh=0x4cd508cbe97344fd payload=0x2b6e96a6050458c9 topology=0x4ff6d0eda8988573
+[PipelineLODHash] lod=6 tiles=96 density=0x0114a34c8b8d6113 mesh=0xb1fec4d01ae1fa44 payload=0x3e50cfd429b8e950 topology=0x08090c96d15c1618
+[PipelineLODHash] lod=7 tiles=96 density=0x3836ed8cfce73418 mesh=0x40bc6589c5fc1aac payload=0x677349bcae16790f topology=0x36e3aa53192e6a6b
+[PipelineLODHash] lod=8 tiles=306 density=0x144f3c1fd722708b mesh=0xef77b28bf7fd9453 payload=0xe3f47b37fcd61489 topology=0x5a3c73e295494e04
+[PipelineExactQueryHash] probes=9 density=0x7b7c50ac24ccadcc unclamped=0x7b7c50ac24ccadcc detail=0xefcba20c0c70c415 collision=0xe604823a249029bf path=direct-Core-queries
diff --git a/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-owner4-surface-world-style1-v226.baseline b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-owner4-surface-world-style1-v226.baseline
new file mode 100644
index 0000000..128c807
--- /dev/null
+++ b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-owner4-surface-world-style1-v226.baseline
@@ -0,0 +1,12 @@
+[PipelineFullDetailHash] tiles=960 density=0xe1aa01ac806a84fb mesh=0x453624e695910639 payload=0x2f5793602ea13144 topology=0x31a43376cb129e55
+[PipelineWholeViewHash] tiles=2578 density=0xb82dc1dcdadda8ff mesh=0xe74e85bf3cb0909b payload=0x40bc3871dd9dccbe topology=0x855e712b549606c3
+[PipelineLODHash] lod=0 tiles=512 density=0x8119a4f12c4661f4 mesh=0xc074e3705293bef2 payload=0x36c965f6b43875ea topology=0x38bed0df168486d0
+[PipelineLODHash] lod=1 tiles=448 density=0x6270dd0856c53432 mesh=0xa803bb427deeba9a payload=0xf4718f1ce85d4f4f topology=0x7b770585aafd8174
+[PipelineLODHash] lod=2 tiles=320 density=0xf774df80811a61c1 mesh=0xb223f0f68d0310f8 payload=0x6c3fd8c4aa100ee9 topology=0x923485084c866600
+[PipelineLODHash] lod=3 tiles=272 density=0x62088ea9f38d2ed0 mesh=0x19bb327ba4ba3287 payload=0xd04c1223d50da0ea topology=0xf3817d1351cc89c5
+[PipelineLODHash] lod=4 tiles=272 density=0xb743eb64967bddcd mesh=0xf6b5bd36b0ebfead payload=0xd1284c1540f50636 topology=0x4a6e197e2a1a9500
+[PipelineLODHash] lod=5 tiles=208 density=0x38706b628a74dd17 mesh=0x445b1c7b51489cbb payload=0x713efc66290768f7 topology=0x56486b919146bb1e
+[PipelineLODHash] lod=6 tiles=144 density=0xd53849b2002a1faa mesh=0xaa210abc8498c415 payload=0xd109aef8639706d1 topology=0x61a4d75fbbc35268
+[PipelineLODHash] lod=7 tiles=96 density=0xb0ea9a140275c9cc mesh=0x3ffd8365c8b0ec29 payload=0x3577afb0a2e4a420 topology=0x97530aafde15b7e0
+[PipelineLODHash] lod=8 tiles=306 density=0x8b3d1816799d67a0 mesh=0x80bf554f17700e1e payload=0xccbe22c3333a9c34 topology=0xcb11b28f6b6bcc50
+[PipelineExactQueryHash] probes=9 density=0xf2df67b38b7802df unclamped=0x48b347986345b557 detail=0xefcba20c0c70c415 collision=0x137e8f6c0b03a624 path=direct-Core-queries
diff --git a/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-surface-world-style1-v226.baseline b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-surface-world-style1-v226.baseline
new file mode 100644
index 0000000..49f85bb
--- /dev/null
+++ b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-surface-world-style1-v226.baseline
@@ -0,0 +1,12 @@
+[PipelineFullDetailHash] tiles=960 density=0xace56c8f205238ad mesh=0xa9c603a1248de6b8 payload=0xea55f1f4fcc6a894 topology=0x5ea7bc78a1b678ae
+[PipelineWholeViewHash] tiles=2370 density=0x637fdf29ccba949b mesh=0xb02cf034b0c9e375 payload=0x74240706eac0c770 topology=0xe607e19fd4684210
+[PipelineLODHash] lod=0 tiles=512 density=0xcd6ab28cfa504aca mesh=0xf03dc80a669fca31 payload=0x397aded1c3d2d297 topology=0xb4a1dc84fabc1fe3
+[PipelineLODHash] lod=1 tiles=448 density=0xce6e713828b2a20a mesh=0x1cf408c25d67d18c payload=0x8a2b5d20571c997e topology=0x51cd985fe8e7d100
+[PipelineLODHash] lod=2 tiles=320 density=0x353ee7ab408a9da1 mesh=0x8d355a8d598d56d5 payload=0x458f26e32d815635 topology=0x2e89b3cba09efcfe
+[PipelineLODHash] lod=3 tiles=208 density=0xfe4f53b3ec010392 mesh=0xbd04d2da98d5b2e0 payload=0x10627f36d82200e8 topology=0xd625f3434ac0a893
+[PipelineLODHash] lod=4 tiles=240 density=0x0be8798407724eaf mesh=0x131c303cbbe51434 payload=0x2a90ecf0a2411ca0 topology=0xe3e054a371152e08
+[PipelineLODHash] lod=5 tiles=144 density=0xf20897ad272103a9 mesh=0x13d00b6253ae1f72 payload=0x0db3880693a962fa topology=0x462bd87a80f15e89
+[PipelineLODHash] lod=6 tiles=96 density=0xc040fcac457d6313 mesh=0x1ed132eeb718fb62 payload=0xcd0214e35577c6e3 topology=0x2c56c69e6fdcfd64
+[PipelineLODHash] lod=7 tiles=96 density=0xc2d419446355fdb8 mesh=0x4ea44452574fc84b payload=0xfd9779bedfc9ae0f topology=0x8948546e770c9b72
+[PipelineLODHash] lod=8 tiles=306 density=0x62db31696f2da831 mesh=0xd373f9e9481532fe payload=0x540a60526e0e3e46 topology=0xc725cb901e6dc335
+[PipelineExactQueryHash] probes=9 density=0xc949c4df2ce6f9d9 unclamped=0xc949c4df2ce6f9d9 detail=0x114c501e2d830d94 collision=0xe6011d3a248d4849 path=direct-Core-queries
diff --git a/Standalone/Tools/verify/verify.py b/Standalone/Tools/verify/verify.py
index 5fc1584..70f6a69 100644
--- a/Standalone/Tools/verify/verify.py
+++ b/Standalone/Tools/verify/verify.py
@@ -122,11 +122,11 @@ RECIPE_RE = re.compile(
def owner_hash_baseline(recipe: str, style: int) -> tuple[Path, str]:
if recipe == "surface-world" and style == 1:
- return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v225.baseline",
- "L125-v225-stamped-passage-sweep")
+ return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v226.baseline",
+ "L129-v226-physical-passage-reach")
if recipe == "floating-islands" and style == 2:
- return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v225.baseline",
- "L125-v225-stamped-passage-sweep")
+ return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v226.baseline",
+ "L129-v226-physical-passage-reach")
raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")