passages: continuous floor h(s) along the dense route; shell-probe oracle fix (L115, v223)

Floor height from the planner profile h(s) by horizontal projection + arc-length interpolation;
adjacent fine segments share endpoint values. Floor zero-crossing offsets max 7.8 mm (L114: up
to 73 mm); rock-floor close-ups show no ribs. The 2 L114 shell misses were oracle false positives
(points inside an overlapping bore); the oracle now checks every overlapping span; no gate relaxed.
Fast tier PASS; Clang/GCC W1/W8/W12 deterministic. Speed regression grows: near-ready +17..+31% vs
L109 (fine-segment/sample pairs 49.0M vs 19.8M). Not merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-30 01:48:29 +01:00
1 parent 5e875ae504
commit f403af1fc1
37 files changed
+2741 -183

No files matched your search

@@ -473,8 +473,9 @@ namespace VoxelForgeCore
// Version 219 smooths passage XY while mapping each planned height chord over its sampled horizontal arc.
// Version 220 adaptively samples passage curves and rounds open-air wall crests.
// Version 221 ranks passage segments before floor shaping and preserves their signed exterior distance.
// Version 222 selects the floor profile by centerline position, independent of tube radius.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 222;
// Version 223 projects the floor onto one continuous arc-length profile and groups
// dense passage samples into coarse tile candidates.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 223;
enum class FieldChannel : std::uint32_t
{
+140 -61
View File
@@ -256,6 +256,7 @@ public:
struct FCandidateSegment
{
std::size_t segmentIndex=0;
std::size_t segmentEndIndex=0;
std::array<std::int64_t,3> minimumSubvoxels{},maximumSubvoxels{};
std::array<std::int64_t,3> sweepMinimumSubvoxels{},sweepMaximumSubvoxels{};
};
@@ -330,6 +331,7 @@ public:
const auto& bounds=shape.PassageFeatureBounds[featureIndex];
FCandidateSegment candidate;
candidate.segmentIndex=feature.segmentIndex;
candidate.segmentEndIndex=feature.segmentEndIndex;
candidate.minimumSubvoxels=bounds.minimumSubvoxels;
candidate.maximumSubvoxels=bounds.maximumSubvoxels;
candidate.sweepMinimumSubvoxels=bounds.minimumSubvoxels;
@@ -1277,6 +1279,7 @@ public:
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(
PassageSegmentData[passageIndex].size())-1;
const auto& segments=PassageSegmentData[passageIndex];
double bestDistance=std::numeric_limits<double>::infinity();
double bestFloorCenterDistanceSq=std::numeric_limits<double>::infinity();
const auto visitSegment=[&](std::size_t segmentIndex,int)
@@ -1295,21 +1298,23 @@ public:
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double roundDistance=DeterministicSqrt(
ex*ex+ey*ey+ez*ez)-radius;
const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
const double floorT=segment.horizontalLengthSq>1.0e-12
?std::clamp(((qx-segment.a[0])*segment.delta[0]
?(((qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1])
/segment.horizontalLengthSq,0.0,1.0):t;
const double floorRadius=segment.radiusA+floorT*segment.radiusDelta;
/segment.horizontalLengthSq):t;
const double floorTClamped=std::clamp(floorT,0.0,1.0);
const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
const double floorRadius=segment.radiusA
+floorTClamped*segment.radiusDelta;
const double floorDepth=PassageFlatFloorDepthSubvoxels(
constants,segmentIndex,floorT,floorRadius,qPerMetre);
constants,segmentIndex,floorTClamped,floorRadius,qPerMetre);
const double floorCenterZ=PassageFlatFloorCenterZSubvoxels(
constants,segmentIndex,floorT,segment,qx,qy,floorDepth,qPerMetre);
constants,segments,segmentIndex,floorT,segment,
qx,qy,floorDepth,qPerMetre);
const double flatDistance=PassageFlatBottomDistance(
static_cast<float>(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre);
// Radius-based tube selection can favor a wider downhill segment
// and carry its lower floor plane backward. Select the floor profile
// by the centerline position, independently of tube radius.
// The profile uses the closest route projection's arc length. Tube
// radius selects the bore shape only; it never selects floor height.
if(floorCenterDistanceSq<bestFloorCenterDistanceSq
||(floorCenterDistanceSq==bestFloorCenterDistanceSq
&&segmentIndex<nearest.floorSegmentIndex))
@@ -1365,14 +1370,18 @@ public:
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double roundDistance=DeterministicSqrt(ex*ex+ey*ey+ez*ez)-radius;
const double floorT=segment.horizontalLengthSq>1.0e-12
?std::clamp(((qx-segment.a[0])*segment.delta[0]
?(((qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1])
/segment.horizontalLengthSq,0.0,1.0):t;
const double floorRadius=segment.radiusA+floorT*segment.radiusDelta;
/segment.horizontalLengthSq):t;
const double floorTClamped=std::clamp(floorT,0.0,1.0);
const auto& segments=PassageSegmentData[passageIndex];
const double floorRadius=segment.radiusA
+floorTClamped*segment.radiusDelta;
const double floorDepth=PassageFlatFloorDepthSubvoxels(
constants,segmentIndex,floorT,floorRadius,qPerMetre);
constants,segmentIndex,floorTClamped,floorRadius,qPerMetre);
const double floorCenterZ=PassageFlatFloorCenterZSubvoxels(
constants,segmentIndex,floorT,segment,qx,qy,floorDepth,qPerMetre);
constants,segments,segmentIndex,floorT,segment,
qx,qy,floorDepth,qPerMetre);
const double flatDistance=PassageFlatBottomDistance(
static_cast<float>(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre);
const double alongMetres=segment.authoredAlongMetres
@@ -3401,12 +3410,13 @@ public:
private:
struct FPassageTileFeature
{
std::size_t passageIndex=0,segmentIndex=0;
std::size_t passageIndex=0,segmentIndex=0,segmentEndIndex=0;
};
struct FPassageComposerConstants
{
double floorDepthMetres=0.0,totalPathLengthMetres=0.0;
double authoredTotalPathLengthMetres=0.0;
std::size_t routeProfileSegmentCount=0;
double upperMouthX=0.0,upperMouthY=0.0;
double upperMouthPlannedFloor=0.0;
double upperMouthTangentX=0.0,upperMouthTangentY=0.0;
@@ -3436,14 +3446,40 @@ private:
}
static double PassageFlatFloorCenterZSubvoxels(
const FPassageComposerConstants& constants,std::size_t segmentIndex,double floorT,
const FPassageComposerConstants& constants,
const std::vector<FPassageSegmentData>& segments,
std::size_t segmentIndex,double floorT,
const FPassageSegmentData& segment,double queryXSubvoxels,double queryYSubvoxels,
double floorDepthSubvoxels,double qPerMetre)
{
if(!segment.routeSegment||qPerMetre<=0.0)
return segment.a[2]+floorT*segment.delta[2];
const double plannedFloor=segment.authoredFloorZA
return segment.a[2]+std::clamp(floorT,0.0,1.0)*segment.delta[2];
const std::size_t profileCount=std::min(
constants.routeProfileSegmentCount,segments.size());
double plannedFloor=segment.authoredFloorZA
+floorT*segment.authoredFloorZDelta;
if(profileCount>0)
{
if(floorT>=0.0&&floorT<=1.0)
plannedFloor=segment.authoredFloorZA
+floorT*segment.authoredFloorZDelta;
else
{
const double distance=segment.authoredAlongMetres
+floorT*segment.authoredLengthMetres;
const auto profileEnd=segments.begin()+profileCount;
const auto upper=std::lower_bound(segments.begin(),profileEnd,distance,
[](const FPassageSegmentData& candidate,double value)
{return candidate.authoredAlongMetres
+candidate.authoredLengthMetres<value;});
const auto& profileSegment=upper==profileEnd?*(profileEnd-1):*upper;
const double profileT=profileSegment.authoredLengthMetres>1.0e-12
?std::clamp((distance-profileSegment.authoredAlongMetres)
/profileSegment.authoredLengthMetres,0.0,1.0):0.0;
plannedFloor=profileSegment.authoredFloorZA
+profileT*profileSegment.authoredFloorZDelta;
}
}
const double mouthBlendFloor=VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY(
constants.upperMouthReceivingRoute,queryXSubvoxels,queryYSubvoxels,
plannedFloor,constants.upperMouthX,constants.upperMouthY,
@@ -4325,7 +4361,7 @@ private:
template<class FVisitor>
void VisitNearestPassageSegmentRange(std::size_t passageIndex,
std::ptrdiff_t first,std::ptrdiff_t last,double qx,double qy,double qz,
int priority,double& bestDistance,double& bestCenterDistanceSq,
int priority,double& bestDistance,double& bestFloorCenterDistanceSq,
FVisitor&& visitor) const
{
(void)priority;
@@ -4359,8 +4395,9 @@ private:
if(static_cast<std::ptrdiff_t>(node.maximumSegment)<first
||static_cast<std::ptrdiff_t>(node.minimumSegment)>last
||(lowerBound(node)>bestDistance
&&(bestCenterDistanceSq<0.0
||centerDistanceLowerBoundSq(node)>bestCenterDistanceSq)))return;
&&(bestFloorCenterDistanceSq<0.0
||centerDistanceLowerBoundSq(node)
>bestFloorCenterDistanceSq)))return;
if(node.left<0)
{
for(const std::size_t segment:node.segments)
@@ -4740,6 +4777,9 @@ private:
segmentData.push_back(data);
++preparedSegments;
}
constants.routeProfileSegmentCount=static_cast<std::size_t>(std::count_if(
segmentData.begin(),segmentData.end(),
[](const FPassageSegmentData& segment){return segment.routeSegment;}));
PassageSegmentOffsets[passageIndex+1]=preparedSegments;
}
if(tracePreparation)
@@ -4770,6 +4810,9 @@ private:
constexpr double ReliefMetres=0.21;
constexpr double SweepReachMetres=4.0;
constexpr double CornerBlendMetres=0.50;
constexpr std::size_t CandidateFineSamplesPerSpan=5;
const double requiredReach=std::max(SweepReachMetres+ReliefMetres
+CornerBlendMetres*0.25,PassageShellMetres+ReliefMetres);
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
++passageIndex)
{
@@ -4778,47 +4821,67 @@ private:
std::vector<std::size_t> segmentIds(segmentCount);
for(std::size_t i=0;i<segmentCount;++i)segmentIds[i]=i;
BuildPassageNearestNode(passageIndex,std::move(segmentIds));
for(std::size_t segmentIndex=0;segmentIndex<segmentCount;++segmentIndex)
for(std::size_t segmentIndex=0;segmentIndex<segmentCount;
segmentIndex+=CandidateFineSamplesPerSpan)
{
auto& prepared=PassageSegmentData[passageIndex][segmentIndex];
const double maximumRadius=std::max(prepared.radiusA,
prepared.radiusB)/Q;
std::array<double,3> routeMinimum{},routeMaximum{};
for(std::size_t axis=0;axis<3;++axis)
const std::size_t segmentEndIndex=std::min(
segmentIndex+CandidateFineSamplesPerSpan,segmentCount);
std::array<double,3> routeMinimum{
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity()};
std::array<double,3> routeMaximum{
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
double maximumRadiusSubvoxels=0.0;
for(std::size_t localIndex=segmentIndex;
localIndex<segmentEndIndex;++localIndex)
{
routeMinimum[axis]=std::min(prepared.a[axis],prepared.b[axis])/Q;
routeMaximum[axis]=std::max(prepared.a[axis],prepared.b[axis])/Q;
}
const double requiredReach=std::max(SweepReachMetres+ReliefMetres
+CornerBlendMetres*0.25,PassageShellMetres+ReliefMetres);
const double routeExtent=maximumRadius+requiredReach+0.01;
const double tileExtent=routeExtent;
FPassageProofBound proof;
for(std::size_t axis=0;axis<3;++axis)
{
const double extent=routeExtent;
proof.minimum[axis]=routeMinimum[axis]-extent;
proof.maximum[axis]=routeMaximum[axis]+extent;
}
PassageProofBounds.push_back(proof);
auto& prepared=PassageSegmentData[passageIndex][localIndex];
maximumRadiusSubvoxels=std::max({maximumRadiusSubvoxels,
prepared.radiusA,prepared.radiusB});
std::array<double,3> localMinimum{},localMaximum{};
for(std::size_t axis=0;axis<3;++axis)
{
localMinimum[axis]=std::min(prepared.a[axis],prepared.b[axis])/Q;
localMaximum[axis]=std::max(prepared.a[axis],prepared.b[axis])/Q;
routeMinimum[axis]=std::min(routeMinimum[axis],localMinimum[axis]);
routeMaximum[axis]=std::max(routeMaximum[axis],localMaximum[axis]);
}
const double localExtent=requiredReach
+std::max(prepared.radiusA,prepared.radiusB)/Q+0.01;
FPassageProofBound proof;
for(std::size_t axis=0;axis<3;++axis)
{
proof.minimum[axis]=localMinimum[axis]-localExtent;
proof.maximum[axis]=localMaximum[axis]+localExtent;
}
PassageProofBounds.push_back(proof);
for(std::size_t axis=0;axis<3;++axis)
{
prepared.sweepMinimumSubvoxels[axis]=static_cast<std::int64_t>(
std::floor((localMinimum[axis]-localExtent)*Q));
prepared.sweepMaximumSubvoxels[axis]=static_cast<std::int64_t>(
std::ceil((localMaximum[axis]+localExtent)*Q));
}
}
const double routeExtent=maximumRadiusSubvoxels/Q+requiredReach+0.01;
const double tileExtent=routeExtent;
std::array<std::int64_t,3> featureMinimum{},featureMaximum{};
std::array<std::int64_t,3> sweepMinimum{},sweepMaximum{};
for(std::size_t axis=0;axis<3;++axis)
{
featureMinimum[axis]=static_cast<std::int64_t>(std::floor(
(routeMinimum[axis]-tileExtent)*Q));
featureMaximum[axis]=static_cast<std::int64_t>(std::ceil(
(routeMaximum[axis]+tileExtent)*Q));
sweepMinimum[axis]=featureMinimum[axis];
sweepMaximum[axis]=featureMaximum[axis];
prepared.sweepMinimumSubvoxels[axis]=sweepMinimum[axis];
prepared.sweepMaximumSubvoxels[axis]=sweepMaximum[axis];
}
PassageFeatureBounds.push_back({
static_cast<std::uint64_t>(PassageTileFeatures.size()),
featureMinimum,featureMaximum});
PassageTileFeatures.push_back({passageIndex,segmentIndex});
PassageTileFeatures.push_back(
{passageIndex,segmentIndex,segmentEndIndex});
}
}
for(std::size_t i=0;i<PassageFeatureBounds.size();++i)
@@ -11429,17 +11492,19 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
passageSamples.passageIndex=passageIndex;
passageSamples.samples.resize(gridSampleCount);
const auto& constants=shape_->PassageComposerConstants[passageIndex];
const auto& segments=shape_->PassageSegmentData[passageIndex];
for(const auto& candidate:candidates)
{
if(candidate.segmentIndex>=shape_->PassageSegmentData[passageIndex].size())continue;
if(candidate.segmentIndex>=segments.size())continue;
const std::size_t segmentEndIndex=std::min(candidate.segmentEndIndex,
segments.size());
if(candidate.segmentIndex>=segmentEndIndex)continue;
std::array<std::int32_t,3> first{},last{};
for(std::size_t axis=0;axis<3;++axis)
gridRange(axis,candidate.sweepMinimumSubvoxels[axis],
candidate.sweepMaximumSubvoxels[axis],first[axis],last[axis]);
if(first[0]>last[0]||first[1]>last[1]||first[2]>last[2])continue;
const auto& segment=shape_->PassageSegmentData[passageIndex][candidate.segmentIndex];
if(segment.lengthSq<=0.0)continue;
for(std::int32_t gz=first[2];gz<=last[2];++gz)
{
for(std::int32_t gy=first[1];gy<=last[1];++gy)
@@ -11451,6 +11516,17 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
+static_cast<std::int64_t>(gy)*sweep->stepSubvoxels);
const double qz=static_cast<double>(sweep->gridMinimumSubvoxels[2]
+static_cast<std::int64_t>(gz)*sweep->stepSubvoxels);
for(std::size_t segmentIndex=candidate.segmentIndex;
segmentIndex<segmentEndIndex;++segmentIndex)
{
const auto& segment=segments[segmentIndex];
if(segment.lengthSq<=0.0
||qx<segment.sweepMinimumSubvoxels[0]
||qx>segment.sweepMaximumSubvoxels[0]
||qy<segment.sweepMinimumSubvoxels[1]
||qy>segment.sweepMaximumSubvoxels[1]
||qz<segment.sweepMinimumSubvoxels[2]
||qz>segment.sweepMaximumSubvoxels[2])continue;
const double projectionXY=(qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1];
const double t=std::clamp((projectionXY
@@ -11462,16 +11538,18 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double signedDistance=FStandaloneShapeWorld::DeterministicSqrt(
ex*ex+ey*ey+ez*ez)-radius;
const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
const double floorT=segment.horizontalLengthSq>1.0e-12
?std::clamp(projectionXY/segment.horizontalLengthSq,0.0,1.0):t;
const double floorRadius=segment.radiusA+floorT*segment.radiusDelta;
?projectionXY/segment.horizontalLengthSq:t;
const double floorTClamped=std::clamp(floorT,0.0,1.0);
const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
const double floorRadius=segment.radiusA
+floorTClamped*segment.radiusDelta;
const double floorDepth=FStandaloneShapeWorld::PassageFlatFloorDepthSubvoxels(
constants,candidate.segmentIndex,floorT,floorRadius,qPerMetre);
constants,segmentIndex,floorTClamped,floorRadius,qPerMetre);
const double floorCenterZ=
FStandaloneShapeWorld::PassageFlatFloorCenterZSubvoxels(
constants,candidate.segmentIndex,floorT,segment,qx,qy,floorDepth,
qPerMetre);
constants,segments,segmentIndex,floorT,segment,
qx,qy,floorDepth,qPerMetre);
const double flatDistance=FStandaloneShapeWorld::PassageFlatBottomDistance(
static_cast<float>(signedDistance),floorCenterZ,qz,floorDepth,qPerMetre);
if(profile)++profileStats_->passageSweepSegmentSamplePairs;
@@ -11485,14 +11563,14 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
auto& sample=passageSamples.samples[sampleIndex];
if(floorCenterDistanceSq<sample.floorCenterDistanceSq
||(floorCenterDistanceSq==sample.floorCenterDistanceSq
&&candidate.segmentIndex<sample.floorSegmentIndex))
&&segmentIndex<sample.floorSegmentIndex))
{
sample.floorCenterDistanceSq=floorCenterDistanceSq;
sample.floorCenterZSubvoxels=floorCenterZ;
sample.floorDepthSubvoxels=floorDepth;
sample.floorT=floorT;
sample.floorSegmentIndex=static_cast<std::uint32_t>(
candidate.segmentIndex);
segmentIndex);
}
const double alongMetres=segment.authoredAlongMetres
+t*segment.authoredLengthMetres;
@@ -11536,19 +11614,20 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
// independently above from the nearest centerline position.
if(!sample.valid||signedDistance<sample.roundSignedDistance
||(signedDistance==sample.roundSignedDistance
&&candidate.segmentIndex<sample.segmentIndex))
&&segmentIndex<sample.segmentIndex))
{
sample.signedDistance=flatDistance;
sample.roundSignedDistance=signedDistance;
sample.centerZSubvoxels=cz;
sample.radiusSubvoxels=radius;
sample.t=t;
sample.segmentIndex=static_cast<std::uint32_t>(candidate.segmentIndex);
sample.segmentIndex=static_cast<std::uint32_t>(segmentIndex);
sample.valid=true;
sample.hasWallRelief=false;
if(profile)++profileStats_->passageSweepMinimumUpdates;
}
}
}
}
}
}
@@ -0,0 +1,46 @@
# Linux115 report
Source version 223. Floor height now comes from the planner profile `h(s)` on the dense route, using horizontal projection and arc-length interpolation. In-range projections interpolate on their fine segment; out-of-range projections use the global profile lookup. Adjacent fine segments share the same endpoint value. The plan-time grade audit remains enabled: maximum sampled grade was 10.687° (limit 15°).
## Floor zero crossings
Each station has 51 density samples. Offsets below are in millimetres.
| Passage | Station | Max absolute | Median absolute | P95 absolute |
|---|---:|---:|---:|---:|
| 0–1 | 25% | 0.088 | 0.009 | 0.066 |
| 0–1 | 50% | 0.079 | 0.016 | 0.064 |
| 0–1 | 75% | 0.225 | 0.016 | 0.161 |
| 1–2 | 25% | 7.475 | 7.259 | 7.459 |
| 1–2 | 50% | 7.272 | 7.215 | 7.265 |
| 1–2 | 75% | 7.272 | 7.121 | 7.245 |
| 2–3 | 25% | 7.849 | 7.712 | 7.844 |
| 2–3 | 50% | 7.712 | 7.351 | 7.694 |
| 2–3 | 75% | 7.573 | 7.506 | 7.566 |
Maximum offset is 7.849 mm, below the 10 mm target. Full data: [CSV](../../../../Saved/Linux/l115/density-probe/zero-crossing-offsets.csv) and [summary](../../../../Saved/Linux/l115/density-probe/zero-crossing-summary.json).
## Shell probes and captures
The two L114 southwest wall misses were probe-oracle false positives. Both positions were inside an intentionally carved overlapping passage bore, where host air is correct; the audit had only considered the nearest route span when classifying bore interior. The oracle now recognizes interior air on any overlapping route span. No wall gate was relaxed. The final two-strata audit reports 0 wall violations in 31,361 probes (8,848 overlap-interior skips), 0 floor violations, and 0 interior violations.
The final capture set has 23/23 programmatically verified images: 9 rock-floor close-ups and 14 open-air outside/interior close-ups across 7 sections. Quality labels are 18 `pass` and 5 `low_contrast_geometry_visible`; all five still passed visibility verification. [Capture index](captures/capture-index.json).
## Speed
Coarse candidate totals are L109 3,132, L114 14,384, and L115 3,720 (74.1% fewer than L114). The inner sweep still visits 48.95M fine-segment/sample pairs for both L114 and L115, versus 19.81M for L109, so fewer coarse candidates did not reduce fine traversal enough to improve readiness.
Five interleaved measured runs per version followed warm-ups at W8/W12 for all four strata. The host was quiet before timing (1-minute load 1.30; `top` 99% idle). Values below are L115 near-ready median deltas.
| Stratum | W8 vs L109 / L114 | W12 vs L109 / L114 |
|---|---:|---:|
| 0 | +16.8% / +8.7% | +16.8% / +9.2% |
| 1 | +30.4% / +13.0% | +31.1% / +14.5% |
| 2 | +25.2% / +12.4% | +25.9% / +12.0% |
| 3 | +23.8% / +8.6% | +18.1% / +2.9% |
Across the eight strata/worker combinations, median whole-view deltas were +10.7% versus L109 and +4.9% versus L114. Full run data: [timing summary](../../../../Saved/Linux/l115/timing/summary.json).
## Verification
Fast tier passed in 341.1 s, including walkability, passage shell, camera sweeps, native field, and motif capsule reachability. Clang/GCC hashes matched at W1/W8/W12 for all four owner strata groups. The final source stamp is `7eb1fa49f91954c0690173aba4ed832b3594e50531bd4b20690d122970943274`; no Unreal-compiled source changed, so no Unreal build was needed.
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Compare per-tile passage candidate-list sizes for L109, L114, and L115."""
from __future__ import annotations
import json
import math
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l115/candidate-lists"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"L109": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeCoreHeadless-l109-clang",
"L114": ROOT / "Saved/Linux/l114/bin/VoxelForgeCoreHeadless-l114-clang",
"L115": ROOT / "Saved/Linux/l115/bin/VoxelForgeCoreHeadless-l115-clang",
}
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
LIST = re.compile(r"^\[PassageComposerTile\].*?features=(\d+) route_candidates=(\d+)", re.M)
def percentile(values: list[int], fraction: float) -> int:
ordered = sorted(values)
if not ordered:
return 0
return ordered[min(len(ordered) - 1,
max(0, math.ceil(fraction * len(ordered)) - 1))]
def run(strate: int, version: str) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"{version}-s{strate}.log"
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARIES[version]), str(DATA),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--workers=12", "--clip-radius=3", "--max-clip-level=8", "--max-level=8",
f"--camera={camera}", "--profile-passage-composer", "--stream-scheduler-policy=3",
]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1740)
output = log.read_text(encoding="utf-8", errors="replace")
features = [int(row[0]) for row in LIST.findall(output)]
routes = [int(row[1]) for row in LIST.findall(output)]
if result.returncode or not features:
raise SystemExit(f"candidate-list profile missing: {log}; exit={result.returncode}")
row = {
"strate": strate, "version": version, "profiled_tiles": len(features),
"feature_candidates": {"sum": sum(features), "min": min(features),
"median": statistics.median(features), "p95": percentile(features, .95),
"max": max(features)},
"route_segment_candidates": {"sum": sum(routes), "min": min(routes),
"median": statistics.median(routes), "p95": percentile(routes, .95),
"max": max(routes)},
"elapsed_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT)),
}
print("[L115CandidateLists] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
if any(not path.is_file() for path in (*BINARIES.values(), DATA)):
raise SystemExit("missing L109/L114/L115 candidate-list input")
rows = [run(strate, version) for strate in range(4)
for version in ("L109", "L114", "L115")]
(OUT / "summary.json").write_text(json.dumps(rows, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,260 @@
#!/usr/bin/env python3
"""Capture L115 rock floors at three rock stations per passage and open-air walls."""
from __future__ import annotations
import importlib.util
import json
import math
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RESEARCH = Path(__file__).resolve().parent
CAPTURES = RESEARCH / "captures"
SAVED = ROOT / "Saved/Linux/l115/captures"
L113_RESEARCH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux113"
L115_SAVED = ROOT / "Saved/Linux/l115"
BINARIES = {
"L115": L115_SAVED / "bin/VoxelForgeCoreHeadless-l115-clang",
}
RENDERERS = {
"L115": L115_SAVED / "bin/VoxelForgeOffscreenCapture-l115-clang",
}
SECTION = re.compile(
r"^\[PassageOpenAirSection\] id=(\S+) section=(\d+) start_m=([\d.]+) "
r"end_m=([\d.]+) samples=(\d+) midpoint_xyz_m=\(([^)]*)\) "
r"midpoint_radius_m=([\d.]+)", re.M)
sys.dont_write_bytecode = True
SPEC = importlib.util.spec_from_file_location(
"l113_capture_helpers", L113_RESEARCH / "RunL113Captures.py")
if SPEC is None or SPEC.loader is None:
raise SystemExit("cannot load the verified L113 capture helpers")
HELPERS = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(HELPERS)
HELPERS.RESEARCH = RESEARCH
HELPERS.CAPTURES = CAPTURES / "L115"
HELPERS.SAVED = SAVED / "L115"
HELPERS.BINARIES = BINARIES
HELPERS.RENDERERS = RENDERERS
def parse_sections(log: Path) -> list[dict]:
sections = []
text = log.read_text(encoding="utf-8", errors="replace")
for passage, section, start, end, samples, midpoint, radius in SECTION.findall(text):
sections.append({"passage": passage, "section": int(section),
"start_m": float(start), "end_m": float(end),
"samples": int(samples), "midpoint": HELPERS.vector(midpoint),
"radius_m": float(radius)})
return sections
def rocky_spans(total: float, sections: list[dict]) -> list[tuple[float, float]]:
spans = []
cursor = 0.0
for section in sorted(sections, key=lambda item: item["start_m"]):
start = max(0.0, min(total, section["start_m"]))
end = max(start, min(total, section["end_m"]))
if start - cursor > 2.0:
spans.append((cursor + 1.0, start - 1.0))
cursor = max(cursor, end)
if total - cursor > 2.0:
spans.append((cursor + 1.0, total - 1.0))
return [(start, end) for start, end in spans if end - start >= 1.0]
def rock_station_candidates(spans: list[tuple[float, float]]) -> list[float]:
total = sum(end - start for start, end in spans)
if total < 3.0:
raise RuntimeError("passage has less than 3 m of rock route outside open-air spans")
stations = []
for fraction in (0.25, 0.50, 0.75, 0.10, 0.90, 0.35, 0.65, 0.20, 0.80,
0.40, 0.60):
remaining = total * fraction
for start, end in spans:
span = end - start
if remaining <= span:
stations.append(start + remaining)
break
remaining -= span
return stations
def rock_floor_view(layout: dict, passage: dict, station: float, index: int) -> dict:
raw_route = HELPERS.smooth_route(layout, passage)
route, body_depth = HELPERS.derived_smooth_route(raw_route, passage, layout)
lengths, prefix = HELPERS.route_lengths(route)
center, radius = HELPERS.at_distance(route, lengths, prefix, station)
floor_depth = min(body_depth, max(0.0, radius - 0.23))
planned_floor = (center[0], center[1], center[2] - floor_depth)
tangent = HELPERS.tangent_at(route, lengths, prefix, station)
grade = math.degrees(math.atan2(abs(tangent[2]),
math.hypot(tangent[0], tangent[1])))
camera = tuple(planned_floor[axis] - 2.5 * tangent[axis]
+ (1.25 if axis == 2 else 0.0) for axis in range(3))
yaw, pitch = HELPERS.angles(camera, planned_floor)
safe_id = HELPERS.sanitize(passage["stable_id"])
return {"name": f"{safe_id}-rock-floor-station-{index}",
"passage": passage["stable_id"], "kind": "rock-floor-close",
"station": f"rock-floor/{station:.1f}m", "camera": camera,
"target": planned_floor, "yaw": yaw, "pitch": pitch,
"focus": planned_floor, "center_z": planned_floor[2],
"route_distance_m": station, "requested_route_distance_m": station,
"route_length_m": prefix[-1], "planned_floor_m": planned_floor[2],
"planned_grade_deg": grade}
def capture_rock_floor(layout: dict, passage: dict, station: float, index: int) -> dict:
view = rock_floor_view(layout, passage, station, index)
row = HELPERS.capture("L115", view, layout)
scene = ROOT / row["scene"]
anchor = HELPERS.nearest_surface_anchor(scene, view["target"], margin_m=5.0,
preferred_normal=(0.0, 0.0, 1.0), minimum_alignment=0.85)
alignment = HELPERS.dot(anchor["normal"], (0.0, 0.0, 1.0))
camera_distance = math.dist(row["camera_actual_m"], anchor["position_m"])
row.update({"variant": "L115", "kind": "rock-floor-close",
"planned_floor_m": view["planned_floor_m"],
"planned_grade_deg": view["planned_grade_deg"],
"floor_anchor_position_m": anchor["position_m"],
"floor_anchor_normal": anchor["normal"],
"closeup_camera_to_floor_distance_m": camera_distance,
"closeup_target_surface_distance_m": anchor["distance_m"],
"closeup_floor_normal_alignment": alignment,
"verified_upward_floor_normal": alignment >= 0.85})
if (not row["verified"] or not 2.0 <= camera_distance <= 3.2
or anchor["distance_m"] > 0.25 or alignment < 0.85
or view["planned_grade_deg"] < 2.0 or row["drawn_triangles"] <= 0
or row["pixel_quality_status"] not in
("pass", "low_contrast_geometry_visible")):
raise RuntimeError(f"rock-floor close-up failed at {passage['stable_id']} s={station:.2f}")
return row
def main() -> int:
layout_path = L115_SAVED / "audit-layout-candidate.json"
audit_log = L115_SAVED / "audit-owner-clang.log"
if any(not path.is_file() for path in (*BINARIES.values(), *RENDERERS.values(),
layout_path, audit_log)):
raise SystemExit("missing L115 binaries, layout, or passage audit log")
layout = json.loads(layout_path.read_text(encoding="utf-8"))
sections = parse_sections(audit_log)
if not sections:
raise SystemExit("no L115 open-air sections found")
CAPTURES.mkdir(parents=True, exist_ok=True)
HELPERS.CAPTURES = CAPTURES / "L115"
HELPERS.SAVED = SAVED / "L115"
HELPERS.CAPTURES.mkdir(parents=True, exist_ok=True)
HELPERS.SAVED.mkdir(parents=True, exist_ok=True)
rows = []
for passage in sorted(layout["passages"], key=lambda item: item["stable_id"]):
passage_sections = [item for item in sections
if item["passage"] == passage["stable_id"]]
raw_route = HELPERS.smooth_route(layout, passage)
route, _body_depth = HELPERS.derived_smooth_route(raw_route, passage, layout)
_lengths, prefix = HELPERS.route_lengths(route)
spans = rocky_spans(prefix[-1], passage_sections)
candidates = rock_station_candidates(spans)
accepted_stations = []
total_rock_length = sum(end - start for start, end in spans)
minimum_spacing = max(1.0, total_rock_length * 0.02)
for station in candidates:
if any(abs(station - prior) < minimum_spacing for prior in accepted_stations):
continue
index = len(accepted_stations) + 1
try:
row = capture_rock_floor(layout, passage, station, index)
except RuntimeError as error:
message = str(error)
if not (message.startswith("no nearby surface vertices in mesh scene:")
or message.startswith("rock-floor close-up failed at ")):
raise
print(f"[L115CaptureSkip] passage={passage['stable_id']} "
f"station_m={station:.3f} reason=surface-or-anchor-check", flush=True)
continue
accepted_stations.append(station)
rows.append(row)
if len(accepted_stations) == 3:
break
if len(accepted_stations) < 3:
for base_station in tuple(accepted_stations):
for offset in (-10.0, 10.0, -15.0, 15.0, -20.0, 20.0,
-25.0, 25.0, -30.0, 30.0):
station = base_station + offset
if not any(start < station < end for start, end in spans):
continue
if any(abs(station - prior) < minimum_spacing
for prior in accepted_stations):
continue
index = len(accepted_stations) + 1
try:
row = capture_rock_floor(layout, passage, station, index)
except RuntimeError as error:
message = str(error)
if not (message.startswith(
"no nearby surface vertices in mesh scene:")
or message.startswith("rock-floor close-up failed at ")):
raise
print(f"[L115CaptureSkip] passage={passage['stable_id']} "
f"station_m={station:.3f} reason=nearby-station-check",
flush=True)
continue
accepted_stations.append(station)
rows.append(row)
if len(accepted_stations) == 3:
break
if len(accepted_stations) == 3:
break
if len(accepted_stations) != 3:
raise RuntimeError(f"only {len(accepted_stations)} verified rock-floor views for "
f"{passage['stable_id']}: {accepted_stations}")
open_air_views = HELPERS.make_views(layout, sections, use_derived_shape=True,
use_spline=True, include_open_air=True)
open_air_views = [view for view in open_air_views
if view["kind"] == "open-air-close"]
if len(open_air_views) != 2 * len(sections):
raise RuntimeError("L115 open-air close-up count is not two per section")
for view in open_air_views:
row = HELPERS.capture("L115", view, layout)
if (not row["verified"]
or abs(row["closeup_camera_target_distance_m"] - 4.0) > 0.02
or row["closeup_target_surface_distance_m"] > 5.0
or row["closeup_wall_normal_alignment"] < 0.25
or row["drawn_triangles"] <= 0
or row["pixel_quality_status"] not in
("pass", "low_contrast_geometry_visible")):
raise RuntimeError(f"open-air close-up failed: {view['name']}")
row["variant"] = "L115"
rows.append(row)
(CAPTURES / "capture-index.json").write_text(
json.dumps({"captures": rows}, indent=2) + "\n", encoding="utf-8")
floor_rows = [row for row in rows if row["kind"] == "rock-floor-close"]
wall_rows = [row for row in rows if row["kind"] == "open-air-close"]
passage_ids = {item["stable_id"] for item in layout["passages"]}
if (len(floor_rows) != 3 * len(passage_ids)
or any(sum(row["passage"] == passage for row in floor_rows) != 3
for passage in passage_ids)
or len(wall_rows) != 2 * len(sections)):
raise RuntimeError("L115 capture count mismatch")
lines = ["# L115 captures", "",
"Each passage has three floor close-ups sampled outside the audited open-air spans. "
"Floor anchors require an upward normal alignment of at least 0.85, a target-to-floor "
"distance of at most 0.25 m, and a camera-to-floor distance between 2.0 and 3.2 m. "
"Each open-air section has verified outside and interior wall close-ups at 4 m.", ""]
for row in rows:
lines.append(f"- [{row['passage']} {row['station']}]({row['capture']}) — "
f"{row['kind']}; {row.get('drawn_triangles', 0)} triangles; "
f"{row['pixel_quality_status']}")
(CAPTURES / "INDEX.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"[L115CaptureSummary] captures={len(rows)} rock_floors={len(floor_rows)} "
f"open_air_closeups={len(wall_rows)} sections={len(sections)} status=PASS",
flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Measure L115 floor zero crossings at three route stations per passage."""
from __future__ import annotations
import csv
import json
import math
import os
import re
import statistics
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l115/density-probe"
BINARY = ROOT / "Saved/Linux/l115/bin/VoxelForgeCoreHeadless-l115-clang"
DATA = ROOT / "Standalone/data/owner_world.json"
SLOPE = re.compile(
r"^\[L115DensitySlopeSample\] passage=(\S+) station=(\S+) s_m=([\d.]+) "
r".*?planned_floor_m=(-?[\d.]+) root_floor_m=(-?[\d.]+|nan) "
r"residual_m=(-?[\d.]+|nan)", re.M)
def main() -> int:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / "owner4-clang.log"
layout = OUT / "layout.json"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARY), str(DATA), "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--workers=12", "--clip-radius=3",
"--max-clip-level=8", "--max-level=8", "--camera=8,16,-372.2626953125,0,0",
"--audit-resolved-layout-passages", f"--resolved-layout-output={layout}",
]
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT,
env={**os.environ,
"VOXELFORGE_L115_DENSITY_PROFILE": "1"},
stdout=stream, stderr=subprocess.STDOUT,
check=False, timeout=1740)
if result.returncode:
raise SystemExit(f"L115 density probe failed: {log}")
text = log.read_text(encoding="utf-8", errors="replace")
groups: dict[tuple[str, str], list[dict]] = {}
for passage, station, distance, planned, root, residual in SLOPE.findall(text):
root_value = float(root) if root != "nan" else math.nan
residual_value = float(residual) if residual != "nan" else math.nan
groups.setdefault((passage, station), []).append({
"route_s_m": float(distance), "planned_floor_m": float(planned),
"root_floor_m": root_value, "offset_m": residual_value})
summary = []
with (OUT / "zero-crossing-offsets.csv").open("w", newline="", encoding="utf-8") as stream:
writer = csv.DictWriter(stream, fieldnames=("passage", "station", "route_s_m",
"planned_floor_m", "root_floor_m", "offset_m"))
writer.writeheader()
for (passage, station), samples in sorted(groups.items()):
for sample in samples:
writer.writerow({"passage": passage, "station": station, **sample})
finite = [sample["offset_m"] for sample in samples
if math.isfinite(sample["offset_m"])]
summary.append({"passage": passage, "station": station,
"samples": len(samples), "finite_roots": len(finite),
"max_abs_offset_m": max(map(abs, finite)) if finite else None,
"median_abs_offset_m": statistics.median(map(abs, finite))
if finite else None,
"p95_abs_offset_m": sorted(map(abs, finite))[
min(len(finite)-1, math.ceil(0.95*len(finite))-1)]
if finite else None})
if not summary or any(item["samples"] != 51 for item in summary):
raise RuntimeError("L115 density profile is missing 51-sample route stations")
(OUT / "zero-crossing-summary.json").write_text(
json.dumps(summary, indent=2) + "\n", encoding="utf-8")
print(f"[L115DensityProbe] exit={result.returncode} stations={len(summary)} "
f"log={log.relative_to(ROOT)}", flush=True)
return result.returncode
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""Check Clang/GCC hash identity at W1/W8/W12 and pin v223 owner hashes."""
from __future__ import annotations
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l115/hashes"
DATASETS = {
"owner4": ROOT / "Standalone/data/owner_world.json",
"owner2": ROOT / "Standalone/data/regression/owner_world_2strate_l61.json",
}
BINARIES = {
"clang": ROOT / "Saved/Linux/l115/bin/VoxelForgeCoreHeadless-l115-clang",
"gcc": ROOT / "Saved/Linux/l115/bin/VoxelForgeCoreHeadless-l115-gcc",
}
HASH = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M)
def run(dataset: str, recipe: str, workers: int, compiler: str) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
label = f"{dataset}-{recipe}-w{workers}-{compiler}"
log = OUT / f"{label}.log"
layout = OUT / f"{label}.layout.json"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARIES[compiler]), str(DATASETS[dataset]),
f"--workers={workers}", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=8", f"--recipe={recipe}",
"--stream-hash", f"--resolved-layout-output={layout}",
]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1740)
output = log.read_text(encoding="utf-8", errors="replace")
hashes = HASH.findall(output)
if result.returncode or len(hashes) != 12:
raise SystemExit(f"hash run failed or incomplete: {label}; exit={result.returncode} "
f"hashes={len(hashes)} log={log}")
row = {"dataset": dataset, "recipe": recipe, "workers": workers,
"compiler": compiler, "exit_code": result.returncode,
"hashes": hashes, "source_stamp": (STAMP.search(output).group(1)
if STAMP.search(output) else None),
"elapsed_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT))}
print("[L115HashRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
if any(not path.is_file() for path in (*DATASETS.values(), *BINARIES.values())):
raise SystemExit("missing L115 hash input")
rows = []
recipes = {"owner4": ("surface-world",),
"owner2": ("surface-world", "floating-islands")}
for dataset, dataset_recipes in recipes.items():
for recipe in dataset_recipes:
for workers in (1, 8, 12):
for compiler in ("clang", "gcc"):
rows.append(run(dataset, recipe, workers, compiler))
(OUT / "runs-partial.json").write_text(
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
groups = {}
for dataset, dataset_recipes in recipes.items():
for recipe in dataset_recipes:
key = f"{dataset}/{recipe}"
group = [row for row in rows if row["dataset"] == dataset
and row["recipe"] == recipe]
reference = group[0]["hashes"]
groups[key] = {"all_worker_compiler_hashes_match": all(
row["hashes"] == reference for row in group),
"source_stamps": sorted({row["source_stamp"] for row in group}),
"runs": [{"workers": row["workers"], "compiler": row["compiler"],
"log": row["log"]} for row in group]}
if not groups[key]["all_worker_compiler_hashes_match"]:
raise SystemExit(f"determinism mismatch: {key}")
if dataset == "owner2":
canonical = next(row for row in group if row["workers"] == 12
and row["compiler"] == "clang")
recipe_name = "floating-islands" if recipe == "floating-islands" else "surface-world"
style = 1 if recipe_name == "surface-world" else 2
baseline = (ROOT / "Standalone/Tools/verify/baselines/owner-hash/canonical"
/ f"owner-hash-{recipe_name}-style{style}-v223.baseline")
baseline.write_text("\n".join(canonical["hashes"]) + "\n",
encoding="utf-8")
summary = {"runs": rows, "groups": groups,
"owner4_all_four_strata": "owner4/surface-world",
"owner2_canonical_pins": ["owner2/surface-world",
"owner2/floating-islands"]}
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""Five interleaved L80 readiness runs for L109, L114, and L115."""
from __future__ import annotations
import json
import os
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l115/timing"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"L109": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeCoreHeadless-l109-clang",
"L114": ROOT / "Saved/Linux/l114/bin/VoxelForgeCoreHeadless-l114-clang",
"L115": ROOT / "Saved/Linux/l115/bin/VoxelForgeCoreHeadless-l115-clang",
}
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
NEAR = re.compile(
r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$",
re.M,
)
def run(strate: int, workers: int, version: str, phase: str, replicate: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
label = f"s{strate}-w{workers}-{version}-{phase}{replicate}"
log = OUT / f"{label}.log"
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARIES[version]), str(DATA),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
f"--workers={workers}", "--clip-radius=3", "--max-clip-level=8", "--max-level=8",
f"--camera={camera}", "--profile-lod-work", "--stream-scheduler-policy=3",
]
start = time.monotonic()
with log.open("w", encoding="utf-8") as output:
result = subprocess.run(command, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
content = log.read_text(encoding="utf-8", errors="replace")
pipeline_match = PIPELINE.search(content)
near_match = NEAR.search(content)
if result.returncode or pipeline_match is None or near_match is None:
raise SystemExit(f"readiness run failed or trace missing: {log}; exit={result.returncode}")
pipeline = dict(FIELDS.findall(pipeline_match.group(1)))
row = {
"strate": strate, "workers": workers, "version": version,
"phase": phase, "replicate": replicate, "exit_code": result.returncode,
"wall_s": round(time.monotonic() - start, 3),
"lod02_ready_s": float(near_match.group(3)),
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
"whole_tiles": int(pipeline["whole_tiles"].split("/")[0]),
"log": str(log.relative_to(ROOT)),
}
print("[L115TimingRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
if not DATA.is_file() or any(not binary.is_file() for binary in BINARIES.values()):
raise SystemExit("missing L109/L114/L115 data or timing binary")
OUT.mkdir(parents=True, exist_ok=True)
load_1m = os.getloadavg()[0]
uptime = subprocess.run(["uptime"], cwd=ROOT, text=True, capture_output=True,
check=True).stdout.strip()
top = subprocess.run(["ps", "-eo", "pid,pcpu,comm", "--sort=-pcpu"], cwd=ROOT,
text=True, capture_output=True, check=True).stdout
(OUT / "machine-load-before.txt").write_text(
f"uptime: {uptime}\n1-minute load: {load_1m:.2f}\ntop CPU users:\n{top}",
encoding="utf-8")
print(f"[L115TimingGate] load_1m={load_1m:.2f} uptime={uptime}", flush=True)
if load_1m >= 1.5:
raise SystemExit(f"timing refused: 1-minute load {load_1m:.2f} is not below 1.5")
rows = []
versions = ("L109", "L114", "L115")
for strate in range(4):
for workers in (8, 12):
for version in versions:
run(strate, workers, version, "warmup", 0)
for replicate in range(1, 6):
offset = (replicate - 1) % len(versions)
order = versions[offset:] + versions[:offset]
for version in order:
rows.append(run(strate, workers, version, "measured", replicate))
(OUT / "runs-partial.json").write_text(
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
summary = []
for strate in range(4):
for workers in (8, 12):
item = {"strate": strate, "workers": workers, "replicates": 5}
medians = {}
for version in versions:
group = [row for row in rows if row["strate"] == strate
and row["workers"] == workers and row["version"] == version]
medians[version] = {}
for key in ("lod02_ready_s", "whole_view_ready_s"):
values = [row[key] for row in group]
medians[version][key] = statistics.median(values)
item[f"{version}_{key}_values"] = values
item[f"{version}_{key}_median"] = medians[version][key]
for comparison in ("L109", "L114"):
for key in ("lod02_ready_s", "whole_view_ready_s"):
item[f"L115_vs_{comparison}_{key}_delta_pct"] = round(
100.0 * (medians["L115"][key] / medians[comparison][key] - 1.0), 2)
summary.append(item)
(OUT / "summary.json").write_text(
json.dumps({"runs": rows, "summary": summary}, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,27 @@
# L115 captures
Each passage has three floor close-ups sampled outside the audited open-air spans. Floor anchors require an upward normal alignment of at least 0.85, a target-to-floor distance of at most 0.25 m, and a camera-to-floor distance between 2.0 and 3.2 m. Each open-air section has verified outside and interior wall close-ups at 4 m.
- [passage/0-1/0 rock-floor/401.7m](captures/L115/L115/passage-0-1-0-rock-floor-station-1.png) — rock-floor-close; 122256 triangles; pass
- [passage/0-1/0 rock-floor/792.8m](captures/L115/L115/passage-0-1-0-rock-floor-station-2.png) — rock-floor-close; 585992 triangles; pass
- [passage/0-1/0 rock-floor/1634.5m](captures/L115/L115/passage-0-1-0-rock-floor-station-3.png) — rock-floor-close; 137252 triangles; pass
- [passage/1-2/0 rock-floor/27.0m](captures/L115/L115/passage-1-2-0-rock-floor-station-1.png) — rock-floor-close; 652088 triangles; pass
- [passage/1-2/0 rock-floor/52.9m](captures/L115/L115/passage-1-2-0-rock-floor-station-2.png) — rock-floor-close; 636128 triangles; pass
- [passage/1-2/0 rock-floor/78.9m](captures/L115/L115/passage-1-2-0-rock-floor-station-3.png) — rock-floor-close; 580512 triangles; pass
- [passage/2-3/0 rock-floor/323.4m](captures/L115/L115/passage-2-3-0-rock-floor-station-1.png) — rock-floor-close; 474896 triangles; low_contrast_geometry_visible
- [passage/2-3/0 rock-floor/333.4m](captures/L115/L115/passage-2-3-0-rock-floor-station-2.png) — rock-floor-close; 494436 triangles; pass
- [passage/2-3/0 rock-floor/343.4m](captures/L115/L115/passage-2-3-0-rock-floor-station-3.png) — rock-floor-close; 485332 triangles; pass
- [passage/0-1/0 section-1/4m-outside](captures/L115/L115/passage-0-1-0-openair-1-4m-outside.png) — open-air-close; 275313 triangles; pass
- [passage/0-1/0 section-1/4m-interior-wall](captures/L115/L115/passage-0-1-0-openair-1-4m-interior-wall.png) — open-air-close; 307964 triangles; low_contrast_geometry_visible
- [passage/0-1/0 section-2/4m-outside](captures/L115/L115/passage-0-1-0-openair-2-4m-outside.png) — open-air-close; 275313 triangles; pass
- [passage/0-1/0 section-2/4m-interior-wall](captures/L115/L115/passage-0-1-0-openair-2-4m-interior-wall.png) — open-air-close; 345024 triangles; pass
- [passage/0-1/0 section-3/4m-outside](captures/L115/L115/passage-0-1-0-openair-3-4m-outside.png) — open-air-close; 148970 triangles; pass
- [passage/0-1/0 section-3/4m-interior-wall](captures/L115/L115/passage-0-1-0-openair-3-4m-interior-wall.png) — open-air-close; 148032 triangles; pass
- [passage/1-2/0 section-1/4m-outside](captures/L115/L115/passage-1-2-0-openair-1-4m-outside.png) — open-air-close; 620866 triangles; low_contrast_geometry_visible
- [passage/1-2/0 section-1/4m-interior-wall](captures/L115/L115/passage-1-2-0-openair-1-4m-interior-wall.png) — open-air-close; 609733 triangles; pass
- [passage/1-2/0 section-2/4m-outside](captures/L115/L115/passage-1-2-0-openair-2-4m-outside.png) — open-air-close; 584622 triangles; pass
- [passage/1-2/0 section-2/4m-interior-wall](captures/L115/L115/passage-1-2-0-openair-2-4m-interior-wall.png) — open-air-close; 572238 triangles; pass
- [passage/2-3/0 section-1/4m-outside](captures/L115/L115/passage-2-3-0-openair-1-4m-outside.png) — open-air-close; 104612 triangles; low_contrast_geometry_visible
- [passage/2-3/0 section-1/4m-interior-wall](captures/L115/L115/passage-2-3-0-openair-1-4m-interior-wall.png) — open-air-close; 94962 triangles; low_contrast_geometry_visible
- [passage/2-3/0 section-2/4m-outside](captures/L115/L115/passage-2-3-0-openair-2-4m-outside.png) — open-air-close; 475838 triangles; pass
- [passage/2-3/0 section-2/4m-interior-wall](captures/L115/L115/passage-2-3-0-openair-2-4m-interior-wall.png) — open-air-close; 486512 triangles; pass
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: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 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: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 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: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

File diff suppressed because it is too large. Load diff
+285 -116
View File
@@ -3166,18 +3166,58 @@ namespace StandaloneStreamBenchmark
std::lerp(a.floorZ,b.floorZ,t),
std::lerp(a.radius,b.radius,t),distance,b.segmentIndex};
};
const char* l114DensityProfile=std::getenv("VOXELFORGE_L114_DENSITY_PROFILE");
if(l114DensityProfile&&l114DensityProfile[0]=='1'&&l114DensityProfile[1]=='\0')
struct FPassageRouteProjection
{
for(const char* passageId:{"passage/0-1/0","passage/1-2/0"})
double centerDistanceSquared=std::numeric_limits<double>::infinity();
double tubeDistance=std::numeric_limits<double>::infinity();
double centerAlong=0.0,tubeAlong=0.0;
};
const auto projectPassageRoute=[](
const std::vector<FPassageAuditRoutePoint>& route,
double x,double y,double z)
{
FPassageRouteProjection projection;
for(std::size_t segment=1;segment<route.size();++segment)
{
const auto passage=std::find_if(ResolvedLayout->passages.begin(),
ResolvedLayout->passages.end(),[&](const auto& candidate)
{return candidate.stableId==passageId;});
if(passage==ResolvedLayout->passages.end())continue;
const std::size_t passageIndex=static_cast<std::size_t>(
passage-ResolvedLayout->passages.begin());
const auto& a=route[segment-1];
const auto& b=route[segment];
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
const double lengthSquared=dx*dx+dy*dy+dz*dz;
if(lengthSquared<=1.0e-12)continue;
const double horizontalLengthSquared=dx*dx+dy*dy;
const double horizontalT=horizontalLengthSquared>1.0e-12
?((x-a.x)*dx+(y-a.y)*dy)/horizontalLengthSquared
:std::clamp((z-a.z)*dz/lengthSquared,0.0,1.0);
const double t=std::clamp(((x-a.x)*dx+(y-a.y)*dy
+(z-a.z)*dz)/lengthSquared,0.0,1.0);
const double ex=x-(a.x+t*dx),ey=y-(a.y+t*dy),ez=z-(a.z+t*dz);
const double distanceSquared=ex*ex+ey*ey+ez*ez;
const double along=std::lerp(a.distance,b.distance,t);
const double centerAlong=std::lerp(a.distance,b.distance,horizontalT);
if(distanceSquared<projection.centerDistanceSquared)
{
projection.centerDistanceSquared=distanceSquared;
projection.centerAlong=centerAlong;
}
const double radius=std::lerp(a.radius,b.radius,t);
const double roundDistance=std::sqrt(distanceSquared)-radius;
if(roundDistance<projection.tubeDistance)
{
projection.tubeDistance=roundDistance;
projection.tubeAlong=along;
}
}
return projection;
};
const char* l115DensityProfile=std::getenv("VOXELFORGE_L115_DENSITY_PROFILE");
if(l115DensityProfile&&l115DensityProfile[0]=='1'&&l115DensityProfile[1]=='\0')
{
for(std::size_t passageIndex=0;
passageIndex<ResolvedLayout->passages.size();++passageIndex)
{
const auto& passage=ResolvedLayout->passages[passageIndex];
const auto& route=allPassageSmoothRoutes[passageIndex];
if(route.size()<2)continue;
const auto densityMetres=[&](double x,double y,double z)
{
return static_cast<double>(Shape.densityAt({x/coreMetres,
@@ -3198,34 +3238,38 @@ namespace StandaloneStreamBenchmark
}
return (low+high)*0.5;
};
const std::array<std::pair<const char*,double>,2> targets={{
{"rock-station-50",route.empty()?0.0:route.back().distance*0.5},
{"open-air-section-3",1265.3425}}};
for(const auto& [label,targetDistance]:targets)
constexpr std::array<const char*,3> labels{{
"route-station-25","route-station-50","route-station-75"}};
for(std::size_t targetIndex=0;targetIndex<labels.size();++targetIndex)
{
if(route.size()<2||targetDistance>route.back().distance)continue;
const double targetDistance=route.back().distance
*static_cast<double>(targetIndex+1)/4.0;
const char* label=labels[targetIndex];
const auto center=interpolatePassageRoute(route,targetDistance);
const VoxelForgeCore::Vec3d centerVoxel={center.x/coreMetres,
center.y/coreMetres,center.floorZ/coreMetres};
std::printf("[L114DensityColumn] passage=%s section=%s x_m=%.6f y_m=%.6f planned_floor_m=%.6f dispatch=%s source_density_m=%.9f unclamped_density_m=%.9f\n",
passage->stableId.c_str(),label,center.x,center.y,center.floorZ,
std::printf("[L115DensityColumn] passage=%s station=%s route_s_m=%.3f route_length_m=%.3f x_m=%.6f y_m=%.6f planned_floor_m=%.6f dispatch=%s source_density_m=%.9f unclamped_density_m=%.9f\n",
passage.stableId.c_str(),label,targetDistance,
route.back().distance,center.x,center.y,center.floorZ,
Shape.densityDispatchPathAt(centerVoxel).c_str(),
static_cast<double>(Shape.passageSourceDensityAt(centerVoxel))*coreMetres,
static_cast<double>(Shape.unclampedDensityAt(centerVoxel))*coreMetres);
for(int sample=0;sample<=40;++sample)
{
const double offset=-0.50+sample*0.025;
std::printf("[L114DensityColumnSample] section=%s dz_m=%.3f density_m=%.9f\n",
label,offset,densityMetres(center.x,center.y,
std::printf("[L115DensityColumnSample] passage=%s station=%s dz_m=%.3f density_m=%.9f\n",
passage.stableId.c_str(),label,offset,densityMetres(center.x,center.y,
center.floorZ+offset));
}
for(int sample=-25;sample<=25;++sample)
{
const double distance=targetDistance+sample*0.10;
const double distance=std::clamp(targetDistance
+sample*0.10,0.0,route.back().distance);
const auto pose=interpolatePassageRoute(route,distance);
const double root=floorRoot(pose);
std::printf("[L114DensitySlopeSample] section=%s s_m=%.3f x_m=%.6f y_m=%.6f planned_floor_m=%.6f root_floor_m=%.6f residual_m=%.6f density_minus_0p1_m=%.9f density_plane_m=%.9f density_plus_0p1_m=%.9f\n",
label,distance,pose.x,pose.y,pose.floorZ,root,
std::printf("[L115DensitySlopeSample] passage=%s station=%s s_m=%.3f x_m=%.6f y_m=%.6f planned_floor_m=%.6f root_floor_m=%.6f residual_m=%.6f density_minus_0p1_m=%.9f density_plane_m=%.9f density_plus_0p1_m=%.9f\n",
passage.stableId.c_str(),label,distance,pose.x,pose.y,
pose.floorZ,root,
root-pose.floorZ,
densityMetres(pose.x,pose.y,pose.floorZ-0.1),
densityMetres(pose.x,pose.y,pose.floorZ),
@@ -3240,6 +3284,13 @@ namespace StandaloneStreamBenchmark
{
constexpr double CornerBlendMetres=0.50;
constexpr double WallReliefMetres=0.21;
const auto smoothMax=[](double left,double right,double radius)
{
if(!(radius>0.0))return std::max(left,right);
const double h=std::clamp(radius-std::abs(left-right),0.0,radius)
/radius;
return std::max(left,right)+h*h*h*(radius/6.0);
};
for(std::size_t passageIndex=0;
passageIndex<ResolvedLayout->passages.size();++passageIndex)
{
@@ -3247,9 +3298,12 @@ namespace StandaloneStreamBenchmark
if(passage.controlPoints.size()<2)continue;
const auto& route=allPassageSmoothRoutes[passageIndex];
if(route.size()<2)continue;
double bestDistance=std::numeric_limits<double>::infinity();
double bestRoundDistance=0.0,bestCenterZ=0.0;
std::size_t bestSegment=std::numeric_limits<std::size_t>::max();
double bestRoundDistance=std::numeric_limits<double>::infinity();
double bestFloorCenterDistanceSquared=
std::numeric_limits<double>::infinity();
double bestCenterZ=0.0,bestFloorCenterZ=0.0,bestRadius=0.0;
std::size_t bestTubeSegment=std::numeric_limits<std::size_t>::max();
std::size_t bestFloorSegment=std::numeric_limits<std::size_t>::max();
for(std::size_t segment=1;segment<route.size();++segment)
{
const auto& a=route[segment-1];
@@ -3259,57 +3313,67 @@ namespace StandaloneStreamBenchmark
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
const double lengthSquared=dx*dx+dy*dy+dz*dz;
if(lengthSquared<=1.0e-12)continue;
const double horizontalLengthSquared=dx*dx+dy*dy;
const double horizontalT=horizontalLengthSquared>1.0e-12
?((xMetres-a.x)*dx+(yMetres-a.y)*dy)
/horizontalLengthSquared
:std::clamp((zMetres-a.z)*dz/lengthSquared,0.0,1.0);
const double floorAlong=std::lerp(a.distance,b.distance,horizontalT);
const double t=std::clamp(((xMetres-a.x)*dx+(yMetres-a.y)*dy
+(zMetres-a.z)*dz)/lengthSquared,0.0,1.0);
const double centerX=a.x+t*dx,centerY=a.y+t*dy,centerZ=a.z+t*dz;
const double radius=std::lerp(a.radius,b.radius,t);
const double horizontalLengthSquared=dx*dx+dy*dy;
const double floorT=horizontalLengthSquared>1.0e-12
?std::clamp(((xMetres-a.x)*dx+(yMetres-a.y)*dy)
/horizontalLengthSquared,0.0,1.0):t;
const double ex=xMetres-centerX,ey=yMetres-centerY,
ez=zMetres-centerZ;
const double roundDistance=std::sqrt(ex*ex+ey*ey+ez*ez)-radius;
const double floorCenterDistanceSquared=ex*ex+ey*ey+ez*ez;
const auto floorPose=interpolatePassageRoute(route,floorAlong);
const double floorCenterZ=passageFloorAtXY(passageIndex,
xMetres,yMetres,std::lerp(a.floorZ,b.floorZ,floorT));
const double roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX)
+(yMetres-centerY)*(yMetres-centerY)
+(zMetres-centerZ)*(zMetres-centerZ))-radius;
const double floorDistance=floorCenterZ-zMetres;
const double h=std::max(CornerBlendMetres-
std::abs(roundDistance-floorDistance),0.0)/CornerBlendMetres;
const double flatDistance=std::max(roundDistance,floorDistance)
+h*h*h*(CornerBlendMetres/6.0);
if(flatDistance<bestDistance)
xMetres,yMetres,floorPose.floorZ);
// A self-overlap can place another route span's open bore
// through this nominal floor probe. Such a point is interior,
// not a missing floor shell, even when a different span is
// nearest by a few floating-point ulps.
if(smoothMax(roundDistance,floorCenterZ-zMetres,
CornerBlendMetres)<=0.0)
return true;
if(floorCenterDistanceSquared<bestFloorCenterDistanceSquared
||(floorCenterDistanceSquared==bestFloorCenterDistanceSquared
&&segment<bestFloorSegment))
{
bestFloorCenterDistanceSquared=floorCenterDistanceSquared;
bestFloorCenterZ=floorCenterZ;
bestFloorSegment=segment;
}
if(roundDistance<bestRoundDistance
||(roundDistance==bestRoundDistance
&&segment<bestTubeSegment))
{
bestDistance=flatDistance;
bestRoundDistance=roundDistance;
bestCenterZ=centerZ;
bestSegment=segment;
bestRadius=radius;
bestTubeSegment=segment;
}
}
if(bestSegment==std::numeric_limits<std::size_t>::max())continue;
const auto& a=route[bestSegment-1];
const auto& b=route[bestSegment];
if(bestTubeSegment==std::numeric_limits<std::size_t>::max())continue;
if(!std::isfinite(bestFloorCenterDistanceSquared))continue;
const double floorCenterZ=bestFloorCenterZ;
const double floorDistance=bestFloorCenterZ-zMetres;
// The composer uses the nearest round tube segment, then the
// nearest route station for its continuous floor plane.
const auto& a=route[bestTubeSegment-1];
const auto& b=route[bestTubeSegment];
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
const double lengthSquared=dx*dx+dy*dy+dz*dz;
if(lengthSquared<=1.0e-12)continue;
const double t=std::clamp(((xMetres-a.x)*dx+(yMetres-a.y)*dy
+(zMetres-a.z)*dz)/lengthSquared,0.0,1.0);
const double radius=std::lerp(a.radius,b.radius,t);
const double centerX=a.x+t*dx,centerY=a.y+t*dy;
const double centerZ=a.z+t*dz;
const double distanceToCenter=std::max(1.0e-9,
bestRoundDistance+radius);
bestRoundDistance+bestRadius);
const double verticalWallAlignment=std::clamp(
-(zMetres-centerZ)/distanceToCenter,0.0,1.0);
const auto& aFloor=route[bestSegment-1];
const auto& bFloor=route[bestSegment];
const double floorDx=bFloor.x-aFloor.x;
const double floorDy=bFloor.y-aFloor.y;
const double horizontalSquared=floorDx*floorDx+floorDy*floorDy;
const double floorT=horizontalSquared>1.0e-12
?std::clamp(((xMetres-aFloor.x)*floorDx
+(yMetres-aFloor.y)*floorDy)/horizontalSquared,0.0,1.0):t;
const double floorCenterZ=passageFloorAtXY(passageIndex,
xMetres,yMetres,std::lerp(aFloor.floorZ,bFloor.floorZ,floorT));
const double wallElevationMetres=zMetres-floorCenterZ;
const double crestFade=std::clamp((1.0-wallElevationMetres)/0.25,
0.0,1.0);
@@ -3325,11 +3389,8 @@ namespace StandaloneStreamBenchmark
const double roughRoundDistance=bestRoundDistance
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))
*WallReliefMetres*wallReliefWeight;
const double floorDistance=floorCenterZ-zMetres;
const double blend=std::max(CornerBlendMetres-
std::abs(roughRoundDistance-floorDistance),0.0)/CornerBlendMetres;
const double shapedDistance=std::max(roughRoundDistance,floorDistance)
+blend*blend*blend*(CornerBlendMetres/6.0);
const double shapedDistance=smoothMax(roughRoundDistance,
floorDistance,CornerBlendMetres);
if(shapedDistance<=0.0)return true;
}
return false;
@@ -4129,9 +4190,9 @@ namespace StandaloneStreamBenchmark
std::uint64_t floorMouthBlendSkips=0,wallMouthBlendSkips=0;
std::uint64_t interiorSamples=0,interiorViolations=0;
std::vector<double> openAirDistancesMetres;
std::array<std::array<double,4>,8> firstViolations{};
std::array<std::array<double,10>,8> firstViolations{};
std::size_t firstViolationCount=0;
std::array<std::array<double,4>,8> firstWallViolations{};
std::array<std::array<double,13>,8> firstWallViolations{};
std::size_t firstWallViolationCount=0;
};
constexpr double PassageShellAuditSpacingMetres=0.25;
@@ -4142,13 +4203,10 @@ namespace StandaloneStreamBenchmark
{
constexpr double CornerBlendMetres=0.50;
constexpr double ReliefBoundMetres=0.21;
const auto smoothstep=[](double value)
{
value=std::clamp(value,0.0,1.0);
return value*value*(3.0-2.0*value);
};
bool ambiguous=false,expectedSolid=false,mouthBlend=false;
if(passageInteriorWinsAt(xMetres,yMetres,zMetres))return 0;
double nearestWallShellDistance=std::numeric_limits<double>::infinity();
bool mouthBlend=false;
if(passageInteriorWinsAt(xMetres,yMetres,zMetres))
return std::pair<int,double>{0,nearestWallShellDistance};
for(std::size_t passageIndex=0;
passageIndex<ResolvedLayout->passages.size();++passageIndex)
{
@@ -4165,17 +4223,20 @@ namespace StandaloneStreamBenchmark
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
const double lengthSquared=dx*dx+dy*dy+dz*dz;
if(lengthSquared<=1.0e-12)continue;
const double horizontalLengthSquared=dx*dx+dy*dy;
const double horizontalT=horizontalLengthSquared>1.0e-12
?((xMetres-ax)*dx+(yMetres-ay)*dy)
/horizontalLengthSquared
:std::clamp((zMetres-az)*dz/lengthSquared,0.0,1.0);
const double floorAlong=std::lerp(a.distance,b.distance,horizontalT);
const double t=std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy
+(zMetres-az)*dz)/lengthSquared,0.0,1.0);
const double centerX=ax+t*dx,centerY=ay+t*dy,centerZ=az+t*dz;
const double radius=std::lerp(a.radius,b.radius,t);
const double horizontalSquared=dx*dx+dy*dy;
const double floorT=horizontalSquared>1.0e-12
?std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy)
/horizontalSquared,0.0,1.0):t;
const double floorCenterZ=passageFloorAtXY(passageIndex,
xMetres,yMetres,std::lerp(a.floorZ,b.floorZ,floorT));
const double along=std::lerp(a.distance,b.distance,t);
const auto floorPose=interpolatePassageRoute(path,floorAlong);
const double floorCenterZ=passageFloorAtXY(passageIndex,
xMetres,yMetres,floorPose.floorZ);
const double roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX)
+(yMetres-centerY)*(yMetres-centerY)
+(zMetres-centerZ)*(zMetres-centerZ))-radius;
@@ -4189,19 +4250,33 @@ namespace StandaloneStreamBenchmark
if(flatDistance<=0.0)continue;
const double fade=std::clamp(mouthDistance/5.0,0.0,1.0);
const double smoothFade=fade*fade*(3.0-2.0*fade);
const double floorBlend=smoothstep(floorDistance/0.25);
const double wallElevation=-floorDistance;
const double wallThickness=wallElevation>=0.0&&wallElevation<=1.0
?smoothFade:0.0;
const double thickness=0.75*std::max(wallThickness,
smoothFade*floorBlend);
if(flatDistance+ReliefBoundMetres<thickness)expectedSolid=true;
else if(flatDistance-ReliefBoundMetres<thickness)ambiguous=true;
?smoothFade*0.75:0.0;
if(wallThickness>1.0e-9)
{
const double shellRadius=wallThickness*0.5;
const double capCenter=1.0-shellRadius;
const double x=flatDistance-shellRadius;
const double boxHalfHeight=capCenter*0.5;
const double boxX=std::abs(x)-shellRadius;
const double boxY=std::abs(wallElevation-boxHalfHeight)
-boxHalfHeight;
const double boxDistance=std::min(std::max(boxX,boxY),0.0)
+std::hypot(std::max(boxX,0.0),std::max(boxY,0.0));
const double capDistance=std::hypot(x,wallElevation-capCenter)
-shellRadius;
nearestWallShellDistance=std::min(nearestWallShellDistance,
std::min(boxDistance,capDistance));
}
else if(mouthDistance<5.0)mouthBlend=true;
}
}
if(expectedSolid)return 1;
return ambiguous||mouthBlend?-1:0;
if(nearestWallShellDistance<-ReliefBoundMetres)
return std::pair<int,double>{1,nearestWallShellDistance};
if(nearestWallShellDistance<ReliefBoundMetres||mouthBlend)
return std::pair<int,double>{-1,nearestWallShellDistance};
return std::pair<int,double>{0,nearestWallShellDistance};
};
const auto auditPassageShell=[&](const VoxelForgeCore::World& Field,
const VoxelForgeCore::World& Native,
@@ -4216,16 +4291,66 @@ namespace StandaloneStreamBenchmark
constexpr std::array<std::array<double,2>,5> floorOffsets{{
{{0.0,0.0}},{{0.30,0.0}},{{-0.30,0.0}},
{{0.0,0.30}},{{0.0,-0.30}}}};
const auto expectedFloorThickness=[&](double along,double floorDistance)
const auto expectedFloorShellMarginAt=[&](double xMetres,
double yMetres,double zMetres)
{
const double mouthDistance=std::max(0.0,std::min(along,
totalRouteLength-along));
const double fade=std::clamp(mouthDistance/5.0,0.0,1.0);
const double smoothFade=fade*fade*(3.0-2.0*fade);
const double floorBlend=std::clamp(floorDistance/0.25,0.0,1.0);
const double smoothFloorBlend=floorBlend*floorBlend
*(3.0-2.0*floorBlend);
return 0.75*smoothFade*smoothFloorBlend;
constexpr double CornerBlendMetres=0.50;
double minimumShellDistance=std::numeric_limits<double>::infinity();
for(std::size_t candidatePassage=0;
candidatePassage<allPassageSmoothRoutes.size();++candidatePassage)
{
const auto& candidateRoute=
allPassageSmoothRoutes[candidatePassage];
if(candidateRoute.size()<2)continue;
const double candidateLength=candidateRoute.back().distance;
for(std::size_t candidateSegment=1;
candidateSegment<candidateRoute.size();++candidateSegment)
{
const auto& a=candidateRoute[candidateSegment-1];
const auto& b=candidateRoute[candidateSegment];
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
const double lengthSquared=dx*dx+dy*dy+dz*dz;
if(lengthSquared<=1.0e-12)continue;
const double t=std::clamp(((xMetres-a.x)*dx
+(yMetres-a.y)*dy+(zMetres-a.z)*dz)/lengthSquared,
0.0,1.0);
const double horizontalLengthSquared=dx*dx+dy*dy;
const double floorT=horizontalLengthSquared>1.0e-12
?((xMetres-a.x)*dx+(yMetres-a.y)*dy)
/horizontalLengthSquared:t;
const double centerX=a.x+t*dx,centerY=a.y+t*dy,
centerZ=a.z+t*dz;
const double radius=std::lerp(a.radius,b.radius,t);
const double along=std::lerp(a.distance,b.distance,t);
const double floorAlong=std::lerp(a.distance,b.distance,floorT);
const double plannedFloor=passageFloorAtXY(candidatePassage,
xMetres,yMetres,interpolatePassageRoute(candidateRoute,
floorAlong).floorZ);
const double roundDistance=std::sqrt(
(xMetres-centerX)*(xMetres-centerX)
+(yMetres-centerY)*(yMetres-centerY)
+(zMetres-centerZ)*(zMetres-centerZ))-radius;
const double floorDistance=plannedFloor-zMetres;
const double blend=std::max(CornerBlendMetres
-std::abs(roundDistance-floorDistance),0.0)
/CornerBlendMetres;
const double flatDistance=std::max(roundDistance,floorDistance)
+blend*blend*blend*(CornerBlendMetres/6.0);
const double mouthDistance=std::max(0.0,std::min(along,
candidateLength-along));
const double fade=std::clamp(mouthDistance/5.0,0.0,1.0);
const double smoothFade=fade*fade*(3.0-2.0*fade);
const double floorBlend=std::clamp(floorDistance/0.25,0.0,1.0);
const double smoothFloorBlend=floorBlend*floorBlend
*(3.0-2.0*floorBlend);
const double thickness=0.75*smoothFade*smoothFloorBlend;
const double shellDistance=std::max(-flatDistance,
flatDistance-thickness);
minimumShellDistance=std::min(minimumShellDistance,
shellDistance);
}
}
return minimumShellDistance;
};
for(std::size_t segment=1;segment<route.size();++segment)
{
@@ -4249,14 +4374,20 @@ namespace StandaloneStreamBenchmark
for(const auto& offset:floorOffsets)
for(const double depth:PassageShellAuditDepthsMetres)
{
const double horizontalLengthSquared=dx*dx+dy*dy;
const double sampleX=centerX+offset[0];
const double sampleY=centerY+offset[1];
const double floorT=horizontalLengthSquared>1.0e-12
?std::clamp(((sampleX-a.x)*dx+(sampleY-a.y)*dy)
/horizontalLengthSquared,0.0,1.0):t;
const double floorZ=passageFloorAtXY(passageIndex,
sampleX,sampleY,std::lerp(a.floorZ,b.floorZ,floorT));
double floorZ=passageFloorAtXY(passageIndex,
sampleX,sampleY,interpolatePassageRoute(route,along).floorZ);
FPassageRouteProjection floorProjection;
for(int projectionStep=0;projectionStep<2;++projectionStep)
{
floorProjection=projectPassageRoute(route,sampleX,sampleY,
floorZ-depth);
const auto floorPose=interpolatePassageRoute(route,
floorProjection.centerAlong);
floorZ=passageFloorAtXY(passageIndex,sampleX,sampleY,
floorPose.floorZ);
}
const VoxelForgeCore::Vec3d position{
sampleX/coreMetres,sampleY/coreMetres,
(floorZ-depth)/coreMetres};
@@ -4271,8 +4402,14 @@ namespace StandaloneStreamBenchmark
{
++audit.openAirSamples;
audit.openAirDistancesMetres.push_back(along);
const bool shellExpected=depth+0.011
<expectedFloorThickness(along,depth);
const double shellMargin=expectedFloorShellMarginAt(
sampleX,sampleY,sampleZ);
if(std::abs(shellMargin)<=0.011)
{
++audit.floorMouthBlendSkips;
continue;
}
const bool shellExpected=shellMargin<-0.011;
const float density=Field.densityAt(position);
if((shellExpected&&density>=PassageShellDensityTolerance)
||(!shellExpected&&density<-PassageShellDensityTolerance))
@@ -4280,7 +4417,10 @@ namespace StandaloneStreamBenchmark
++audit.floorViolations;
if(audit.firstViolationCount<audit.firstViolations.size())
audit.firstViolations[audit.firstViolationCount++]={
sampleX,sampleY,sampleZ,density};
sampleX,sampleY,sampleZ,density,along,depth,
floorZ,floorProjection.centerAlong,
floorProjection.tubeAlong,
shellMargin};
}
}
}
@@ -4294,8 +4434,8 @@ namespace StandaloneStreamBenchmark
}
const double nx=-dy/tangentLength,ny=dx/tangentLength;
const double floorDepth=passageTubeDepthAt(radius,bodyFloorDepth);
const double floorZ=passageFloorAtXY(passageIndex,centerX,centerY,
std::lerp(a.floorZ,b.floorZ,t));
double floorZ=passageFloorAtXY(passageIndex,centerX,centerY,
interpolatePassageRoute(route,along).floorZ);
struct FWallProbe {double side=0.0,height=0.0;bool roof=false;};
const std::array<FWallProbe,5> wallProbes{{
{0.0,0.0,true},{-1.0,0.25,false},{1.0,0.25,false},
@@ -4313,12 +4453,21 @@ namespace StandaloneStreamBenchmark
const double wallRadius=std::sqrt(wallRadiusSquared);
sampleX+=nx*probe.side*(wallRadius+0.375);
sampleY+=ny*probe.side*(wallRadius+0.375);
for(int projectionStep=0;projectionStep<2;++projectionStep)
{
sampleZ=floorZ+probe.height;
const auto floorProjection=projectPassageRoute(route,
sampleX,sampleY,sampleZ);
const auto floorPose=interpolatePassageRoute(route,
floorProjection.centerAlong);
floorZ=passageFloorAtXY(passageIndex,sampleX,sampleY,
floorPose.floorZ);
}
sampleZ=floorZ+probe.height;
}
const VoxelForgeCore::Vec3d wallPosition{sampleX/coreMetres,
sampleY/coreMetres,sampleZ/coreMetres};
if(passageInteriorWinsAt(sampleX,sampleY,sampleZ,
passageIndex,b.segmentIndex))
if(passageInteriorWinsAt(sampleX,sampleY,sampleZ))
{
++audit.wallInteriorWins;
continue;
@@ -4328,8 +4477,8 @@ namespace StandaloneStreamBenchmark
++audit.wallSamples;
audit.openAirDistancesMetres.push_back(along);
const float density=Field.densityAt(wallPosition);
const int shellExpected=expectedPassageShellAt(
sampleX,sampleY,sampleZ);
const auto [shellExpected,wallShellMargin]=
expectedPassageShellAt(sampleX,sampleY,sampleZ);
if(shellExpected>=0
&&((shellExpected==1
&&density>=PassageShellDensityTolerance)
@@ -4340,7 +4489,9 @@ namespace StandaloneStreamBenchmark
if(audit.firstWallViolationCount
<audit.firstWallViolations.size())
audit.firstWallViolations[audit.firstWallViolationCount++]={
sampleX,sampleY,sampleZ,density};
sampleX,sampleY,sampleZ,density,along,probe.height,
probe.side,centerX,centerY,centerZ,floorZ,radius,
wallShellMargin};
}
else if(shellExpected<0)
++audit.wallMouthBlendSkips;
@@ -4533,23 +4684,41 @@ namespace StandaloneStreamBenchmark
&&shellAudit.interiorViolations==0
?"PASS":"FAIL");
for(std::size_t i=0;i<shellAudit.firstViolationCount;++i)
std::printf("[PassageShellAuditSample] id=%s kind=floor_missing xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
passage.stableId.c_str(),shellAudit.firstViolations[i][0],
shellAudit.firstViolations[i][1],shellAudit.firstViolations[i][2],
shellAudit.firstViolations[i][3]);
{
const auto& sample=shellAudit.firstViolations[i];
const VoxelForgeCore::Vec3d position{
sample[0]/coreMetres,sample[1]/coreMetres,
sample[2]/coreMetres};
std::printf("[PassageShellAuditSample] id=%s kind=floor_missing xyz_m=(%.3f,%.3f,%.3f) density=%.6f native_density=%.6f native_path=%s probe_s_m=%.3f projected_center_s_m=%.3f projected_tube_s_m=%.3f depth_m=%.3f floor_z_m=%.3f expected_shell_margin_m=%.6f\n",
passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3],
blockedWorld.densityAt(position),
blockedWorld.densityDispatchPathAt(position).c_str(),
sample[4],sample[7],sample[8],sample[5],sample[6],sample[9]);
}
for(std::size_t i=0;i<shellAudit.firstWallViolationCount;++i)
{
const auto& sample=shellAudit.firstWallViolations[i];
const int expected=expectedPassageShellAt(sample[0],sample[1],sample[2]);
const auto [expected,wallShellMargin]=
expectedPassageShellAt(sample[0],sample[1],sample[2]);
const bool passageInterior=passageInteriorWinsAt(
sample[0],sample[1],sample[2]);
std::printf("[PassageShellAuditSample] id=%s kind=wall_missing xyz_m=(%.3f,%.3f,%.3f) density=%.6f expected=%d passage_interior=%s native_density=%.6f\n",
std::printf("[PassageShellAuditSample] id=%s kind=wall_missing xyz_m=(%.3f,%.3f,%.3f) density=%.6f expected=%d wall_shell_margin_m=%.6f passage_interior=%s native_density=%.6f probe_s_m=%.3f probe_height_m=%.3f probe_side=%.0f route_center_m=(%.3f,%.3f,%.3f) floor_z_m=%.3f radius_m=%.3f dispatch=%s passage_source=%.6f unclamped=%.6f\n",
passage.stableId.c_str(),shellAudit.firstWallViolations[i][0],
shellAudit.firstWallViolations[i][1],shellAudit.firstWallViolations[i][2],
shellAudit.firstWallViolations[i][3],expected,
shellAudit.firstWallViolations[i][3],expected,wallShellMargin,
passageInterior?"true":"false",
blockedWorld.densityAt({sample[0]/coreMetres,
sample[1]/coreMetres,sample[2]/coreMetres}));
sample[1]/coreMetres,sample[2]/coreMetres}),
sample[4],sample[5],sample[6],sample[7],sample[8],sample[9],
sample[10],sample[11],
Shape.densityDispatchPathAt({sample[0]/coreMetres,
sample[1]/coreMetres,sample[2]/coreMetres}).c_str(),
static_cast<double>(Shape.passageSourceDensityAt({
sample[0]/coreMetres,sample[1]/coreMetres,
sample[2]/coreMetres})),
static_cast<double>(Shape.unclampedDensityAt({
sample[0]/coreMetres,sample[1]/coreMetres,
sample[2]/coreMetres})));
}
for(std::size_t i=0;i<passageAudit.firstBlockedCount;++i)
{
@@ -0,0 +1,12 @@
[PipelineFullDetailHash] tiles=960 density=0x91931a57a067fd92 mesh=0xa818654be89790ac payload=0x370ff80451105a39 topology=0x06e507557aa70173
[PipelineWholeViewHash] tiles=2370 density=0x24c4b5eac2413d22 mesh=0xb9f3a6b9b413e5d4 payload=0x30a1d2de354e7331 topology=0xaf6be92d376df824
[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=0x39c648c16c7a5eaf mesh=0x9fd2005db5a5355c payload=0x2c5d7ffd168f768c topology=0x2ee8f3a9eb82f6ae
[PipelineLODHash] lod=3 tiles=208 density=0x7d1fd74cb7fcbe50 mesh=0xa94e442025574cc8 payload=0xb2ef1431b68db0bb topology=0xbafd034ec099755c
[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
@@ -0,0 +1,12 @@
[PipelineFullDetailHash] tiles=960 density=0xace56c8f205238ad mesh=0xa9c603a1248de6b8 payload=0xea55f1f4fcc6a894 topology=0x5ea7bc78a1b678ae
[PipelineWholeViewHash] tiles=2370 density=0x134e05bb2f1cb230 mesh=0x8e949829cdf96ae2 payload=0xc80c0474009a0d9f topology=0xd1a5eb8855451bf4
[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=0xb4853d0641e11fee mesh=0x26f79c8d8acdf042 payload=0xaf70f7188cdfaf14 topology=0xd1f2e3638ea429aa
[PipelineLODHash] lod=3 tiles=208 density=0x0aff8a79d5472b9e mesh=0xecae6f1d16b27fbc payload=0xa93c0d823b8185fe topology=0x8fe982d0c84843df
[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
+4 -4
View File
@@ -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-v222.baseline",
"L114-v222-local-passage-floor-profile")
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v223.baseline",
"L115-v223-continuous-arc-length-floor-profile")
if recipe == "floating-islands" and style == 2:
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v222.baseline",
"L114-v222-local-passage-floor-profile")
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v223.baseline",
"L115-v223-continuous-arc-length-floor-profile")
raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")