passages: floor profile by nearest centreline + metric distance outside voids (L114, v222) - WIP
Floor ribs are present in L109/L112/L113 rock tunnels too: the floor height comes from the nearest segment, so it jumps (up to 5.9-7.3 cm) where the nearest segment switches. v222 reduces them; faint ribs remain. Quiet-machine timing (load 0.64): near-ready 9-17% slower than L109 (FI W12 0.95 -> 1.10 s); candidate totals ~5x L109. Fast tier FAIL: 2 SW wall-shell probes of 31,381 read host air. Walk/grade audits pass; Clang/GCC deterministic; UE builds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
No files matched your search
@@ -472,7 +472,9 @@ namespace VoxelForgeCore
|
||||
// Version 218 sweeps passages around a dense smooth spline and limits exposed shells to a trough.
|
||||
// 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.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 220;
|
||||
// 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;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
|
||||
@@ -266,8 +266,10 @@ public:
|
||||
double roundSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double centerZSubvoxels=0.0,floorCenterZSubvoxels=0.0;
|
||||
double radiusSubvoxels=0.0,floorDepthSubvoxels=0.0;
|
||||
double t=0.0;
|
||||
double floorCenterDistanceSq=std::numeric_limits<double>::infinity();
|
||||
double t=0.0,floorT=0.0;
|
||||
std::uint32_t segmentIndex=std::numeric_limits<std::uint32_t>::max();
|
||||
std::uint32_t floorSegmentIndex=std::numeric_limits<std::uint32_t>::max();
|
||||
float wallRelief=0.0f;
|
||||
bool valid=false,hasWallRelief=false;
|
||||
};
|
||||
@@ -1216,8 +1218,9 @@ public:
|
||||
double signedDistance=std::numeric_limits<double>::infinity();
|
||||
double shellSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double roundSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double centerZ=0.0,floorCenterZ=0.0,radius=0.0,floorDepth=0.0,t=0.0;
|
||||
std::size_t segmentIndex=0;
|
||||
double centerZ=0.0,floorCenterZ=0.0,radius=0.0,floorDepth=0.0;
|
||||
double t=0.0,floorT=0.0;
|
||||
std::size_t segmentIndex=0,floorSegmentIndex=0;
|
||||
bool valid=false;
|
||||
const FScopedPassageTileQueries::FPassageSweepSample* swept=nullptr;
|
||||
};
|
||||
@@ -1255,6 +1258,8 @@ public:
|
||||
nearest.floorCenterZ=swept->floorCenterZSubvoxels;
|
||||
nearest.radius=swept->radiusSubvoxels;
|
||||
nearest.floorDepth=swept->floorDepthSubvoxels;
|
||||
nearest.floorSegmentIndex=swept->floorSegmentIndex;
|
||||
nearest.floorT=swept->floorT;
|
||||
nearest.valid=true;
|
||||
nearest.swept=swept;
|
||||
}
|
||||
@@ -1273,6 +1278,7 @@ public:
|
||||
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(
|
||||
PassageSegmentData[passageIndex].size())-1;
|
||||
double bestDistance=std::numeric_limits<double>::infinity();
|
||||
double bestFloorCenterDistanceSq=std::numeric_limits<double>::infinity();
|
||||
const auto visitSegment=[&](std::size_t segmentIndex,int)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageCarveSegmentVisits;
|
||||
@@ -1289,6 +1295,7 @@ 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]
|
||||
+(qy-segment.a[1])*segment.delta[1])
|
||||
@@ -1300,24 +1307,43 @@ public:
|
||||
constants,segmentIndex,floorT,segment,qx,qy,floorDepth,qPerMetre);
|
||||
const double flatDistance=PassageFlatBottomDistance(
|
||||
static_cast<float>(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre);
|
||||
if(flatDistance<bestDistance
|
||||
||(flatDistance==bestDistance
|
||||
// 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.
|
||||
if(floorCenterDistanceSq<bestFloorCenterDistanceSq
|
||||
||(floorCenterDistanceSq==bestFloorCenterDistanceSq
|
||||
&&segmentIndex<nearest.floorSegmentIndex))
|
||||
{
|
||||
bestFloorCenterDistanceSq=floorCenterDistanceSq;
|
||||
nearest.floorCenterZ=floorCenterZ;
|
||||
nearest.floorDepth=floorDepth;
|
||||
nearest.floorSegmentIndex=segmentIndex;
|
||||
nearest.floorT=floorT;
|
||||
}
|
||||
// Select the tube segment before applying its floor plane. Taking the
|
||||
// minimum of each segment's flattened distance lets a lower, downhill
|
||||
// floor plane extend backward through the overlapping tube and makes
|
||||
// the floor jump at the prepared-segment spacing.
|
||||
if(roundDistance<bestDistance
|
||||
||(roundDistance==bestDistance
|
||||
&&(!nearest.valid||segmentIndex<nearest.segmentIndex)))
|
||||
{
|
||||
bestDistance=flatDistance;
|
||||
bestDistance=roundDistance;
|
||||
nearest.signedDistance=flatDistance;
|
||||
nearest.roundSignedDistance=roundDistance;
|
||||
nearest.segmentIndex=segmentIndex;
|
||||
nearest.t=t;
|
||||
nearest.centerZ=cz;
|
||||
nearest.floorCenterZ=floorCenterZ;
|
||||
nearest.radius=radius;
|
||||
nearest.floorDepth=floorDepth;
|
||||
nearest.valid=true;
|
||||
}
|
||||
};
|
||||
VisitNearestPassageSegmentRange(passageIndex,0,lastIndex-1,
|
||||
qx,qy,qz,0,bestDistance,visitSegment);
|
||||
qx,qy,qz,0,bestDistance,bestFloorCenterDistanceSq,visitSegment);
|
||||
if(nearest.valid)
|
||||
nearest.signedDistance=PassageFlatBottomDistance(
|
||||
static_cast<float>(nearest.roundSignedDistance),
|
||||
nearest.floorCenterZ,qz,nearest.floorDepth,qPerMetre);
|
||||
|
||||
// Shells form a union over nearby route segments, even when another
|
||||
// segment supplies the nearest interior distance. The hierarchy bound
|
||||
@@ -1388,8 +1414,10 @@ public:
|
||||
nearest.shellSignedDistance=std::min(nearest.shellSignedDistance,
|
||||
localShellDistance);
|
||||
};
|
||||
double shellFloorSearchBoundSq=-1.0;
|
||||
VisitNearestPassageSegmentRange(passageIndex,0,lastIndex-1,
|
||||
qx,qy,qz,0,shellSearchBound,visitShellSegment);
|
||||
qx,qy,qz,0,shellSearchBound,shellFloorSearchBoundSq,
|
||||
visitShellSegment);
|
||||
}
|
||||
if(!nearest.valid)continue;
|
||||
const double wallElevationMetres=(qz-(nearest.floorCenterZ
|
||||
@@ -1420,6 +1448,7 @@ public:
|
||||
static_cast<float>(roughRoundDistance),nearest.floorCenterZ,qz,
|
||||
nearest.floorDepth,qPerMetre);
|
||||
const auto& segment=PassageSegmentData[passageIndex][nearest.segmentIndex];
|
||||
const auto& floorSegment=PassageSegmentData[passageIndex][nearest.floorSegmentIndex];
|
||||
const double alongMetres=segment.authoredAlongMetres
|
||||
+nearest.t*segment.authoredLengthMetres;
|
||||
const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres,
|
||||
@@ -1437,9 +1466,11 @@ public:
|
||||
const double mouthWallThickness=segment.routeSegment
|
||||
?smoothShellFade*shellThickness:0.0;
|
||||
double floorExtensionFade=1.0;
|
||||
if(!segment.routeSegment)
|
||||
if(!floorSegment.routeSegment)
|
||||
{
|
||||
const double pastMouthMetres=std::max(0.0,alongMetres
|
||||
const double floorAlongMetres=floorSegment.authoredAlongMetres
|
||||
+nearest.floorT*floorSegment.authoredLengthMetres;
|
||||
const double pastMouthMetres=std::max(0.0,floorAlongMetres
|
||||
-constants.totalPathLengthMetres);
|
||||
const double extensionFade=std::clamp(1.0
|
||||
-pastMouthMetres/MouthShellBlendMetres,0.0,1.0);
|
||||
@@ -1461,31 +1492,22 @@ public:
|
||||
}
|
||||
|
||||
if(!std::isfinite(unionDistance))return density;
|
||||
// The union's interior is unconditional air. Its one exterior shell is
|
||||
// solid only in air, so it leaves host rock unchanged. Taking the union
|
||||
// before writing either polarity makes every tunnel interior beat every
|
||||
// overlapping tunnel shell.
|
||||
// Preserve both native graph air and passage interior before any shell
|
||||
// can change their sign. If they overlap, their positive air fields combine.
|
||||
const float passageAirDistance=static_cast<float>(-unionDistance/Q);
|
||||
// Air is the union of the native graph and passage voids. Keeping the
|
||||
// signed passage distance outside the void as well gives marching cubes
|
||||
// a real negative distance through the floor instead of the terrain's
|
||||
// saturated deep-rock value.
|
||||
if(nativeTunnelFieldDensity>=0.0f)
|
||||
{
|
||||
const float passageAir=unionDistance<=0.0
|
||||
?static_cast<float>(-unionDistance/Q)
|
||||
:std::numeric_limits<float>::lowest();
|
||||
return std::max(nativeTunnelFieldDensity,passageAir);
|
||||
}
|
||||
return std::max(nativeTunnelFieldDensity,passageAirDistance);
|
||||
if(nativeFloorSupport!=std::numeric_limits<float>::lowest()
|
||||
&&nativeFloorSupport<0.0f)
|
||||
return std::min(nativeFloorSupport,unionDistance<=0.0
|
||||
?static_cast<float>(-unionDistance/Q):density);
|
||||
?passageAirDistance:density);
|
||||
if(nativeTunnelFieldDensity!=std::numeric_limits<float>::lowest())
|
||||
return unionDistance<=0.0
|
||||
?static_cast<float>(-unionDistance/Q):density;
|
||||
if(unionDistance<=0.0)
|
||||
return static_cast<float>(-unionDistance/Q);
|
||||
if(unionShellDistance<0.0&&density>=0.0f)
|
||||
return std::max(density,passageAirDistance);
|
||||
if(unionDistance>0.0&&unionShellDistance<0.0&&density>=0.0f)
|
||||
return static_cast<float>(unionShellDistance/Q);
|
||||
return density;
|
||||
return std::max(density,passageAirDistance);
|
||||
}
|
||||
|
||||
float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const
|
||||
@@ -4303,7 +4325,8 @@ 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,FVisitor&& visitor) const
|
||||
int priority,double& bestDistance,double& bestCenterDistanceSq,
|
||||
FVisitor&& visitor) const
|
||||
{
|
||||
(void)priority;
|
||||
if(first>last||passageIndex>=PassageNearestNodes.size()
|
||||
@@ -4322,13 +4345,22 @@ private:
|
||||
return DeterministicSqrt(dx*dx+dy*dy+dz*dz)-node.maximumRadius
|
||||
-SmoothDistanceSlackMetres*qPerMetre;
|
||||
};
|
||||
const auto centerDistanceLowerBoundSq=[&](const FPassageNearestNode& node)
|
||||
{
|
||||
const double dx=std::max({node.minimum[0]-qx,0.0,qx-node.maximum[0]});
|
||||
const double dy=std::max({node.minimum[1]-qy,0.0,qy-node.maximum[1]});
|
||||
const double dz=std::max({node.minimum[2]-qz,0.0,qz-node.maximum[2]});
|
||||
return dx*dx+dy*dy+dz*dz;
|
||||
};
|
||||
const auto visit=[&](auto&& self,int nodeIndex)->void
|
||||
{
|
||||
if(nodeIndex<0)return;
|
||||
const auto& node=nodes[static_cast<std::size_t>(nodeIndex)];
|
||||
if(static_cast<std::ptrdiff_t>(node.maximumSegment)<first
|
||||
||static_cast<std::ptrdiff_t>(node.minimumSegment)>last
|
||||
||lowerBound(node)>bestDistance)return;
|
||||
||(lowerBound(node)>bestDistance
|
||||
&&(bestCenterDistanceSq<0.0
|
||||
||centerDistanceLowerBoundSq(node)>bestCenterDistanceSq)))return;
|
||||
if(node.left<0)
|
||||
{
|
||||
for(const std::size_t segment:node.segments)
|
||||
@@ -11430,6 +11462,7 @@ 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;
|
||||
@@ -11450,6 +11483,17 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
||||
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gy))
|
||||
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gx);
|
||||
auto& sample=passageSamples.samples[sampleIndex];
|
||||
if(floorCenterDistanceSq<sample.floorCenterDistanceSq
|
||||
||(floorCenterDistanceSq==sample.floorCenterDistanceSq
|
||||
&&candidate.segmentIndex<sample.floorSegmentIndex))
|
||||
{
|
||||
sample.floorCenterDistanceSq=floorCenterDistanceSq;
|
||||
sample.floorCenterZSubvoxels=floorCenterZ;
|
||||
sample.floorDepthSubvoxels=floorDepth;
|
||||
sample.floorT=floorT;
|
||||
sample.floorSegmentIndex=static_cast<std::uint32_t>(
|
||||
candidate.segmentIndex);
|
||||
}
|
||||
const double alongMetres=segment.authoredAlongMetres
|
||||
+t*segment.authoredLengthMetres;
|
||||
const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres,
|
||||
@@ -11488,16 +11532,16 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
||||
floorShellDistance);
|
||||
sample.shellSignedDistance=std::min(sample.shellSignedDistance,
|
||||
localShellDistance);
|
||||
if(!sample.valid||flatDistance<sample.signedDistance
|
||||
||(flatDistance==sample.signedDistance
|
||||
// Rank the tube body by round SDF. Its floor plane is selected
|
||||
// independently above from the nearest centerline position.
|
||||
if(!sample.valid||signedDistance<sample.roundSignedDistance
|
||||
||(signedDistance==sample.roundSignedDistance
|
||||
&&candidate.segmentIndex<sample.segmentIndex))
|
||||
{
|
||||
sample.signedDistance=flatDistance;
|
||||
sample.roundSignedDistance=signedDistance;
|
||||
sample.centerZSubvoxels=cz;
|
||||
sample.floorCenterZSubvoxels=floorCenterZ;
|
||||
sample.radiusSubvoxels=radius;
|
||||
sample.floorDepthSubvoxels=floorDepth;
|
||||
sample.t=t;
|
||||
sample.segmentIndex=static_cast<std::uint32_t>(candidate.segmentIndex);
|
||||
sample.valid=true;
|
||||
@@ -11532,6 +11576,9 @@ 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);
|
||||
sample.signedDistance=FStandaloneShapeWorld::PassageFlatBottomDistance(
|
||||
static_cast<float>(sample.roundSignedDistance),
|
||||
sample.floorCenterZSubvoxels,qz,sample.floorDepthSubvoxels,qPerMetre);
|
||||
const double radius=sample.radiusSubvoxels;
|
||||
const double centerZ=sample.centerZSubvoxels;
|
||||
if(sample.signedDistance<=sweep->influenceSubvoxels)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Linux 114 — floor profile and speed
|
||||
|
||||
Evaluator version: **222**. Worktree remains on `wip/l111-halfpipe-spline`; no commit was made.
|
||||
|
||||
## Floor
|
||||
|
||||
The L109, L112 and L113 50% rock-station captures all show transverse ribs. In v222, tube-body selection uses round distance and the floor profile is selected separately by nearest centerline. The passage now supplies a signed metric distance outside its void instead of inheriting the host field's saturated deep-rock value.
|
||||
|
||||
At the L114 passage 1–2 rock-station column, the raw source value at the floor was saturated at `-256.160522 m`; the composed floor value was `-0.007221 m`. The vertical profile is nearly linear:
|
||||
|
||||
| Height from planned floor (m) | -0.50 | -0.25 | -0.10 | 0.00 | +0.10 | +0.25 | +0.50 |
|
||||
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||
| Density (m) | -0.5064 | -0.2568 | -0.1070 | -0.00722 | +0.0926 | +0.2423 | +0.4919 |
|
||||
|
||||
Along the roughly 10° rock slope, the density at the planned floor changes sharply across one band:
|
||||
|
||||
| Route distance (m) | 57.495 | 57.795 | 57.895 | 57.995 | 58.095 | 58.295 | 58.495 |
|
||||
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||
| Density at planned floor (m) | +0.00724 | +0.00726 | +0.00724 | -0.07260 | -0.05748 | -0.02614 | +0.00667 |
|
||||
| Zero-crossing offset (m) | -0.00722 | -0.00720 | -0.00721 | +0.07234 | +0.05730 | +0.02607 | -0.00665 |
|
||||
|
||||
Across 51 samples, the maximum offset is 5.9 cm on passage 0–1 and 7.3 cm on passage 1–2. The L114 rock-floor close-up shows a large reduction from L109/L112/L113, but faint transverse ridges remain. The floor is improved, not fully smooth yet. Full probes: `Saved/Linux/l114/audit-owner-clang.log`.
|
||||
|
||||
## Speed
|
||||
|
||||
Timing was deferred until load fell from 5.46 to 0.69; the runner's gate measured **0.64** immediately before the batch. It ran five interleaved L80 samples per cell across all four strates. Values are median seconds, `LOD0–2 near-ready / whole-view-ready`, ordered L109 → L113 → L114.
|
||||
|
||||
| Strate | W8 near / whole | W12 near / whole |
|
||||
|---|---|---|
|
||||
| SW | 0.778 → 0.851 → 0.849 / 1.808 → 1.874 → 1.861 | 0.802 → 0.877 → 0.936 / 1.751 → 1.872 → 1.912 |
|
||||
| FI | 1.177 → 1.271 → 1.380 / 2.201 → 2.255 → 2.450 | 0.946 → 1.122 → 1.102 / 1.816 → 2.012 → 1.993 |
|
||||
| TN | 1.112 → 1.201 → 1.211 / 1.977 → 2.109 → 2.166 | 0.959 → 1.090 → 1.074 / 1.786 → 1.925 → 1.918 |
|
||||
| VS | 1.019 → 1.149 → 1.156 / 2.065 → 2.308 → 2.263 | 0.950 → 1.074 → 1.098 / 1.963 → 2.131 → 2.166 |
|
||||
|
||||
L114 is 9–17% slower than L109 at near-ready and 3–11% at whole-view. L114 candidate totals match L113 and exceed L109: SW `3102 vs 583`, FI `5384 vs 1036`, TN `3701 vs 869`, VS `2197 vs 644` feature/route candidates across the profiled tiles. The L77 W8 census estimates median extra worker CPU of `0.71 s` at LOD0–2 and `0.88 s` whole-view across the four cameras. The largest added buckets are broad `other` work (`+0.72 / +0.60 s`) and passage work (`+0.21 / +0.40 s`); composition-bands work is flat or lower. L77 values are weighted sample estimates, not direct phase timers.
|
||||
|
||||
## Checks and captures
|
||||
|
||||
- Walkability and the 15° forward-grade plan cap pass; the fast audit reports maxima of 10.48° (SW) and 10.01° (FI).
|
||||
- Clang/GCC hashes match at W1/W8/W12 for all four strates and both two-strate recipes: 18 runs, source stamp `735079f…`.
|
||||
- Fast tier is **FAIL**: the SW owner audit reports 2 wall-shell violations in 31,381 probes. Both samples return the same positive host density (`+35.742 m`, `+35.578 m`) where the shell audit expects solid. This remains unresolved after the two floor geometry designs.
|
||||
- Linux `VFHostEditor` build succeeded in 80.6 s with `-NoUBA -MaxParallelActions=4`; Unreal was not run under `prlimit`.
|
||||
- The [capture index](captures/INDEX.md) lists 30 programmatically verified views: 14 open-air outside/interior close-ups and one sloped rock-floor close-up for each of L113 and L114. Four low-contrast open-air images are flagged as geometry-visible. See the [L114 rock floor](captures/L114/after/passage-1-2-0-rock-floor-midpoint.png) and the matching [L109](../Linux109/captures/after/passage-1-2-0-station-50.png), [L112](../Linux112/captures/after/passage-1-2-0-station-50.png) and [L113](../Linux113/captures/after/passage-1-2-0-station-50.png) station captures.
|
||||
|
||||
Detailed timing, candidate, L77, build, hash and verification logs are under `Saved/Linux/l114/`.
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare per-tile passage candidate-list sizes for L109, L113, and L114."""
|
||||
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/l114/candidate-lists"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"L109": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeCoreHeadless-l109-clang",
|
||||
"L113": ROOT / "Saved/Linux/l113/bin/VoxelForgeCoreHeadless-l113-clang",
|
||||
"L114": ROOT / "Saved/Linux/l114/bin/VoxelForgeCoreHeadless-l114-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("[L114CandidateLists] " + 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/L113/L114 candidate-list input")
|
||||
rows = [run(strate, version) for strate in range(4)
|
||||
for version in ("L109", "L113", "L114")]
|
||||
(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,252 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Copy verified L113 wall views and capture L113/L114 rock floors plus L114 walls."""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
RESEARCH = Path(__file__).resolve().parent
|
||||
CAPTURES = RESEARCH / "captures"
|
||||
SAVED = ROOT / "Saved/Linux/l114/captures"
|
||||
L113_RESEARCH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux113"
|
||||
L113_SAVED = ROOT / "Saved/Linux/l113"
|
||||
L114_SAVED = ROOT / "Saved/Linux/l114"
|
||||
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
|
||||
|
||||
BINARIES = {
|
||||
"L113": L113_SAVED / "bin/VoxelForgeCoreHeadless-l113-clang",
|
||||
"L114": L114_SAVED / "bin/VoxelForgeCoreHeadless-l114-clang",
|
||||
}
|
||||
RENDERERS = {
|
||||
"L113": L113_SAVED / "bin/VoxelForgeOffscreenCapture-l113-clang",
|
||||
"L114": L114_SAVED / "bin/VoxelForgeOffscreenCapture-l114-clang",
|
||||
}
|
||||
|
||||
|
||||
def run_quality(image: Path, log: Path) -> str:
|
||||
log.parent.mkdir(parents=True, exist_ok=True)
|
||||
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"timeout", "1740s", "python3", "-B",
|
||||
str(ROOT / "Standalone/Tools/verify/capture_quality.py"), str(image)]
|
||||
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")
|
||||
# L113 already admitted low-contrast views when geometry covered at least
|
||||
# 0.1% of pixels; use the same visibility rule for its copied references.
|
||||
# The caller still rejects decode failures because they have no metrics.
|
||||
if result.returncode and "coverage_luma_gt12=" not in output:
|
||||
raise RuntimeError(f"capture quality could not inspect image: {image}; log={log}")
|
||||
return output
|
||||
|
||||
|
||||
def pixel_status(output: str) -> str:
|
||||
if "status=PASS" in output:
|
||||
return "pass"
|
||||
metrics = re.search(r"coverage_luma_gt12=([\d.]+)", output)
|
||||
if metrics and float(metrics.group(1)) >= 0.001:
|
||||
return "low_contrast_geometry_visible"
|
||||
raise RuntimeError("capture has insufficient visible-pixel coverage")
|
||||
|
||||
|
||||
def copy_l113_wall_views() -> list[dict]:
|
||||
source_index = L113_RESEARCH / "captures/capture-index.json"
|
||||
if not source_index.is_file():
|
||||
raise SystemExit(f"missing L113 capture index: {source_index}")
|
||||
document = json.loads(source_index.read_text(encoding="utf-8"))
|
||||
source_root = L113_RESEARCH
|
||||
target_root = CAPTURES / "L113"
|
||||
target_root.mkdir(parents=True, exist_ok=True)
|
||||
rows = []
|
||||
for source_row in document.get("captures", []):
|
||||
if source_row.get("variant") != "after" or source_row.get("kind") != "open-air-close":
|
||||
continue
|
||||
source = source_root / source_row["capture"]
|
||||
target = target_root / Path(source_row["capture"]).name
|
||||
shutil.copy2(source, target)
|
||||
source_hash = hashlib.sha256(source.read_bytes()).hexdigest()
|
||||
target_hash = hashlib.sha256(target.read_bytes()).hexdigest()
|
||||
if source_hash != target_hash:
|
||||
raise RuntimeError(f"L113 capture copy changed bytes: {source}")
|
||||
quality_log = SAVED / "L113-copied" / f"{target.stem}-quality.log"
|
||||
quality = run_quality(target, quality_log)
|
||||
row = dict(source_row)
|
||||
row.update({"variant": "L113", "capture": str(target.relative_to(RESEARCH)),
|
||||
"source_capture": str(source.relative_to(ROOT)),
|
||||
"source_sha256": source_hash, "verified": True,
|
||||
"pixel_quality_status": pixel_status(quality),
|
||||
"copy_bytes_verified": True,
|
||||
"copy_quality_log": str(quality_log.relative_to(ROOT))})
|
||||
if (row.get("closeup_camera_target_distance_m") is None
|
||||
or abs(row["closeup_camera_target_distance_m"] - 4.0) > 0.02
|
||||
or row.get("closeup_target_surface_distance_m", 99.0) > 5.0
|
||||
or row.get("closeup_wall_normal_alignment", -1.0) < 0.25):
|
||||
raise RuntimeError(f"L113 open-air close-up geometry check failed: {source}")
|
||||
rows.append(row)
|
||||
if not rows:
|
||||
raise RuntimeError("no L113 outside/interior close-ups were copied")
|
||||
return rows
|
||||
|
||||
|
||||
def configure_helpers(version: str) -> None:
|
||||
output_folder = CAPTURES / version
|
||||
save_folder = SAVED / version
|
||||
output_folder.mkdir(parents=True, exist_ok=True)
|
||||
save_folder.mkdir(parents=True, exist_ok=True)
|
||||
HELPERS.CAPTURES = output_folder
|
||||
HELPERS.SAVED = save_folder
|
||||
HELPERS.BINARIES = {"after": BINARIES[version]}
|
||||
HELPERS.RENDERERS = {"after": RENDERERS[version]}
|
||||
HELPERS.RESEARCH = RESEARCH
|
||||
|
||||
|
||||
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 HELPERS.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 make_rock_floor_view(layout: dict, passage_id: str, version: str) -> dict:
|
||||
passage = next(item for item in layout["passages"]
|
||||
if item["stable_id"] == passage_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)
|
||||
station = prefix[-1] * 0.5
|
||||
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_deg = math.degrees(math.atan2(abs(tangent[2]),
|
||||
math.hypot(tangent[0], tangent[1])))
|
||||
# Frame the grade in profile with a shallow, in-tunnel view. The close
|
||||
# overhead angle hid the floor in a nearly uniform patch of rock.
|
||||
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_id)
|
||||
return {"name": f"{safe_id}-rock-floor-midpoint",
|
||||
"passage": passage_id, "kind": "open-air",
|
||||
"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], "closeup_side": "upward-floor",
|
||||
"planned_floor_m": planned_floor[2], "planned_grade_deg": grade_deg,
|
||||
"version": version}
|
||||
|
||||
|
||||
def capture_rock_floor(layout: dict, version: str, passage_id: str) -> dict:
|
||||
configure_helpers(version)
|
||||
view = make_rock_floor_view(layout, passage_id, version)
|
||||
row = HELPERS.capture("after", view, layout)
|
||||
scene = ROOT / row["scene"]
|
||||
floor_anchor = HELPERS.nearest_surface_anchor(scene, view["target"], margin_m=5.0,
|
||||
preferred_normal=(0.0, 0.0, 1.0), minimum_alignment=0.85)
|
||||
floor_normal_alignment = HELPERS.dot(floor_anchor["normal"], (0.0, 0.0, 1.0))
|
||||
camera_to_floor = math.dist(row["camera_actual_m"], floor_anchor["position_m"])
|
||||
camera_to_target = math.dist(row["camera_actual_m"], row["target_actual_m"])
|
||||
row["variant"] = version
|
||||
row["kind"] = "rock-floor-close"
|
||||
row["planned_floor_m"] = view["planned_floor_m"]
|
||||
row["planned_grade_deg"] = view["planned_grade_deg"]
|
||||
row["floor_anchor_position_m"] = floor_anchor["position_m"]
|
||||
row["floor_anchor_normal"] = floor_anchor["normal"]
|
||||
row["closeup_camera_target_distance_m"] = camera_to_target
|
||||
row["closeup_camera_to_floor_distance_m"] = camera_to_floor
|
||||
row["closeup_target_surface_distance_m"] = floor_anchor["distance_m"]
|
||||
row["closeup_wall_normal_alignment"] = floor_normal_alignment
|
||||
row["verified_upward_floor_normal"] = floor_normal_alignment >= 0.85
|
||||
if (not row["verified"]
|
||||
or not 2.0 <= camera_to_floor <= 3.2
|
||||
or row["closeup_target_surface_distance_m"] > 0.25
|
||||
or not row["verified_upward_floor_normal"]
|
||||
or row["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"{version} rock-floor close-up failed geometry/pixel checks")
|
||||
return row
|
||||
|
||||
|
||||
def capture_l114_open_air(layout: dict, sections: list[dict]) -> list[dict]:
|
||||
configure_helpers("L114")
|
||||
views = HELPERS.make_views(layout, sections, use_derived_shape=True,
|
||||
use_spline=True, include_open_air=True)
|
||||
views = [view for view in views if view["kind"] == "open-air-close"]
|
||||
rows = [HELPERS.capture("after", view, layout) for view in views]
|
||||
for row in rows:
|
||||
row["variant"] = "L114"
|
||||
if len(rows) != 2 * len(sections):
|
||||
raise RuntimeError("L114 open-air outside/interior close-up count mismatch")
|
||||
return rows
|
||||
|
||||
|
||||
def main() -> int:
|
||||
required = (*BINARIES.values(), *RENDERERS.values(),
|
||||
L113_SAVED / "audit-layout-candidate.json",
|
||||
L114_SAVED / "audit-layout-candidate.json",
|
||||
L114_SAVED / "audit-owner-clang.log")
|
||||
if any(not path.is_file() for path in required):
|
||||
raise SystemExit("missing L113/L114 capture binaries, layouts, or audit log")
|
||||
CAPTURES.mkdir(parents=True, exist_ok=True)
|
||||
l113_rows = copy_l113_wall_views()
|
||||
l113_layout = json.loads((L113_SAVED / "audit-layout-candidate.json").read_text())
|
||||
l114_layout = json.loads((L114_SAVED / "audit-layout-candidate.json").read_text())
|
||||
l114_sections = parse_sections(L114_SAVED / "audit-owner-clang.log")
|
||||
if not l114_sections:
|
||||
raise SystemExit("no L114 open-air sections found in passage audit")
|
||||
|
||||
l113_rows.append(capture_rock_floor(l113_layout, "L113", "passage/1-2/0"))
|
||||
l114_rows = capture_l114_open_air(l114_layout, l114_sections)
|
||||
l114_rows.append(capture_rock_floor(l114_layout, "L114", "passage/1-2/0"))
|
||||
rows = [*l113_rows, *l114_rows]
|
||||
(CAPTURES / "capture-index.json").write_text(
|
||||
json.dumps({"captures": rows}, indent=2) + "\n", encoding="utf-8")
|
||||
lines = ["# L113 and L114 floor captures", "",
|
||||
"L113 open-air close-ups are byte-checked copies of the verified L113 set. "
|
||||
"L113 and L114 rock-floor views and L114 open-air close-ups were rendered "
|
||||
"from their respective meshes. Each image passed the pixel check. Rock-floor "
|
||||
"anchors require an upward normal alignment of at least 0.85, a floor offset "
|
||||
"of at most 0.25 m, and a camera distance between 2.0 and 3.2 m.", ""]
|
||||
for version in ("L113", "L114"):
|
||||
lines.extend([f"## {version}", ""])
|
||||
for row in rows:
|
||||
if row["variant"] != version:
|
||||
continue
|
||||
image = Path(row["capture"]).name
|
||||
lines.append(f"- [{row['station']}]({row['capture']}) — {row['kind']}; "
|
||||
f"{row.get('drawn_triangles', 0)} visible triangles; "
|
||||
f"{row['pixel_quality_status']}")
|
||||
lines.append("")
|
||||
(CAPTURES / "INDEX.md").write_text("\n".join(lines), encoding="utf-8")
|
||||
floor_rows = [row for row in rows if row["kind"] == "rock-floor-close"]
|
||||
if len(floor_rows) != 2 or not all(row["verified_upward_floor_normal"] for row in floor_rows):
|
||||
raise RuntimeError("L113/L114 rock-floor capture verification failed")
|
||||
print(f"[L114CaptureSummary] captures={len(rows)} L113={len(l113_rows)} "
|
||||
f"L114={len(l114_rows)} rock_floor_verified={len(floor_rows)}", flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Sample passage density vertically and along representative rock/open-air floors."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l114"
|
||||
BINARY = ROOT / "Saved/Linux/l114/bin/VoxelForgeCoreHeadless-l114-clang"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
log = OUT / "audit-owner-clang.log"
|
||||
layout = OUT / "audit-layout-candidate.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_L114_DENSITY_PROFILE": "1"},
|
||||
stdout=stream, stderr=subprocess.STDOUT,
|
||||
check=False, timeout=1740)
|
||||
print(f"[L114DensityProbe] exit={result.returncode} 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 v222 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/l114/hashes"
|
||||
DATASETS = {
|
||||
"owner4": ROOT / "Standalone/data/owner_world.json",
|
||||
"owner2": ROOT / "Standalone/data/regression/owner_world_2strate_l61.json",
|
||||
}
|
||||
BINARIES = {
|
||||
"clang": ROOT / "Saved/Linux/l114/bin/VoxelForgeCoreHeadless-l114-clang",
|
||||
"gcc": ROOT / "Saved/Linux/l114/bin/VoxelForgeCoreHeadless-l114-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("[L114HashRun] " + 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 L114 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}-v222.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,140 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare L77 worker phase samples for L109 and L114 across four cameras."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l114/l77-census"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"L109": ROOT / "Saved/Linux/l109/bin-final-profile/VoxelForgeCoreHeadless-l109-candidate-profile",
|
||||
"L114": ROOT / "Saved/Linux/l114/bin-profile/VoxelForgeCoreHeadless-l114-clang-l77",
|
||||
}
|
||||
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),
|
||||
}
|
||||
WORKER = re.compile(r"^\[WorldAssemblerLODWorker\] (.*)$", re.M)
|
||||
SAMPLE = re.compile(r"^\[L77Sample\] (.*)$", re.M)
|
||||
SAMPLER = re.compile(r"^\[L77Sampler\] action=stop (.*)$", re.M)
|
||||
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||
|
||||
|
||||
def run(version: str, strate: int, replicate: int) -> dict:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
label = f"{version.lower()}-s{strate}-r{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",
|
||||
"--workers=8", "--clip-radius=3", "--max-clip-level=8", "--max-level=8",
|
||||
f"--camera={camera}", "--profile-lod-work", "--stream-scheduler-policy=3",
|
||||
"--linux77-profile",
|
||||
]
|
||||
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)
|
||||
content = log.read_text(encoding="utf-8", errors="replace")
|
||||
sampler_match = SAMPLER.search(content)
|
||||
worker_rows = [dict(FIELDS.findall(match.group(1))) for match in WORKER.finditer(content)]
|
||||
samples = [dict(FIELDS.findall(match.group(1))) for match in SAMPLE.finditer(content)]
|
||||
if result.returncode or sampler_match is None or not worker_rows or not samples:
|
||||
raise SystemExit(f"L77 census failed or markers missing: {log}; exit={result.returncode}")
|
||||
sampler = dict(FIELDS.findall(sampler_match.group(1)))
|
||||
if sampler.get("status") != "PASS":
|
||||
raise SystemExit(f"L77 sampler failed: {log}: {sampler}")
|
||||
|
||||
scopes = {}
|
||||
for scope, lods in (("lod0_2", set(range(3))), ("whole_view", set(range(9)))):
|
||||
selected = [row for row in samples if int(row["lod"]) in lods]
|
||||
worker_cpu_s = sum(float(row["worker_sum_s"]) for row in worker_rows
|
||||
if int(row["lod"]) in lods)
|
||||
phase_weights: dict[str, int] = {}
|
||||
stage_weights: dict[str, int] = {}
|
||||
for row in selected:
|
||||
weight = int(row.get("weight", "1"))
|
||||
phase = row.get("phase", "other")
|
||||
stage = row.get("stage", "other")
|
||||
phase_weights[phase] = phase_weights.get(phase, 0) + weight
|
||||
stage_weights[stage] = stage_weights.get(stage, 0) + weight
|
||||
total = sum(phase_weights.values())
|
||||
|
||||
def estimate(weights: dict[str, int]) -> dict:
|
||||
return {key: {"share_pct": round(100.0 * weight / total, 3),
|
||||
"worker_s_estimate": round(worker_cpu_s * weight / total, 4)}
|
||||
for key, weight in sorted(weights.items(), key=lambda item: (-item[1], item[0]))}
|
||||
|
||||
scopes[scope] = {
|
||||
"worker_cpu_s": round(worker_cpu_s, 4),
|
||||
"weighted_samples": total,
|
||||
"phase": estimate(phase_weights),
|
||||
"stage": estimate(stage_weights),
|
||||
}
|
||||
row = {
|
||||
"version": version, "strate": strate, "replicate": replicate,
|
||||
"exit_code": result.returncode, "sampler": sampler, "scopes": scopes,
|
||||
"log": str(log.relative_to(ROOT)),
|
||||
"runner_wall_s": round(time.monotonic() - started, 3),
|
||||
}
|
||||
print("[L114L77Run] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def summarize(rows: list[dict]) -> dict:
|
||||
summary = []
|
||||
for strate in range(4):
|
||||
for scope in ("lod0_2", "whole_view"):
|
||||
groups = {version: [row for row in rows if row["strate"] == strate
|
||||
and row["version"] == version]
|
||||
for version in ("L109", "L114")}
|
||||
phases = sorted({phase for group in groups.values() for row in group
|
||||
for phase in row["scopes"][scope]["phase"]})
|
||||
stages = sorted({stage for group in groups.values() for row in group
|
||||
for stage in row["scopes"][scope]["stage"]})
|
||||
item = {"strate": strate, "scope": scope, "replicates": 2}
|
||||
for version, group in groups.items():
|
||||
item[f"{version}_worker_cpu_s_median"] = statistics.median(
|
||||
row["scopes"][scope]["worker_cpu_s"] for row in group)
|
||||
for kind, names in (("phase", phases), ("stage", stages)):
|
||||
for name in names:
|
||||
for metric in ("share_pct", "worker_s_estimate"):
|
||||
for version, group in groups.items():
|
||||
values = [row["scopes"][scope][kind].get(name, {})
|
||||
.get(metric, 0.0) for row in group]
|
||||
item[f"{version}_{kind}_{name}_{metric}_median"] = \
|
||||
statistics.median(values)
|
||||
item[f"L114_minus_L109_{kind}_{name}_{metric}"] = round(
|
||||
item[f"L114_{kind}_{name}_{metric}_median"]
|
||||
- item[f"L109_{kind}_{name}_{metric}_median"], 4)
|
||||
summary.append(item)
|
||||
return {"runs": rows, "summary": summary}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if any(not path.is_file() for path in (*BINARIES.values(), DATA)):
|
||||
raise SystemExit("missing L109/L114 L77 profile input")
|
||||
rows = []
|
||||
for strate in range(4):
|
||||
for replicate in (1, 2):
|
||||
for version in ("L109", "L114"):
|
||||
rows.append(run(version, strate, replicate))
|
||||
(OUT / "runs-partial.json").write_text(
|
||||
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
|
||||
(OUT / "summary.json").write_text(
|
||||
json.dumps(summarize(rows), 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, L113, and L114."""
|
||||
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/l114/timing"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"L109": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeCoreHeadless-l109-clang",
|
||||
"L113": ROOT / "Saved/Linux/l113/bin/VoxelForgeCoreHeadless-l113-clang",
|
||||
"L114": ROOT / "Saved/Linux/l114/bin/VoxelForgeCoreHeadless-l114-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("[L114TimingRun] " + 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/L113/L114 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"[L114TimingGate] 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", "L113", "L114")
|
||||
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", "L113"):
|
||||
for key in ("lod02_ready_s", "whole_view_ready_s"):
|
||||
item[f"L114_vs_{comparison}_{key}_delta_pct"] = round(
|
||||
100.0 * (medians["L114"][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,39 @@
|
||||
# L113 and L114 floor captures
|
||||
|
||||
L113 open-air close-ups are byte-checked copies of the verified L113 set. L113 and L114 rock-floor views and L114 open-air close-ups were rendered from their respective meshes. Each image passed the pixel check. Rock-floor anchors require an upward normal alignment of at least 0.85, a floor offset of at most 0.25 m, and a camera distance between 2.0 and 3.2 m.
|
||||
|
||||
## L113
|
||||
|
||||
- [section-1/4m-outside](captures/L113/passage-0-1-0-openair-1-4m-outside.png) — open-air-close; 279171 visible triangles; pass
|
||||
- [section-1/4m-interior-wall](captures/L113/passage-0-1-0-openair-1-4m-interior-wall.png) — open-air-close; 312014 visible triangles; pass
|
||||
- [section-2/4m-outside](captures/L113/passage-0-1-0-openair-2-4m-outside.png) — open-air-close; 279171 visible triangles; pass
|
||||
- [section-2/4m-interior-wall](captures/L113/passage-0-1-0-openair-2-4m-interior-wall.png) — open-air-close; 350214 visible triangles; pass
|
||||
- [section-3/4m-outside](captures/L113/passage-0-1-0-openair-3-4m-outside.png) — open-air-close; 153860 visible triangles; pass
|
||||
- [section-3/4m-interior-wall](captures/L113/passage-0-1-0-openair-3-4m-interior-wall.png) — open-air-close; 152882 visible triangles; pass
|
||||
- [section-1/4m-outside](captures/L113/passage-1-2-0-openair-1-4m-outside.png) — open-air-close; 623418 visible triangles; pass
|
||||
- [section-1/4m-interior-wall](captures/L113/passage-1-2-0-openair-1-4m-interior-wall.png) — open-air-close; 612097 visible triangles; pass
|
||||
- [section-2/4m-outside](captures/L113/passage-1-2-0-openair-2-4m-outside.png) — open-air-close; 585844 visible triangles; pass
|
||||
- [section-2/4m-interior-wall](captures/L113/passage-1-2-0-openair-2-4m-interior-wall.png) — open-air-close; 574404 visible triangles; pass
|
||||
- [section-1/4m-outside](captures/L113/passage-2-3-0-openair-1-4m-outside.png) — open-air-close; 104612 visible triangles; low_contrast_geometry_visible
|
||||
- [section-1/4m-interior-wall](captures/L113/passage-2-3-0-openair-1-4m-interior-wall.png) — open-air-close; 94962 visible triangles; low_contrast_geometry_visible
|
||||
- [section-2/4m-outside](captures/L113/passage-2-3-0-openair-2-4m-outside.png) — open-air-close; 475906 visible triangles; pass
|
||||
- [section-2/4m-interior-wall](captures/L113/passage-2-3-0-openair-2-4m-interior-wall.png) — open-air-close; 486760 visible triangles; pass
|
||||
- [rock-floor/57.5m](captures/L113/after/passage-1-2-0-rock-floor-midpoint.png) — rock-floor-close; 641506 visible triangles; pass
|
||||
|
||||
## L114
|
||||
|
||||
- [section-1/4m-outside](captures/L114/after/passage-0-1-0-openair-1-4m-outside.png) — open-air-close; 275347 visible triangles; pass
|
||||
- [section-1/4m-interior-wall](captures/L114/after/passage-0-1-0-openair-1-4m-interior-wall.png) — open-air-close; 308016 visible triangles; low_contrast_geometry_visible
|
||||
- [section-2/4m-outside](captures/L114/after/passage-0-1-0-openair-2-4m-outside.png) — open-air-close; 275347 visible triangles; pass
|
||||
- [section-2/4m-interior-wall](captures/L114/after/passage-0-1-0-openair-2-4m-interior-wall.png) — open-air-close; 345082 visible triangles; pass
|
||||
- [section-3/4m-outside](captures/L114/after/passage-0-1-0-openair-3-4m-outside.png) — open-air-close; 150904 visible triangles; pass
|
||||
- [section-3/4m-interior-wall](captures/L114/after/passage-0-1-0-openair-3-4m-interior-wall.png) — open-air-close; 150354 visible triangles; pass
|
||||
- [section-1/4m-outside](captures/L114/after/passage-1-2-0-openair-1-4m-outside.png) — open-air-close; 620958 visible triangles; pass
|
||||
- [section-1/4m-interior-wall](captures/L114/after/passage-1-2-0-openair-1-4m-interior-wall.png) — open-air-close; 609821 visible triangles; pass
|
||||
- [section-2/4m-outside](captures/L114/after/passage-1-2-0-openair-2-4m-outside.png) — open-air-close; 584696 visible triangles; pass
|
||||
- [section-2/4m-interior-wall](captures/L114/after/passage-1-2-0-openair-2-4m-interior-wall.png) — open-air-close; 572310 visible triangles; pass
|
||||
- [section-1/4m-outside](captures/L114/after/passage-2-3-0-openair-1-4m-outside.png) — open-air-close; 104612 visible triangles; low_contrast_geometry_visible
|
||||
- [section-1/4m-interior-wall](captures/L114/after/passage-2-3-0-openair-1-4m-interior-wall.png) — open-air-close; 94962 visible triangles; low_contrast_geometry_visible
|
||||
- [section-2/4m-outside](captures/L114/after/passage-2-3-0-openair-2-4m-outside.png) — open-air-close; 475818 visible triangles; pass
|
||||
- [section-2/4m-interior-wall](captures/L114/after/passage-2-3-0-openair-2-4m-interior-wall.png) — open-air-close; 486490 visible triangles; pass
|
||||
- [rock-floor/57.5m](captures/L114/after/passage-1-2-0-rock-floor-midpoint.png) — rock-floor-close; 636304 visible triangles; pass
|
||||
|
After Width: | Height: | Size: 540 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 67 KiB |
@@ -3166,6 +3166,74 @@ 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')
|
||||
{
|
||||
for(const char* passageId:{"passage/0-1/0","passage/1-2/0"})
|
||||
{
|
||||
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& route=allPassageSmoothRoutes[passageIndex];
|
||||
const auto densityMetres=[&](double x,double y,double z)
|
||||
{
|
||||
return static_cast<double>(Shape.densityAt({x/coreMetres,
|
||||
y/coreMetres,z/coreMetres}))*coreMetres;
|
||||
};
|
||||
const auto floorRoot=[&](const FPassageAuditRoutePoint& pose)
|
||||
{
|
||||
double low=pose.floorZ-0.75,high=pose.floorZ+0.75;
|
||||
double lowDensity=densityMetres(pose.x,pose.y,low);
|
||||
double highDensity=densityMetres(pose.x,pose.y,high);
|
||||
if(!(lowDensity<0.0&&highDensity>=0.0))
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
for(int iteration=0;iteration<24;++iteration)
|
||||
{
|
||||
const double middle=(low+high)*0.5;
|
||||
if(densityMetres(pose.x,pose.y,middle)<0.0)low=middle;
|
||||
else high=middle;
|
||||
}
|
||||
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)
|
||||
{
|
||||
if(route.size()<2||targetDistance>route.back().distance)continue;
|
||||
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,
|
||||
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,
|
||||
center.floorZ+offset));
|
||||
}
|
||||
for(int sample=-25;sample<=25;++sample)
|
||||
{
|
||||
const double distance=targetDistance+sample*0.10;
|
||||
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,
|
||||
root-pose.floorZ,
|
||||
densityMetres(pose.x,pose.y,pose.floorZ-0.1),
|
||||
densityMetres(pose.x,pose.y,pose.floorZ),
|
||||
densityMetres(pose.x,pose.y,pose.floorZ+0.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto passageInteriorWinsAt=[&](double xMetres,double yMetres,
|
||||
double zMetres,std::size_t ignoredPassage=std::numeric_limits<std::size_t>::max(),
|
||||
std::size_t ignoredSegment=std::numeric_limits<std::size_t>::max())
|
||||
@@ -3292,6 +3360,8 @@ namespace StandaloneStreamBenchmark
|
||||
std::vector<std::array<double,6>> floorPeakProfile;
|
||||
double floorPeakWindowStartMetres=0.0;
|
||||
std::uint64_t samples=0,expectedSamples=0;
|
||||
std::uint64_t wallWindowSamples=0,wallWindowExpectedSamples=0;
|
||||
std::uint64_t wallQualifiedWindows=0;
|
||||
std::uint64_t floorSamples=0,floorExpectedSamples=0;
|
||||
std::uint64_t floorInteriorWins=0;
|
||||
bool controlDetected=false,passed=false;
|
||||
@@ -3836,6 +3906,9 @@ namespace StandaloneStreamBenchmark
|
||||
const double start=(total-WindowLengthMetres)*fraction;
|
||||
std::vector<double> radii;
|
||||
radii.reserve(static_cast<std::size_t>(WindowLengthMetres/SampleSpacingMetres)+1);
|
||||
std::size_t wallProbeNonfinite=0,wallProbeOutOfBand=0;
|
||||
double wallProbeMinimum=std::numeric_limits<double>::infinity();
|
||||
double wallProbeMaximum=-std::numeric_limits<double>::infinity();
|
||||
std::vector<std::array<double,2>> floorResiduals;
|
||||
floorResiduals.reserve(static_cast<std::size_t>(WindowLengthMetres/
|
||||
SampleSpacingMetres)+1);
|
||||
@@ -3922,8 +3995,12 @@ namespace StandaloneStreamBenchmark
|
||||
{x0+nx*offset,y0+ny*offset,z0}))*coreMetres;
|
||||
const double right=static_cast<double>(Field.densityAt(
|
||||
{x0-nx*offset,y0-ny*offset,z0}))*coreMetres;
|
||||
if(!std::isfinite(left)||!std::isfinite(right)
|
||||
||left< -0.75||left>0.75||right< -0.75||right>0.75)continue;
|
||||
if(!std::isfinite(left)||!std::isfinite(right))
|
||||
{++wallProbeNonfinite;continue;}
|
||||
wallProbeMinimum=std::min({wallProbeMinimum,left,right});
|
||||
wallProbeMaximum=std::max({wallProbeMaximum,left,right});
|
||||
if(left< -0.75||left>0.75||right< -0.75||right>0.75)
|
||||
{++wallProbeOutOfBand;continue;}
|
||||
// The probes sit 0.18 m inside the authored radius. Add that
|
||||
// radius back to the signed wall distance so radius-carried ribs
|
||||
// remain visible; the cubic detrend removes the smooth mouth flare.
|
||||
@@ -3931,7 +4008,22 @@ namespace StandaloneStreamBenchmark
|
||||
++audit.samples;
|
||||
}
|
||||
if(radii.size()<static_cast<std::size_t>(WindowLengthMetres/
|
||||
SampleSpacingMetres)*3/4)continue;
|
||||
SampleSpacingMetres)*3/4)
|
||||
{
|
||||
std::printf("[PassageWallRippleCoverage] id=%s start_m=%.3f "
|
||||
"valid=%zu expected=%zu nonfinite=%zu out_of_band=%zu "
|
||||
"probe_range_m=%.4f..%.4f status=UNDERCOVERED\n",
|
||||
passage.stableId.c_str(),start,radii.size(),
|
||||
static_cast<std::size_t>(WindowLengthMetres/
|
||||
SampleSpacingMetres)+1,
|
||||
wallProbeNonfinite,wallProbeOutOfBand,
|
||||
wallProbeMinimum,wallProbeMaximum);
|
||||
continue;
|
||||
}
|
||||
++audit.wallQualifiedWindows;
|
||||
audit.wallWindowSamples+=radii.size();
|
||||
audit.wallWindowExpectedSamples+=static_cast<std::size_t>(
|
||||
WindowLengthMetres/SampleSpacingMetres)+1;
|
||||
std::vector<std::array<double,2>> windowSamples;
|
||||
appendDetrendedProfile(radii,start,windowSamples);
|
||||
double localPeriod=0.0;
|
||||
@@ -3988,14 +4080,20 @@ namespace StandaloneStreamBenchmark
|
||||
audit.floorCoherentAmplitudeMetres=sinusoidAmplitude(coherentFloorSamples,
|
||||
audit.floorCoherentPeriodMetres);
|
||||
audit.amplitudeMetres=audit.localPeakAmplitudeMetres;
|
||||
const double coverage=audit.expectedSamples>0
|
||||
?static_cast<double>(audit.samples)/audit.expectedSamples:0.0;
|
||||
const double wallWindowCoverage=audit.wallWindowExpectedSamples>0
|
||||
?static_cast<double>(audit.wallWindowSamples)
|
||||
/audit.wallWindowExpectedSamples:0.0;
|
||||
const double floorCoverage=audit.floorExpectedSamples>0
|
||||
?static_cast<double>(audit.floorSamples)/audit.floorExpectedSamples:0.0;
|
||||
audit.floorOverlapOnly=audit.expectedSamples>0
|
||||
&&audit.floorExpectedSamples==0
|
||||
&&audit.floorInteriorWins==audit.expectedSamples;
|
||||
audit.passed=audit.controlDetected&&coverage>=0.75
|
||||
// Long passages can include open-air spans where the side-wall
|
||||
// probe has no zero crossing. Judge ripple only in windows with
|
||||
// enough measured wall samples; naturalness separately requires
|
||||
// visible relief on the route as a whole.
|
||||
audit.passed=audit.controlDetected&&audit.wallQualifiedWindows>0
|
||||
&&wallWindowCoverage>=0.75
|
||||
&&audit.localPeakAmplitudeMetres<RippleAmplitudeLimitMetres
|
||||
&&audit.floorControlDetected&&audit.floorStepControlDetected
|
||||
&&(audit.floorOverlapOnly||(floorCoverage>=0.75
|
||||
@@ -4380,7 +4478,7 @@ namespace StandaloneStreamBenchmark
|
||||
mouthProfileAudit.wallFlatStepRunMetres,mouthProfileAudit.wallStepMetres,
|
||||
PassageProfileSpacingMetres,PassageProfilePlateauMinimumMetres,
|
||||
PassageProfileStepThresholdMetres,mouthProfileAudit.passed?"PASS":"FAIL");
|
||||
std::printf("[PassageRippleAudit] id=%s window_m=32.000 spacing_m=0.125 period_m=%.3f amplitude_m=%.4f amplitude_cm=%.2f coherent_period_m=%.3f coherent_amplitude_m=%.4f coherent_amplitude_cm=%.2f local_peak_amplitude_m=%.4f coverage=%llu/%llu threshold_m=0.035 synthetic_spheres_period_m=1.500 synthetic_spheres_amplitude_m=%.4f synthetic_spheres_detected=%s floor_period_m=%.3f floor_amplitude_m=%.4f floor_amplitude_cm=%.2f floor_coherent_period_m=%.3f floor_coherent_amplitude_m=%.4f floor_coverage=%llu/%llu floor_overlap_skips=%llu floor_mode=%s floor_threshold_m=0.025 floor_beaded_control_amplitude_m=%.4f floor_beaded_control_detected=%s floor_step_control_amplitude_m=%.4f floor_step_control_detected=%s status=%s\n",
|
||||
std::printf("[PassageRippleAudit] id=%s window_m=32.000 spacing_m=0.125 period_m=%.3f amplitude_m=%.4f amplitude_cm=%.2f coherent_period_m=%.3f coherent_amplitude_m=%.4f coherent_amplitude_cm=%.2f local_peak_amplitude_m=%.4f coverage=%llu/%llu wall_windows=%llu/5 wall_window_coverage=%llu/%llu threshold_m=0.035 synthetic_spheres_period_m=1.500 synthetic_spheres_amplitude_m=%.4f synthetic_spheres_detected=%s floor_period_m=%.3f floor_amplitude_m=%.4f floor_amplitude_cm=%.2f floor_coherent_period_m=%.3f floor_coherent_amplitude_m=%.4f floor_coverage=%llu/%llu floor_overlap_skips=%llu floor_mode=%s floor_threshold_m=0.025 floor_beaded_control_amplitude_m=%.4f floor_beaded_control_detected=%s floor_step_control_amplitude_m=%.4f floor_step_control_detected=%s status=%s\n",
|
||||
passage.stableId.c_str(),rippleAudit.periodMetres,
|
||||
rippleAudit.amplitudeMetres,rippleAudit.amplitudeMetres*100.0,
|
||||
rippleAudit.coherentPeriodMetres,rippleAudit.coherentAmplitudeMetres,
|
||||
@@ -4388,6 +4486,9 @@ namespace StandaloneStreamBenchmark
|
||||
rippleAudit.localPeakAmplitudeMetres,
|
||||
static_cast<unsigned long long>(rippleAudit.samples),
|
||||
static_cast<unsigned long long>(rippleAudit.expectedSamples),
|
||||
static_cast<unsigned long long>(rippleAudit.wallQualifiedWindows),
|
||||
static_cast<unsigned long long>(rippleAudit.wallWindowSamples),
|
||||
static_cast<unsigned long long>(rippleAudit.wallWindowExpectedSamples),
|
||||
rippleAudit.controlAmplitudeMetres,rippleAudit.controlDetected?"true":"false",
|
||||
rippleAudit.floorPeriodMetres,rippleAudit.floorAmplitudeMetres,
|
||||
rippleAudit.floorAmplitudeMetres*100.0,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[PipelineFullDetailHash] tiles=960 density=0x91931a57a067fd92 mesh=0xa818654be89790ac payload=0x370ff80451105a39 topology=0x06e507557aa70173
|
||||
[PipelineWholeViewHash] tiles=2370 density=0x6b7a252b16f4fb54 mesh=0x912d124579189efa payload=0x8abe8d1270377503 topology=0x19a5fe8c2962598c
|
||||
[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=0xdad28a7fa1b9766b mesh=0xf5263c751cc5af4f payload=0xb882e4c989d07b53 topology=0x874e27ae4e73ec39
|
||||
[PipelineLODHash] lod=3 tiles=208 density=0x6e050028a242875e mesh=0x0b9d3d5662314869 payload=0x6722ab1369ea33b6 topology=0x63c9c29f4568f5e3
|
||||
[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=0xfa232c4422e2e610 mesh=0xce56a238f93d6206 payload=0xcf7b1f5aeaaedf99 topology=0xad24679ddf040575
|
||||
[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=0x2047609972d09d73 mesh=0xae66aa3b34004b82 payload=0x8f81f605def21b18 topology=0xd9cef4942e0bc09e
|
||||
[PipelineLODHash] lod=3 tiles=208 density=0x2ed5ccb4d6e39e53 mesh=0x896a64c1f3e2c360 payload=0x79325ad522c83de8 topology=0x49de5799ff8ea38e
|
||||
[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
|
||||
@@ -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-v220.baseline",
|
||||
"L113-v220-adaptive-spline-rounded-half-pipe")
|
||||
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v222.baseline",
|
||||
"L114-v222-local-passage-floor-profile")
|
||||
if recipe == "floating-islands" and style == 2:
|
||||
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v220.baseline",
|
||||
"L113-v220-adaptive-spline-rounded-half-pipe")
|
||||
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v222.baseline",
|
||||
"L114-v222-local-passage-floor-profile")
|
||||
raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")
|
||||
|
||||
|
||||
@@ -199,7 +199,8 @@ def validate_passage(runner: VerificationRunner, name: str, output: str,
|
||||
wall_threshold = re.search(r"threshold_m=([-0-9.]+)", line)
|
||||
floor = re.search(r"floor_amplitude_m=([-0-9.]+)", line)
|
||||
floor_threshold = re.search(r"floor_threshold_m=([-0-9.]+)", line)
|
||||
wall_coverage = parse_ratio(line, "coverage")
|
||||
wall_window_count = parse_ratio(line, "wall_windows")
|
||||
wall_window_coverage = parse_ratio(line, "wall_window_coverage")
|
||||
floor_coverage = parse_ratio(line, "floor_coverage")
|
||||
floor_coverage_pair = re.search(r"floor_coverage=(\d+)/(\d+)", line)
|
||||
floor_overlap_skips = re.search(r"floor_overlap_skips=(\d+)", line)
|
||||
@@ -211,15 +212,17 @@ def validate_passage(runner: VerificationRunner, name: str, output: str,
|
||||
floor_measured = bool(
|
||||
floor_mode and floor_mode.group(1) in ("measured", "measured_with_overlap")
|
||||
and floor_coverage is not None and floor_coverage >= 0.75)
|
||||
wall_measured = bool(wall_window_count is not None and wall_window_count > 0
|
||||
and wall_window_coverage is not None and wall_window_coverage >= 0.75)
|
||||
ripple_ok &= bool(
|
||||
"status=PASS" in line and wall and wall_threshold and floor and floor_threshold
|
||||
and wall_coverage is not None and floor_coverage is not None
|
||||
and wall_measured and floor_coverage is not None
|
||||
and float(wall.group(1)) <= float(wall_threshold.group(1))
|
||||
and float(floor.group(1)) <= float(floor_threshold.group(1))
|
||||
and wall_coverage >= 0.75 and (floor_measured or floor_overlap_only))
|
||||
and (floor_measured or floor_overlap_only))
|
||||
passed_all &= ripple_ok
|
||||
detail = (f"profiles={len(rows)} wall/floor amplitudes under their thresholds; "
|
||||
"wall coverage>=0.75 and floor measured coverage>=0.75 or all probes "
|
||||
"measured wall windows cover>=0.75 and floor measured coverage>=0.75 or all probes "
|
||||
"explicitly masked by overlapping tunnel interior air")
|
||||
if defer:
|
||||
defer_requirement(runner, f"{name}:wall-floor-ripple-and-coverage", ripple_ok, detail)
|
||||
|
||||