wip(L26, side branch): every passage style walkable in both recipes (0/0/0); SW bridges look wrong
All 4 styles x both recipes pass capsule, open-air and naturalness audits, and full-view hashes are deterministic. But the SW passage crosses 355 m of open SurfaceWorld sky as a ~2 km fragmented rock ramp, which is not natural. L27 replaces long bridges with a shared ceiling-to-ground rock column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
ef789aaa00
commit
37a223046c
13 files changed
+877
-40
No files matched your search
@@ -2010,6 +2010,30 @@ captures, style results, and the single Unreal adapter version edit.
|
|||||||
[`Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md)
|
[`Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md)
|
||||||
records the evidence and caveats.
|
records the evidence and caveats.
|
||||||
|
|
||||||
|
### 8.44 Walkable passages as tunnels in rock — World Assembler L26
|
||||||
|
|
||||||
|
L25 correctly stopped support from appearing in native air, but its SurfaceWorld-recipe passages
|
||||||
|
still crossed a long open volume in the SurfaceWorld arrival strata. The segment map places that gap
|
||||||
|
inside SurfaceWorld (about `-146` to `-497 m`), after the TunnelNetwork transition; it is not TunnelNetwork
|
||||||
|
cave air. Both ends meet native rock. The shared Core planner now finds contiguous unsupported ranges
|
||||||
|
from the native field sign and caches them by resolved-layout identity. It adds a deterministic,
|
||||||
|
relief-bearing rock span only across those ranges. The span is at least 5 m wide, several metres thick,
|
||||||
|
and joins native rock at both ends. The passage carve then opens the bore through the added density,
|
||||||
|
leaving a grounded floor and rock walls/ceiling. SurfaceWorld away from the passage remains unchanged.
|
||||||
|
|
||||||
|
Native route support remains gated on pre-passage solid density. Before adding any support density, the
|
||||||
|
composer also checks the resolved player capsule, so neighboring floor segments cannot restore rock
|
||||||
|
inside the avatar path. This keeps all four style overrides walkable in both recipes. The bridge audit
|
||||||
|
measures its authored density field at 256 route sections, checks the 5 m width and connected ends, and
|
||||||
|
records a relief-bearing thickness range; the open-air audit still runs the L24 negative control.
|
||||||
|
|
||||||
|
The shared evaluator is version 13; FloatingIslands geometry remains version 7 and resolved-layout
|
||||||
|
schema V3 is unchanged. These changes are in Core and the standalone audit/capture path; no Unreal
|
||||||
|
adapter edit is required. The L26 report contains the eight-style table, hashes, captures and run history.
|
||||||
|
|
||||||
|
[`Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md)
|
||||||
|
records the final evidence.
|
||||||
|
|
||||||
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||||
|
|
||||||
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
||||||
|
|||||||
+14
@@ -1194,6 +1194,20 @@ The shared evaluator is version 12 and FI geometry version is 7; resolved-layout
|
|||||||
unchanged. See ARCHITECTURE §8.43 and
|
unchanged. See ARCHITECTURE §8.43 and
|
||||||
[`Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md).
|
[`Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md).
|
||||||
|
|
||||||
|
### 3.27 Tunnels in native rock with grounded passage bridges — World Assembler L26
|
||||||
|
|
||||||
|
| Symbol | Canonical file | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| `BuildPassageBridgePlan` / `PassageBridgeSignedDistance` | `Standalone/Core/src/ShapeWorldInternal.h` | Uses native density sign to plan cached, connected rock spans only across unsupported route ranges; shared by both recipes and all passage styles. |
|
||||||
|
| `PassageBridgeCache` | `Standalone/Source/OwnerWorldLoader.h` | Shares deterministic ranges by resolved-layout identity across shape instances. |
|
||||||
|
| `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Adds bridge density before carving the bore; gates native floor support on solid input and player-capsule clearance. |
|
||||||
|
| `auditPassageBridges` / `auditPassageOpenAir` / `--audit-resolved-layout-passages` | `Standalone/Source/StreamBenchmarkPipeline.h` | Measures bridge width, thickness, relief and connected ends; checks capsule support, open air, naturalness, route binding and the L24 negative control. |
|
||||||
|
| `PassageBridgeRangeAuditV1` / `passageBridgeRanges()` / `isPassageBridgeSample()` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/VoxelForgeCore.cpp` | Exposes audit-only bridge ranges and density queries to the standalone validator. |
|
||||||
|
|
||||||
|
The shared evaluator is version 13; FloatingIslands geometry remains version 7 and resolved-layout
|
||||||
|
schema V3 is unchanged. See ARCHITECTURE §8.44 and
|
||||||
|
[`Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md).
|
||||||
|
|
||||||
## 4. The density pipeline (most-edited hot path)
|
## 4. The density pipeline (most-edited hot path)
|
||||||
|
|
||||||
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ command -v ar >/dev/null || { printf 'ar not found\n' >&2; exit 2; }
|
|||||||
command -v pkg-config >/dev/null || { printf 'pkg-config not found\n' >&2; exit 2; }
|
command -v pkg-config >/dev/null || { printf 'pkg-config not found\n' >&2; exit 2; }
|
||||||
pkg-config --exists egl glesv2 libpng || { printf 'EGL, GLESv2, and libpng development files are required\n' >&2; exit 2; }
|
pkg-config --exists egl glesv2 libpng || { printf 'EGL, GLESv2, and libpng development files are required\n' >&2; exit 2; }
|
||||||
|
|
||||||
BUILD_DIR="$ROOT/Build/linux-$VARIANT"
|
BUILD_ROOT="${VOXELFORGE_BUILD_ROOT:-$ROOT/Build}"
|
||||||
BIN_DIR="$ROOT/bin/linux"
|
BIN_DIR="${VOXELFORGE_BIN_DIR:-$ROOT/bin/linux}"
|
||||||
|
BUILD_DIR="$BUILD_ROOT/linux-$VARIANT"
|
||||||
mkdir -p "$BUILD_DIR/wrappers" "$BUILD_DIR/objects" "$BIN_DIR"
|
mkdir -p "$BUILD_DIR/wrappers" "$BUILD_DIR/objects" "$BIN_DIR"
|
||||||
|
|
||||||
INCLUDES=(
|
INCLUDES=(
|
||||||
|
|||||||
@@ -249,8 +249,8 @@ namespace VoxelForgeCore
|
|||||||
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
|
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
|
||||||
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
||||||
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
|
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
|
||||||
// Version 12 gates passage supports on native rock and anchors the FI spiral inside a measured root.
|
// Version 13 adds connected relief-bearing rock spans across native open-air passage gaps.
|
||||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 12;
|
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 13;
|
||||||
|
|
||||||
enum class FieldChannel : std::uint32_t
|
enum class FieldChannel : std::uint32_t
|
||||||
{
|
{
|
||||||
@@ -666,6 +666,17 @@ namespace VoxelForgeCore
|
|||||||
std::vector<ResolvedLayoutPassagePointV1> controlPoints;
|
std::vector<ResolvedLayoutPassagePointV1> controlPoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PassageBridgeRangeAuditV1
|
||||||
|
{
|
||||||
|
std::size_t passageIndex = 0;
|
||||||
|
std::size_t firstSegment = 0;
|
||||||
|
std::size_t lastSegment = 0;
|
||||||
|
std::uint64_t unsupportedSamples = 0;
|
||||||
|
double unsupportedLengthMetres = 0.0;
|
||||||
|
std::array<double, 4> startPointSubvoxels{};
|
||||||
|
std::array<double, 4> endPointSubvoxels{};
|
||||||
|
};
|
||||||
|
|
||||||
struct ResolvedLayoutPassageConfigV1
|
struct ResolvedLayoutPassageConfigV1
|
||||||
{
|
{
|
||||||
std::int32_t upperStrateIndex = -1;
|
std::int32_t upperStrateIndex = -1;
|
||||||
@@ -1314,6 +1325,10 @@ namespace VoxelForgeCore
|
|||||||
ShapeBuildStats buildStats() const;
|
ShapeBuildStats buildStats() const;
|
||||||
|
|
||||||
float densityAt(Vec3d voxelPosition) const;
|
float densityAt(Vec3d voxelPosition) const;
|
||||||
|
// Audit-only view of connected rock spans planned for passage support.
|
||||||
|
std::vector<PassageBridgeRangeAuditV1> passageBridgeRanges() const;
|
||||||
|
bool isPassageBridgeSample(std::size_t passageIndex, std::size_t segmentIndex,
|
||||||
|
Vec3d voxelPosition) const;
|
||||||
// Audit-only selector provenance for the same point evaluator; never used by meshing.
|
// Audit-only selector provenance for the same point evaluator; never used by meshing.
|
||||||
std::string densityDispatchPathAt(Vec3d voxelPosition) const;
|
std::string densityDispatchPathAt(Vec3d voxelPosition) const;
|
||||||
// Analytic source distance without the occupancy-preserving near-zero clamp or packed proxy.
|
// Analytic source distance without the occupancy-preserving near-zero clamp or packed proxy.
|
||||||
|
|||||||
@@ -41,6 +41,27 @@ public:
|
|||||||
double SealThickness=0.0;
|
double SealThickness=0.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const std::vector<FStandalonePassageBridgeRange>& GetPassageBridgeRanges() const
|
||||||
|
{return PassageBridgeRanges;}
|
||||||
|
|
||||||
|
bool IsResolvedPassageBridgeSample(std::size_t PassageIndex,std::size_t SegmentIndex,
|
||||||
|
const VoxelForgeCore::Vec3d& Position) const
|
||||||
|
{
|
||||||
|
if(IsPassageBridgeSegment(PassageIndex,SegmentIndex)
|
||||||
|
&&PassageBridgeSignedDistance(PassageIndex,SegmentIndex,
|
||||||
|
static_cast<float>(Position.x),static_cast<float>(Position.y),
|
||||||
|
static_cast<float>(Position.z))<=0.0)return true;
|
||||||
|
for(const auto& range:PassageBridgeRanges)
|
||||||
|
{
|
||||||
|
if(range.passageIndex!=PassageIndex)continue;
|
||||||
|
for(std::size_t segment=range.firstSegment;segment<=range.lastSegment;++segment)
|
||||||
|
if(segment!=SegmentIndex&&PassageBridgeSignedDistance(PassageIndex,segment,
|
||||||
|
static_cast<float>(Position.x),static_cast<float>(Position.y),
|
||||||
|
static_cast<float>(Position.z))<=0.0)return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
double GetCoreVoxelSizeCm() const
|
double GetCoreVoxelSizeCm() const
|
||||||
{
|
{
|
||||||
return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0
|
return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0
|
||||||
@@ -168,20 +189,19 @@ public:
|
|||||||
for(const auto& passage:ResolvedLayout->passages)
|
for(const auto& passage:ResolvedLayout->passages)
|
||||||
composeRouteMouthSupport(passage.upperStrateIndex,
|
composeRouteMouthSupport(passage.upperStrateIndex,
|
||||||
passage.upperMouthSubvoxels,true);
|
passage.upperMouthSubvoxels,true);
|
||||||
for(const auto& passage:ResolvedLayout->passages)
|
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||||
{
|
{
|
||||||
|
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||||
if(passage.controlPoints.size()<2)continue;
|
if(passage.controlPoints.size()<2)continue;
|
||||||
const double placementRadiusQ=static_cast<double>(passage.placementRadiusSubvoxels)
|
|
||||||
+static_cast<double>(passage.midRadiusSubvoxels);
|
|
||||||
if(qx*qx+qy*qy>placementRadiusQ*placementRadiusQ)
|
|
||||||
continue;
|
|
||||||
const double firstFloorQ=passage.controlPoints.front().positionSubvoxels[2]
|
const double firstFloorQ=passage.controlPoints.front().positionSubvoxels[2]
|
||||||
-passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ;
|
-passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ;
|
||||||
const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2]
|
const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2]
|
||||||
-passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ;
|
-passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ;
|
||||||
if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels
|
if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels
|
||||||
||qz<lastFloorQ-2.0*passage.mouthRadiusSubvoxels)
|
||qz<lastFloorQ-2.0*passage.mouthRadiusSubvoxels)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
const auto floorProfile=[&](const auto& point)
|
const auto floorProfile=[&](const auto& point)
|
||||||
{return point.positionSubvoxels[2]-point.radiusSubvoxels+playerCenterAboveFloorQ;};
|
{return point.positionSubvoxels[2]-point.radiusSubvoxels+playerCenterAboveFloorQ;};
|
||||||
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(passage.controlPoints.size())-1;
|
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(passage.controlPoints.size())-1;
|
||||||
@@ -204,8 +224,10 @@ public:
|
|||||||
const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,
|
const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,
|
||||||
(carveLow-passage.controlPoints.begin())+1);
|
(carveLow-passage.controlPoints.begin())+1);
|
||||||
double bestSignedDistance=std::numeric_limits<double>::infinity();
|
double bestSignedDistance=std::numeric_limits<double>::infinity();
|
||||||
|
double bestPlayerCapsuleSignedDistance=std::numeric_limits<double>::infinity();
|
||||||
double bestT=0.0;
|
double bestT=0.0;
|
||||||
std::ptrdiff_t bestIndex=0;
|
std::ptrdiff_t bestIndex=0;
|
||||||
|
std::ptrdiff_t bestSupportSegmentIndex=0;
|
||||||
bool hasSupportFloor=false;
|
bool hasSupportFloor=false;
|
||||||
double bestSupportDensity=0.0;
|
double bestSupportDensity=0.0;
|
||||||
const auto considerCarveSegment=[&](std::ptrdiff_t index)
|
const auto considerCarveSegment=[&](std::ptrdiff_t index)
|
||||||
@@ -225,12 +247,37 @@ public:
|
|||||||
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
|
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
|
||||||
const double radius=static_cast<double>(a.radiusSubvoxels)
|
const double radius=static_cast<double>(a.radiusSubvoxels)
|
||||||
+t*(b.radiusSubvoxels-a.radiusSubvoxels);
|
+t*(b.radiusSubvoxels-a.radiusSubvoxels);
|
||||||
|
const double horizontalLengthSq=dx*dx+dy*dy;
|
||||||
|
const double playerT=horizontalLengthSq>0.0
|
||||||
|
?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):t;
|
||||||
|
const double playerDx=qx-(ax+playerT*dx);
|
||||||
|
const double playerDy=qy-(ay+playerT*dy);
|
||||||
|
const double playerRadius=static_cast<double>(a.radiusSubvoxels)
|
||||||
|
+playerT*(b.radiusSubvoxels-a.radiusSubvoxels);
|
||||||
|
const double playerCenterZ=az+playerT*dz-playerRadius+playerCenterAboveFloorQ;
|
||||||
|
const double playerVertical=std::max(0.0,
|
||||||
|
std::abs(qz-playerCenterZ)-0.54*qPerMetre);
|
||||||
|
const double playerCapsuleDistance=DeterministicSqrt(
|
||||||
|
playerDx*playerDx+playerDy*playerDy+playerVertical*playerVertical)
|
||||||
|
-0.34*qPerMetre;
|
||||||
|
bestPlayerCapsuleSignedDistance=std::min(bestPlayerCapsuleSignedDistance,
|
||||||
|
playerCapsuleDistance);
|
||||||
const double signedDistance=DeterministicSqrt(ex*ex+ey*ey+ez*ez)-radius;
|
const double signedDistance=DeterministicSqrt(ex*ex+ey*ey+ez*ez)-radius;
|
||||||
if(signedDistance<bestSignedDistance)
|
if(signedDistance<bestSignedDistance)
|
||||||
{bestSignedDistance=signedDistance;bestT=t;bestIndex=index;}
|
{bestSignedDistance=signedDistance;bestT=t;bestIndex=index;}
|
||||||
};
|
};
|
||||||
for(std::ptrdiff_t index=carveBegin;index<=carveEnd;++index)
|
for(std::ptrdiff_t index=carveBegin;index<=carveEnd;++index)
|
||||||
considerCarveSegment(index);
|
considerCarveSegment(index);
|
||||||
|
// Alternate styles can bend locally at either mouth, so floorProfile is
|
||||||
|
// not monotone over those short endpoint runs. Keep the binary search for
|
||||||
|
// the long descent and inspect both endpoint neighborhoods directly.
|
||||||
|
for(std::ptrdiff_t index=0;index<std::min<std::ptrdiff_t>(8,lowerMouthIndex);++index)
|
||||||
|
considerCarveSegment(index);
|
||||||
|
const std::ptrdiff_t mouthSearchBegin=std::max<std::ptrdiff_t>(0,lowerMouthIndex-8);
|
||||||
|
const std::ptrdiff_t mouthSearchEnd=std::min<std::ptrdiff_t>(lastIndex,
|
||||||
|
lowerMouthIndex+8);
|
||||||
|
for(std::ptrdiff_t index=mouthSearchBegin;index<mouthSearchEnd;++index)
|
||||||
|
considerCarveSegment(index);
|
||||||
// The route-bound landing can rise and fall after the lower mouth. Scan
|
// The route-bound landing can rise and fall after the lower mouth. Scan
|
||||||
// that short tail directly; binary search is only valid on the descent.
|
// that short tail directly; binary search is only valid on the descent.
|
||||||
for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index)
|
for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index)
|
||||||
@@ -250,6 +297,12 @@ public:
|
|||||||
const double wallReliefWeight=1.0-0.80*verticalWallAlignment;
|
const double wallReliefWeight=1.0-0.80*verticalWallAlignment;
|
||||||
const double roughSignedDistance=bestSignedDistance
|
const double roughSignedDistance=bestSignedDistance
|
||||||
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.24*qPerMetre*wallReliefWeight;
|
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.24*qPerMetre*wallReliefWeight;
|
||||||
|
if(IsPassageBridgeSegment(passageIndex,static_cast<std::size_t>(bestIndex)))
|
||||||
|
{
|
||||||
|
const double bridgeDistance=PassageBridgeSignedDistance(passageIndex,
|
||||||
|
static_cast<std::size_t>(bestIndex),X,Y,Z);
|
||||||
|
composed=std::min(composed,static_cast<float>(bridgeDistance/Q));
|
||||||
|
}
|
||||||
// Positive density is air; rough rock is cut with full side/ceiling detail and
|
// Positive density is air; rough rock is cut with full side/ceiling detail and
|
||||||
// gentler relief on the gravity-facing wall so the supported tread stays open.
|
// gentler relief on the gravity-facing wall so the supported tread stays open.
|
||||||
composed=std::max(composed,static_cast<float>(-roughSignedDistance/Q));
|
composed=std::max(composed,static_cast<float>(-roughSignedDistance/Q));
|
||||||
@@ -264,7 +317,10 @@ public:
|
|||||||
const double radiusB=segmentB.radiusSubvoxels;
|
const double radiusB=segmentB.radiusSubvoxels;
|
||||||
// Keep the tread within the probe band and below the capsule's
|
// Keep the tread within the probe band and below the capsule's
|
||||||
// lowest sampled point.
|
// lowest sampled point.
|
||||||
const double floorOffset=0.20;
|
const double floorOffset=IsPassageBridgeSegment(passageIndex,
|
||||||
|
static_cast<std::size_t>(index))
|
||||||
|
?PassageBridgeDeckOffsetMetres(passageIndex,static_cast<std::size_t>(index))
|
||||||
|
:0.20;
|
||||||
const double horizontalLengthSq=dx*dx+dy*dy;
|
const double horizontalLengthSq=dx*dx+dy*dy;
|
||||||
const double supportT=horizontalLengthSq>0.0
|
const double supportT=horizontalLengthSq>0.0
|
||||||
?std::clamp(((qx-segmentA.positionSubvoxels[0])*dx
|
?std::clamp(((qx-segmentA.positionSubvoxels[0])*dx
|
||||||
@@ -304,6 +360,7 @@ public:
|
|||||||
if(supportDistanceSq>=bestSupportDistanceSquared)return;
|
if(supportDistanceSq>=bestSupportDistanceSquared)return;
|
||||||
bestSupportDistanceSquared=supportDistanceSq;
|
bestSupportDistanceSquared=supportDistanceSq;
|
||||||
hasSupportFloor=true;
|
hasSupportFloor=true;
|
||||||
|
bestSupportSegmentIndex=index;
|
||||||
bestSupportDensity=std::clamp(Z-floorVoxels,
|
bestSupportDensity=std::clamp(Z-floorVoxels,
|
||||||
-supportThicknessVoxels,0.0);
|
-supportThicknessVoxels,0.0);
|
||||||
};
|
};
|
||||||
@@ -330,7 +387,11 @@ public:
|
|||||||
considerSupportSegment(index);
|
considerSupportSegment(index);
|
||||||
// Keep route treads inside the native rock envelope. Carving may reopen the
|
// Keep route treads inside the native rock envelope. Carving may reopen the
|
||||||
// floor above this point, but a support cannot create a new surface in air.
|
// floor above this point, but a support cannot create a new surface in air.
|
||||||
if(hasSupportFloor&&density<0.0f)
|
const bool bridgeFloorSupport=hasSupportFloor
|
||||||
|
&&IsPassageBridgeSegment(passageIndex,
|
||||||
|
static_cast<std::size_t>(bestSupportSegmentIndex));
|
||||||
|
if(hasSupportFloor&&bestPlayerCapsuleSignedDistance>=0.05*qPerMetre
|
||||||
|
&&(density<0.0f||bridgeFloorSupport))
|
||||||
composed=std::min(composed,static_cast<float>(bestSupportDensity));
|
composed=std::min(composed,static_cast<float>(bestSupportDensity));
|
||||||
}
|
}
|
||||||
return composed;
|
return composed;
|
||||||
@@ -343,6 +404,10 @@ public:
|
|||||||
{
|
{
|
||||||
if(!ResolvedLayout||ResolvedLayout->passages.empty())return false;
|
if(!ResolvedLayout||ResolvedLayout->passages.empty())return false;
|
||||||
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||||
|
const double voxelsPerMetre=Owner&&Owner->CoreVoxelSizeMicrons>0
|
||||||
|
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
|
||||||
|
const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre;
|
||||||
|
const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre;
|
||||||
for(const auto& passage:ResolvedLayout->passages)
|
for(const auto& passage:ResolvedLayout->passages)
|
||||||
{
|
{
|
||||||
for(std::size_t i=1;i<passage.controlPoints.size();++i)
|
for(std::size_t i=1;i<passage.controlPoints.size();++i)
|
||||||
@@ -351,13 +416,18 @@ public:
|
|||||||
const double radius=static_cast<double>(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q;
|
const double radius=static_cast<double>(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q;
|
||||||
const double floorThicknessVoxels=Owner&&Owner->CoreVoxelSizeMicrons>0
|
const double floorThicknessVoxels=Owner&&Owner->CoreVoxelSizeMicrons>0
|
||||||
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
|
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
|
||||||
|
// A resolved passage bridge extends to a 3.5 m half-width and almost 5 m
|
||||||
|
// below the bore floor. Keep those cells shape-dependent for classification
|
||||||
|
// and streaming even before its bridge ranges have been compiled.
|
||||||
|
const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels);
|
||||||
|
const double footDepth=std::max(floorThicknessVoxels,bridgeFootDepthVoxels);
|
||||||
const double low[3]={
|
const double low[3]={
|
||||||
std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-radius,
|
std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent,
|
||||||
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-radius,
|
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent,
|
||||||
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-floorThicknessVoxels};
|
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-footDepth};
|
||||||
const double high[3]={
|
const double high[3]={
|
||||||
std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+radius,
|
std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent,
|
||||||
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+radius,
|
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent,
|
||||||
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius};
|
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius};
|
||||||
bool overlap=true;
|
bool overlap=true;
|
||||||
for(int axis=0;axis<3;++axis)
|
for(int axis=0;axis<3;++axis)
|
||||||
@@ -715,6 +785,7 @@ public:
|
|||||||
if(RoundTunnelsEnabled)
|
if(RoundTunnelsEnabled)
|
||||||
BuildRoundTunnelPrimitives();
|
BuildRoundTunnelPrimitives();
|
||||||
BuildSpatialBins(LineBinSize);
|
BuildSpatialBins(LineBinSize);
|
||||||
|
BuildPassageBridgePlan();
|
||||||
Stats.LocalLineBinSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
|
Stats.LocalLineBinSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
|
||||||
Stats.ShapeSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
|
Stats.ShapeSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
|
||||||
Stats.PrimitiveCount=Primitives.size();
|
Stats.PrimitiveCount=Primitives.size();
|
||||||
@@ -1622,6 +1693,218 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool IsPassageBridgeSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const
|
||||||
|
{
|
||||||
|
return std::any_of(PassageBridgeRanges.begin(),PassageBridgeRanges.end(),
|
||||||
|
[PassageIndex,SegmentIndex](const auto& range)
|
||||||
|
{return range.passageIndex==PassageIndex
|
||||||
|
&&SegmentIndex>=range.firstSegment&&SegmentIndex<=range.lastSegment;});
|
||||||
|
}
|
||||||
|
|
||||||
|
double PassageBridgeDeckOffsetMetres(std::size_t PassageIndex,
|
||||||
|
std::size_t SegmentIndex) const
|
||||||
|
{
|
||||||
|
for(const auto& range:PassageBridgeRanges)
|
||||||
|
{
|
||||||
|
if(range.passageIndex!=PassageIndex||SegmentIndex>range.lastSegment)continue;
|
||||||
|
if(SegmentIndex>=range.firstSegment)return 0.13;
|
||||||
|
}
|
||||||
|
return 0.20;
|
||||||
|
}
|
||||||
|
|
||||||
|
double PassageBridgeSignedDistance(std::size_t PassageIndex,std::size_t SegmentIndex,
|
||||||
|
float X,float Y,float Z) const
|
||||||
|
{
|
||||||
|
if(!ResolvedLayout||PassageIndex>=ResolvedLayout->passages.size())
|
||||||
|
return std::numeric_limits<double>::infinity();
|
||||||
|
const auto& passage=ResolvedLayout->passages[PassageIndex];
|
||||||
|
if(SegmentIndex+1>=passage.controlPoints.size())
|
||||||
|
return std::numeric_limits<double>::infinity();
|
||||||
|
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||||
|
const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,
|
||||||
|
ResolvedLayout->coreVoxelSizeMicrometres);
|
||||||
|
const auto& a=passage.controlPoints[SegmentIndex];
|
||||||
|
const auto& b=passage.controlPoints[SegmentIndex+1];
|
||||||
|
const double ax=static_cast<double>(a.positionSubvoxels[0]);
|
||||||
|
const double ay=static_cast<double>(a.positionSubvoxels[1]);
|
||||||
|
const double dx=static_cast<double>(b.positionSubvoxels[0])-ax;
|
||||||
|
const double dy=static_cast<double>(b.positionSubvoxels[1])-ay;
|
||||||
|
const double horizontalLengthSq=dx*dx+dy*dy;
|
||||||
|
const double qx=static_cast<double>(X)*Q,qy=static_cast<double>(Y)*Q;
|
||||||
|
const double qz=static_cast<double>(Z)*Q;
|
||||||
|
const double t=horizontalLengthSq>0.0
|
||||||
|
?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):0.0;
|
||||||
|
const double centerX=ax+t*dx,centerY=ay+t*dy;
|
||||||
|
const double centerZ=a.positionSubvoxels[2]+t*(
|
||||||
|
static_cast<double>(b.positionSubvoxels[2])-a.positionSubvoxels[2]);
|
||||||
|
const double radius=a.radiusSubvoxels+t*(
|
||||||
|
static_cast<double>(b.radiusSubvoxels)-a.radiusSubvoxels);
|
||||||
|
// Embed the deck 13 cm above the bore bottom: within the 25 cm support
|
||||||
|
// probe, while leaving 16 cm between it and the capsule's lowest sample.
|
||||||
|
const double floorQ=centerZ-radius
|
||||||
|
+PassageBridgeDeckOffsetMetres(PassageIndex,SegmentIndex)*qPerMetre;
|
||||||
|
const double relief=std::clamp(static_cast<double>(VF_FieldSurfaceRelief(X,Y,Z,
|
||||||
|
static_cast<std::uint32_t>(Owner?Owner->Seed:0))),-1.0,1.0);
|
||||||
|
const double halfWidth=(3.30+0.20*relief)*qPerMetre;
|
||||||
|
const double thickness=(4.0+1.0*relief)*qPerMetre;
|
||||||
|
const double horizontalDistance=DeterministicSqrt((qx-centerX)*(qx-centerX)
|
||||||
|
+(qy-centerY)*(qy-centerY));
|
||||||
|
const double sideDistance=horizontalDistance-halfWidth;
|
||||||
|
const double topDistance=qz-floorQ;
|
||||||
|
const double bottomDistance=floorQ-thickness-qz;
|
||||||
|
const double verticalDistance=std::max(topDistance,bottomDistance);
|
||||||
|
const double outside=DeterministicSqrt(
|
||||||
|
std::max(sideDistance,0.0)*std::max(sideDistance,0.0)
|
||||||
|
+std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0));
|
||||||
|
const double inside=std::min(std::max(sideDistance,verticalDistance),0.0);
|
||||||
|
return outside+inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NativePassageDensityAt(float X,float Y,float Z) const
|
||||||
|
{
|
||||||
|
float density=0.0f;
|
||||||
|
if(!NativeFloatingIslandDensityAt(X,Y,Z,density)
|
||||||
|
&&!NativeSurfaceDensityAt(X,Y,Z,density))
|
||||||
|
{
|
||||||
|
if(UsesLegacySurfaceField(X,Y,Z)&&Owner&&Owner->Generator)
|
||||||
|
density=Owner->Generator->GetDensityAt(X,Y,Z);
|
||||||
|
else
|
||||||
|
density=AnalyticDensityAt(X,Y,Z,true,nullptr,nullptr,nullptr,
|
||||||
|
false,false);
|
||||||
|
}
|
||||||
|
return ApplyResolvedStrateBoundaryBand(density,X,Y,Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuildPassageBridgePlan()
|
||||||
|
{
|
||||||
|
PassageBridgeRanges.clear();
|
||||||
|
if(!Owner||!ResolvedLayout||ResolvedLayout->passages.empty()
|
||||||
|
||!Owner->PassageBridgeCache)return;
|
||||||
|
auto& cache=*Owner->PassageBridgeCache;
|
||||||
|
std::lock_guard<std::mutex> lock(cache.mutex);
|
||||||
|
const auto cached=cache.byLayout.find(ResolvedLayout->stableIdentity);
|
||||||
|
if(cached!=cache.byLayout.end())
|
||||||
|
{PassageBridgeRanges=cached->second;return;}
|
||||||
|
|
||||||
|
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||||
|
const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,
|
||||||
|
ResolvedLayout->coreVoxelSizeMicrometres);
|
||||||
|
const auto inSurfaceWorldStratum=[&](double zSubvoxels)
|
||||||
|
{
|
||||||
|
const double zMicrometres=zSubvoxels
|
||||||
|
*ResolvedLayout->coreVoxelSizeMicrometres/Q;
|
||||||
|
return std::any_of(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(),
|
||||||
|
[zMicrometres](const auto& stratum)
|
||||||
|
{return stratum.generatorKind=="SurfaceWorld"
|
||||||
|
&&zMicrometres>=stratum.interiorBottomMicrometres
|
||||||
|
&&zMicrometres<stratum.interiorTopMicrometres;});
|
||||||
|
};
|
||||||
|
constexpr std::array<std::array<double,2>,5> supportOffsets{{
|
||||||
|
{{0.0,0.0}},{{0.45,0.0}},{{-0.45,0.0}},{{0.0,0.45}},{{0.0,-0.45}}}};
|
||||||
|
constexpr std::array<double,2> supportDepthsMetres{{0.125,0.25}};
|
||||||
|
struct FPathSample
|
||||||
|
{
|
||||||
|
std::size_t segment=0;
|
||||||
|
std::array<double,4> point{};
|
||||||
|
bool nativeSupported=false;
|
||||||
|
};
|
||||||
|
const auto nativeSupportedAt=[&](const std::array<double,4>& point)
|
||||||
|
{
|
||||||
|
const double floorQ=point[2]-point[3]+0.13*qPerMetre;
|
||||||
|
std::uint32_t supported=0;
|
||||||
|
for(const auto& offset:supportOffsets)
|
||||||
|
{
|
||||||
|
bool hasSolid=false;
|
||||||
|
for(const double depth:supportDepthsMetres)
|
||||||
|
{
|
||||||
|
const double sampleZ=floorQ-depth*qPerMetre;
|
||||||
|
// Bridge spans address the measured SurfaceWorld void. Other native
|
||||||
|
// strata retain their own route/cave composition and are excluded here.
|
||||||
|
if(!inSurfaceWorldStratum(sampleZ)){hasSolid=true;break;}
|
||||||
|
float density=1.0f;
|
||||||
|
if(!NativeSurfaceDensityAt(
|
||||||
|
static_cast<float>((point[0]+offset[0]*qPerMetre)/Q),
|
||||||
|
static_cast<float>((point[1]+offset[1]*qPerMetre)/Q),
|
||||||
|
static_cast<float>(sampleZ/Q),density))continue;
|
||||||
|
if(density<0.0f){hasSolid=true;break;}
|
||||||
|
}
|
||||||
|
supported+=hasSolid;
|
||||||
|
}
|
||||||
|
return supported>=3;
|
||||||
|
};
|
||||||
|
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
||||||
|
++passageIndex)
|
||||||
|
{
|
||||||
|
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||||
|
if(passage.controlPoints.size()<2)continue;
|
||||||
|
std::vector<FPathSample> samples;
|
||||||
|
samples.reserve(passage.controlPoints.size()*2);
|
||||||
|
const auto pointAt=[&](std::size_t segment,double t)
|
||||||
|
{
|
||||||
|
const auto& a=passage.controlPoints[segment];
|
||||||
|
const auto& b=passage.controlPoints[segment+1];
|
||||||
|
FPathSample sample;sample.segment=segment;
|
||||||
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
|
sample.point[axis]=a.positionSubvoxels[axis]+t*(
|
||||||
|
static_cast<double>(b.positionSubvoxels[axis])
|
||||||
|
-a.positionSubvoxels[axis]);
|
||||||
|
sample.point[3]=a.radiusSubvoxels+t*(
|
||||||
|
static_cast<double>(b.radiusSubvoxels)-a.radiusSubvoxels);
|
||||||
|
sample.nativeSupported=nativeSupportedAt(sample.point);
|
||||||
|
return sample;
|
||||||
|
};
|
||||||
|
samples.push_back(pointAt(0,0.0));
|
||||||
|
for(std::size_t segment=0;segment+1<passage.controlPoints.size();++segment)
|
||||||
|
{
|
||||||
|
const auto& a=passage.controlPoints[segment];
|
||||||
|
const auto& b=passage.controlPoints[segment+1];
|
||||||
|
const double dx=static_cast<double>(b.positionSubvoxels[0])-a.positionSubvoxels[0];
|
||||||
|
const double dy=static_cast<double>(b.positionSubvoxels[1])-a.positionSubvoxels[1];
|
||||||
|
const double dz=static_cast<double>(b.positionSubvoxels[2])-a.positionSubvoxels[2];
|
||||||
|
const double lengthMetres=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre;
|
||||||
|
const std::size_t intervals=std::max<std::size_t>(1,
|
||||||
|
static_cast<std::size_t>(std::ceil(lengthMetres/0.5)));
|
||||||
|
for(std::size_t step=1;step<=intervals;++step)
|
||||||
|
samples.push_back(pointAt(segment,static_cast<double>(step)/intervals));
|
||||||
|
}
|
||||||
|
std::size_t cursor=0;
|
||||||
|
while(cursor<samples.size())
|
||||||
|
{
|
||||||
|
if(samples[cursor].nativeSupported){++cursor;continue;}
|
||||||
|
const std::size_t gapBegin=cursor;
|
||||||
|
while(cursor<samples.size()&&!samples[cursor].nativeSupported)++cursor;
|
||||||
|
const std::size_t gapEnd=cursor-1;
|
||||||
|
if(gapBegin==0||cursor>=samples.size())continue;
|
||||||
|
double gapLength=0.0;
|
||||||
|
for(std::size_t index=gapBegin;index<=gapEnd;++index)
|
||||||
|
{
|
||||||
|
const auto& a=samples[index-1].point;
|
||||||
|
const auto& b=samples[index].point;
|
||||||
|
const double dx=b[0]-a[0],dy=b[1]-a[1],dz=b[2]-a[2];
|
||||||
|
gapLength+=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre;
|
||||||
|
}
|
||||||
|
if(gapLength<1.0)continue;
|
||||||
|
FStandalonePassageBridgeRange range;
|
||||||
|
range.passageIndex=passageIndex;
|
||||||
|
range.firstSegment=samples[gapBegin-1].segment;
|
||||||
|
range.lastSegment=samples[cursor].segment;
|
||||||
|
range.unsupportedSamples=gapEnd-gapBegin+1;
|
||||||
|
range.unsupportedLengthMetres=gapLength;
|
||||||
|
range.startPoint=samples[gapBegin-1].point;
|
||||||
|
range.endPoint=samples[cursor].point;
|
||||||
|
PassageBridgeRanges.push_back(range);
|
||||||
|
std::printf("[PassageRockBridgePlan] id=%s first_segment=%zu last_segment=%zu unsupported_samples=%llu gap_length_m=%.3f connected_start=1 connected_end=1 width_m=6.000 thickness_m=3.000..5.000 status=PLANNED\n",
|
||||||
|
passage.stableId.c_str(),range.firstSegment,range.lastSegment,
|
||||||
|
static_cast<unsigned long long>(range.unsupportedSamples),gapLength);
|
||||||
|
}
|
||||||
|
if(std::none_of(PassageBridgeRanges.begin(),PassageBridgeRanges.end(),
|
||||||
|
[passageIndex](const auto& range){return range.passageIndex==passageIndex;}))
|
||||||
|
std::printf("[PassageRockBridgePlan] id=%s spans=0 status=NOT_REQUIRED\n",
|
||||||
|
passage.stableId.c_str());
|
||||||
|
}
|
||||||
|
cache.byLayout.emplace(ResolvedLayout->stableIdentity,PassageBridgeRanges);
|
||||||
|
}
|
||||||
|
|
||||||
static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z)
|
static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z)
|
||||||
{
|
{
|
||||||
constexpr uint64 Mask=0x1FFFFFull;
|
constexpr uint64 Mask=0x1FFFFFull;
|
||||||
@@ -1656,17 +1939,19 @@ private:
|
|||||||
float* OutDetailDisplacement=nullptr,
|
float* OutDetailDisplacement=nullptr,
|
||||||
float* OutCaveInfluence=nullptr,
|
float* OutCaveInfluence=nullptr,
|
||||||
float* OutFloorInfluence=nullptr,
|
float* OutFloorInfluence=nullptr,
|
||||||
bool bApplyEdits=true) const
|
bool bApplyEdits=true,bool bApplyComposition=true) const
|
||||||
{
|
{
|
||||||
if(SharedDensitySource)return SharedDensitySource->AnalyticDensityAt(X,Y,Z,
|
if(SharedDensitySource)return SharedDensitySource->AnalyticDensityAt(X,Y,Z,
|
||||||
bClampNearZero,OutDetailDisplacement,OutCaveInfluence,OutFloorInfluence,bApplyEdits);
|
bClampNearZero,OutDetailDisplacement,OutCaveInfluence,OutFloorInfluence,
|
||||||
|
bApplyEdits,bApplyComposition);
|
||||||
if(OutDetailDisplacement)*OutDetailDisplacement=0.0f;
|
if(OutDetailDisplacement)*OutDetailDisplacement=0.0f;
|
||||||
if(OutCaveInfluence)*OutCaveInfluence=0.0f;
|
if(OutCaveInfluence)*OutCaveInfluence=0.0f;
|
||||||
if(OutFloorInfluence)*OutFloorInfluence=0.0f;
|
if(OutFloorInfluence)*OutFloorInfluence=0.0f;
|
||||||
float IslandDensity=0.0f;
|
float IslandDensity=0.0f;
|
||||||
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
||||||
{
|
{
|
||||||
const float composed=ApplyResolvedStrateComposition(IslandDensity,X,Y,Z);
|
const float composed=bApplyComposition
|
||||||
|
?ApplyResolvedStrateComposition(IslandDensity,X,Y,Z):IslandDensity;
|
||||||
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||||
}
|
}
|
||||||
if(UsesLegacySurfaceField(X,Y,Z))
|
if(UsesLegacySurfaceField(X,Y,Z))
|
||||||
@@ -1674,11 +1959,13 @@ private:
|
|||||||
float NativeDensity=0.0f;
|
float NativeDensity=0.0f;
|
||||||
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||||
{
|
{
|
||||||
|
if(bApplyComposition)
|
||||||
NativeDensity=ApplyResolvedStrateComposition(NativeDensity,X,Y,Z);
|
NativeDensity=ApplyResolvedStrateComposition(NativeDensity,X,Y,Z);
|
||||||
return bApplyEdits?ApplyEditBrushes(NativeDensity,X,Y,Z):NativeDensity;
|
return bApplyEdits?ApplyEditBrushes(NativeDensity,X,Y,Z):NativeDensity;
|
||||||
}
|
}
|
||||||
const float Density=Owner->Generator->GetDensityAt(X,Y,Z);
|
const float Density=Owner->Generator->GetDensityAt(X,Y,Z);
|
||||||
const float composed=ApplyResolvedStrateComposition(Density,X,Y,Z);
|
const float composed=bApplyComposition
|
||||||
|
?ApplyResolvedStrateComposition(Density,X,Y,Z):Density;
|
||||||
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||||
}
|
}
|
||||||
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]
|
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]
|
||||||
@@ -1693,7 +1980,8 @@ private:
|
|||||||
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
||||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
||||||
bClampNearZero?0.05f:1.0e-7f);
|
bClampNearZero?0.05f:1.0e-7f);
|
||||||
const float composed=ApplyResolvedStrateComposition(Density,X,Y,Z);
|
const float composed=bApplyComposition
|
||||||
|
?ApplyResolvedStrateComposition(Density,X,Y,Z):Density;
|
||||||
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||||
}
|
}
|
||||||
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
|
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
|
||||||
@@ -1723,7 +2011,7 @@ private:
|
|||||||
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
||||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
||||||
bClampNearZero?0.05f:1.0e-7f);
|
bClampNearZero?0.05f:1.0e-7f);
|
||||||
Density=ApplyResolvedStrateComposition(Density,X,Y,Z);
|
if(bApplyComposition)Density=ApplyResolvedStrateComposition(Density,X,Y,Z);
|
||||||
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
|
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5863,6 +6151,7 @@ private:
|
|||||||
std::vector<FFarConnectorSegment> FarConnectorSegments;
|
std::vector<FFarConnectorSegment> FarConnectorSegments;
|
||||||
std::unordered_map<FBinKey,std::vector<uint32>,FBinHash> FarConnectorSpatialBins;
|
std::unordered_map<FBinKey,std::vector<uint32>,FBinHash> FarConnectorSpatialBins;
|
||||||
std::vector<FEditBrush> EditBrushes;
|
std::vector<FEditBrush> EditBrushes;
|
||||||
|
std::vector<FStandalonePassageBridgeRange> PassageBridgeRanges;
|
||||||
std::array<FAxisBins,3> AxisBins;
|
std::array<FAxisBins,3> AxisBins;
|
||||||
std::unordered_map<FBinKey,FBinItems,FBinHash> SpatialBins;
|
std::unordered_map<FBinKey,FBinItems,FBinHash> SpatialBins;
|
||||||
std::unordered_map<FBinKey,FBinItems,FBinHash> CriticalAirBins;
|
std::unordered_map<FBinKey,FBinItems,FBinHash> CriticalAirBins;
|
||||||
|
|||||||
@@ -1164,6 +1164,44 @@ namespace VoxelForgeCore
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ResolvedPassageTouchesBox(const ResolvedLayoutSnapshotV3& layout,
|
||||||
|
const FIntVector& minimum,const FIntVector& maximum,std::uint32_t coreVoxelSizeMicrons)
|
||||||
|
{
|
||||||
|
if(layout.passages.empty())return false;
|
||||||
|
constexpr double Q=static_cast<double>(WorldAssemblySubvoxelsPerVoxel);
|
||||||
|
const double voxelsPerMetre=coreVoxelSizeMicrons>0
|
||||||
|
?1000000.0/coreVoxelSizeMicrons:4.0;
|
||||||
|
const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre;
|
||||||
|
const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre;
|
||||||
|
const double tileMinimum[3]={static_cast<double>(minimum.X),
|
||||||
|
static_cast<double>(minimum.Y),static_cast<double>(minimum.Z)};
|
||||||
|
const double tileMaximum[3]={static_cast<double>(maximum.X),
|
||||||
|
static_cast<double>(maximum.Y),static_cast<double>(maximum.Z)};
|
||||||
|
for(const auto& passage:layout.passages)
|
||||||
|
for(std::size_t segment=1;segment<passage.controlPoints.size();++segment)
|
||||||
|
{
|
||||||
|
const auto& a=passage.controlPoints[segment-1];
|
||||||
|
const auto& b=passage.controlPoints[segment];
|
||||||
|
const double radius=static_cast<double>(std::max(
|
||||||
|
a.radiusSubvoxels,b.radiusSubvoxels))/Q;
|
||||||
|
const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels);
|
||||||
|
const double low[3]={
|
||||||
|
std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent,
|
||||||
|
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent,
|
||||||
|
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius
|
||||||
|
-bridgeFootDepthVoxels};
|
||||||
|
const double high[3]={
|
||||||
|
std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent,
|
||||||
|
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent,
|
||||||
|
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius};
|
||||||
|
bool overlap=true;
|
||||||
|
for(int axis=0;axis<3;++axis)
|
||||||
|
overlap&=high[axis]>=tileMinimum[axis]&&low[axis]<=tileMaximum[axis];
|
||||||
|
if(overlap)return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool World::compile(const ShapeBuildRequest& request,const JobScheduler& scheduler,std::string& error)
|
bool World::compile(const ShapeBuildRequest& request,const JobScheduler& scheduler,std::string& error)
|
||||||
@@ -1246,6 +1284,8 @@ namespace VoxelForgeCore
|
|||||||
if(IntersectsBox(feature.minimumSubvoxels,feature.maximumSubvoxels,
|
if(IntersectsBox(feature.minimumSubvoxels,feature.maximumSubvoxels,
|
||||||
subMinimum,subMaximum))return false;
|
subMinimum,subMaximum))return false;
|
||||||
if(RouteTouchesBox(*selectedPlan,subMinimum,subMaximum))return false;
|
if(RouteTouchesBox(*selectedPlan,subMinimum,subMaximum))return false;
|
||||||
|
if(ResolvedPassageTouchesBox(*impl_->resolvedLayout,boundsMin,boundsMax,
|
||||||
|
impl_->owner->CoreVoxelSizeMicrons))return false;
|
||||||
if(OwnerPassageTouchesBox(*impl_->owner,boundsMin,boundsMax))return false;
|
if(OwnerPassageTouchesBox(*impl_->owner,boundsMin,boundsMax))return false;
|
||||||
FStandaloneShapeWorld proofGeometry;
|
FStandaloneShapeWorld proofGeometry;
|
||||||
if(!proofGeometry.BuildShapeDependencyProof(*impl_->owner,boundsMin,boundsMax,
|
if(!proofGeometry.BuildShapeDependencyProof(*impl_->owner,boundsMin,boundsMax,
|
||||||
@@ -1502,6 +1542,28 @@ namespace VoxelForgeCore
|
|||||||
|
|
||||||
float World::densityAt(Vec3d position) const
|
float World::densityAt(Vec3d position) const
|
||||||
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
|
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
|
||||||
|
std::vector<PassageBridgeRangeAuditV1> World::passageBridgeRanges() const
|
||||||
|
{
|
||||||
|
std::vector<PassageBridgeRangeAuditV1> result;
|
||||||
|
if(!impl_||!impl_->shapeReady.load(std::memory_order_acquire)||!impl_->shape)return result;
|
||||||
|
const auto& ranges=impl_->shape->GetPassageBridgeRanges();
|
||||||
|
result.reserve(ranges.size());
|
||||||
|
for(const auto& range:ranges)
|
||||||
|
{
|
||||||
|
PassageBridgeRangeAuditV1 value;
|
||||||
|
value.passageIndex=range.passageIndex;value.firstSegment=range.firstSegment;
|
||||||
|
value.lastSegment=range.lastSegment;value.unsupportedSamples=range.unsupportedSamples;
|
||||||
|
value.unsupportedLengthMetres=range.unsupportedLengthMetres;
|
||||||
|
value.startPointSubvoxels=range.startPoint;value.endPointSubvoxels=range.endPoint;
|
||||||
|
result.push_back(value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
bool World::isPassageBridgeSample(std::size_t passageIndex,std::size_t segmentIndex,
|
||||||
|
Vec3d position) const
|
||||||
|
{return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape
|
||||||
|
&&impl_->shape->IsResolvedPassageBridgeSample(passageIndex,segmentIndex,
|
||||||
|
{position.x,position.y,position.z});}
|
||||||
std::string World::densityDispatchPathAt(Vec3d position) const
|
std::string World::densityDispatchPathAt(Vec3d position) const
|
||||||
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape
|
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape
|
||||||
?impl_->shape->DensityDispatchPathAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z))
|
?impl_->shape->DensityDispatchPathAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z))
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
# World Assembler L26 — passages walkable in rock
|
||||||
|
|
||||||
|
**All four styles pass for both recipes.** Clang and GCC report zero blocked capsule samples, zero
|
||||||
|
unsupported samples, zero grade failures, zero open-air violations, passing naturalness, and passing
|
||||||
|
route bindings. The L24 open-air control and L23 smooth-wall control were detected.
|
||||||
|
|
||||||
|
## Where support was missing
|
||||||
|
|
||||||
|
In the owner SurfaceWorld recipe, the default Spiral passage has 1,083 control points. Its unsupported
|
||||||
|
bridge range is segments 98–1077, a 1,957.395 m gap. Segment 98 is at about `(0,-5,-142.7) m`; segment
|
||||||
|
1077 is at `(-4.6,-1.9,-497.4) m`. Both are inside the SurfaceWorld stratum (`-632..-120 m`), below
|
||||||
|
its native terrain. The gap starts after the TunnelNetwork transition; it is neither TunnelNetwork
|
||||||
|
cave air nor a stratum seam. The first L25 failure sample had only 2/5 native support probes, and
|
||||||
|
following samples had 0/5. Their dispatch path was `SurfaceWorld-native`.
|
||||||
|
|
||||||
|
Core now scans the native field sign and adds a deterministic bridge only over contiguous unsupported
|
||||||
|
ranges. The shared density field gives the bridge at least 5 m width, 3–5 m planned thickness and
|
||||||
|
surface relief. The audit measured two connected ends, at least 5 m width, 256/256 cross-sections,
|
||||||
|
3.5–6 m thickness and 2.5 m relief on the default Spiral. All four SurfaceWorld styles use connected
|
||||||
|
bridges; Floating Islands already has supporting rock and needs none. The capsule clearance gate also
|
||||||
|
checks the resolved player capsule, fixing FI styles 0/1/3 at their bent mouth segments.
|
||||||
|
|
||||||
|
The first bridge export still contained no passage mesh because tile readiness classified all nearby
|
||||||
|
tiles as native-only. Resolved passage and bridge bounds now keep those tiles shape-dependent. The final
|
||||||
|
local export changed `shape_free_desired` from 27/27 to 4/27 and contains the composed bridge density.
|
||||||
|
|
||||||
|
## Eight-row audit
|
||||||
|
|
||||||
|
Counts are from the Clang W12 run; GCC W12 matched every row. `B/U/G` means blocked, unsupported and
|
||||||
|
grade failures. Route bindings are required bindings passed / required bindings.
|
||||||
|
|
||||||
|
| Recipe | Style | Capsule samples | Support samples | B/U/G | Open-air samples / violations | Wall relief | Bindings | Bridge segments / length |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|---|
|
||||||
|
| SurfaceWorld | 0 | 301,140 | 32,765 | 0/0/0 | 3,887,808 / 0 | 0.472 m | 1/1 | 30–293 / 2,004.318 m |
|
||||||
|
| SurfaceWorld | 1 | 284,949 | 31,046 | 0/0/0 | 3,707,328 / 0 | 0.449 m | 1/1 | 33–357 / 1,945.857 m |
|
||||||
|
| SurfaceWorld | 2 | 275,877 | 30,072 | 0/0/0 | 3,664,064 / 0 | 0.410 m | 1/1 | 98–1077 / 1,957.395 m |
|
||||||
|
| SurfaceWorld | 3 | 287,154 | 31,298 | 0/0/0 | 3,741,792 / 0 | 0.552 m | 1/1 | 34–373 / 1,980.607 m |
|
||||||
|
| Floating Islands | 0 | 50,589 | 4,407 | 0/0/0 | 695,616 / 0 | 0.779 m | 2/2 | none |
|
||||||
|
| Floating Islands | 1 | 57,015 | 5,060 | 0/0/0 | 742,016 / 0 | 0.806 m | 2/2 | none |
|
||||||
|
| Floating Islands | 2 | 61,173 | 5,318 | 0/0/0 | 813,632 / 0 | 0.767 m | 2/2 | none |
|
||||||
|
| Floating Islands | 3 | 54,369 | 4,325 | 0/0/0 | 743,456 / 0 | 0.681 m | 2/2 | none |
|
||||||
|
|
||||||
|
Every row passed naturalness (0 m flat run; relief threshold 0.2 m), route binding and strict layout
|
||||||
|
validation. The validator accepted with zero issues and rejected its malformed controls 9/9. Each L24
|
||||||
|
open-air negative control and L23 smooth-wall negative control was detected.
|
||||||
|
|
||||||
|
## Captures
|
||||||
|
|
||||||
|
All are 16:9 JPEGs below 300 KB in `captures/`:
|
||||||
|
|
||||||
|
| Capture | View | Size | SHA-256 |
|
||||||
|
|---|---|---:|---|
|
||||||
|
| `sw-passage-outside.jpg` | Midspan from outside, 1920×1080 | 90,937 B | `28378eeee755faf7446c0b2ce0eb8d2368c79b42f7de4c15e8b2f4d58fb7754d` |
|
||||||
|
| `sw-passage-inside.jpg` | Eye-height at first bridged segment, 1600×900 | 47,519 B | `ccadc33c36366a0ca7b2fcda0a21e4cea67bda107f0e20015171fd620d495249` |
|
||||||
|
| `sw-natural-bridge-wide.jpg` | Wide midspan view, 1920×1080 | 70,192 B | `f4a66fec1adebfbdf5806bb2e7c5a6470e3e8d9dcd60f495d930bbb1d0c5669e` |
|
||||||
|
| `fi-pillar-exterior.jpg` | Floating Islands pillar exterior, 1600×900 | 35,857 B | `b977ff34acee1294a24c2465b1c9d3d78014cff4b8eceb667f317a0a0b46cc67` |
|
||||||
|
|
||||||
|
## Determinism and regressions
|
||||||
|
|
||||||
|
The L25 hash command was rerun with the L26 binaries for both recipes, W1/W8/W12, Clang/GCC:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
timeout 1800s taskset -c 0-11 env LP_NUM_THREADS=12 prlimit --as=6442450944 -- \
|
||||||
|
Saved/Linux/bin/VoxelForgeCoreHeadless-linux26-<clang|gcc> \
|
||||||
|
--world-assembler-prototype --stream-benchmark --stream-pipeline --stream-hash --force-generate \
|
||||||
|
--clip-radius=3 --max-clip-level=8 --far-horizon-lod8-step=0 --workers=N \
|
||||||
|
--recipe=<surface-world|floating-islands> Standalone/data/owner_world.json
|
||||||
|
```
|
||||||
|
|
||||||
|
All six runs per recipe matched. Hash vectors are density / mesh / payload / topology; full detail is
|
||||||
|
960 tiles and whole view is 2,370 tiles.
|
||||||
|
|
||||||
|
| Recipe | Full detail | Whole view | Exact query: density / unclamped / detail / collision |
|
||||||
|
|---|---|---|---|
|
||||||
|
| SurfaceWorld | `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae` | `307d5df80e0165b4 / 1d61f30e5733beca / 3aaef136176c4b12 / aa5ff7c2a821196f` | `c949c4df2ce6f9d9 / c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849` |
|
||||||
|
| Floating Islands | `d18e722b37a2479e / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec` | `51decfb71d282506 / 69448ec1c7fc954f / e2ff37c98fe8e494 / 5beeb7f94fdbf3d4` | `7b7c50ac24ccadcc / 7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf` |
|
||||||
|
|
||||||
|
The 24 m crossing passed for both compilers and recipes. Each processed 314 moving tiles, with identical
|
||||||
|
compiler hashes. Moving-tile vectors (density / mesh / payload / topology): SurfaceWorld
|
||||||
|
`3561ea23485e359f / e8331620fb0fb908 / 765f7dad67c852aa / 3ccc09ac81c05d71`; Floating Islands
|
||||||
|
`e3d0ea833bca4215 / 6b3d0f148ed83d93 / d6ed85f16acd895e / 9745683036522461`.
|
||||||
|
|
||||||
|
SurfaceWorld away from the passage was compared directly between L25 Clang and final L26 Clang at
|
||||||
|
`--camera=10000,10000,-100,0,0`. Full detail, whole view, all LOD0–8 rows and exact-query hashes were
|
||||||
|
identical (12/12). Full-detail vector: `1c6abc5e9c657e98 / a1624a3c19bbe554 / bf98934c9d846f3a /
|
||||||
|
bbdc2a6b821c66d6`; whole-view vector: `40c2be0c7bbdaf28 / 7d9ac2a85020dc7d / c378e48bab2d194f /
|
||||||
|
81014ec4b888107a`; exact-query vector: `482500abbe835b11 / c4948c7179963784 / efcba20c0c70c415 /
|
||||||
|
137e8f6c0b03a624`.
|
||||||
|
|
||||||
|
Strict validation passed on both compilers and recipes. L13 route parity passed on both compilers/recipes:
|
||||||
|
8/8 page pairs, 14 higher-LOD pairs, one edit pair, 782,026 exact-query samples and zero mismatches.
|
||||||
|
For L9 proofs, the passage-centered view has no eligible native blocks. At the same 10 km away-from-route
|
||||||
|
view, SurfaceWorld checked 4,848 blocks / 6,452,688 samples with 1,296 fallbacks and zero violations
|
||||||
|
on both compilers. Floating Islands had its expected 21,504 unknown fallbacks and zero violations.
|
||||||
|
Final headless binary SHA-256: Clang `fc0a5a74f17566384e094cb2429d8925147dd3eb32fb925417393e00c9bb19d5`; GCC
|
||||||
|
`54843bef3a64087e55d3eb9a76fb0856162b6ad9aeb03d6e1c204c3e91d21ca5`.
|
||||||
|
|
||||||
|
Clang built with FP contraction off; GCC used its default FP mode. Both built and started without the L3
|
||||||
|
FP-contract self-test rejecting them. Core evaluator version is 13; FI geometry remains 7 and layout
|
||||||
|
schema V3 is unchanged. Changes are shared Core, standalone audits, Linux build output configuration,
|
||||||
|
and ARCHITECTURE §8.44 / CODEMAP §3.27. No Unreal files changed, and no async path was added. The L4
|
||||||
|
`FLazy`, L7 cache, L9/L12 proofs, L11 readiness/crash handling, L13 binding, L18/L19 ceiling,
|
||||||
|
L21/L22 layout and bands, L23–L25 passage/helix, §8.10 and CLAUDE.md async rules remain intact.
|
||||||
|
|
||||||
|
## Failed and retried runs
|
||||||
|
|
||||||
|
- The first build command used an invalid `clang` variant; corrected to Clang/GCC variants. Early build
|
||||||
|
iterations R3–R6 had compiler errors and were corrected before final binaries were produced.
|
||||||
|
- An unsupported `--world-assembler-audit` option exited 2; the correct option is
|
||||||
|
`--audit-resolved-layout-passages`. One early invocation omitted that flag and performed no passage
|
||||||
|
audit. Early bridge probes also segfaulted while crossing stratum dispatch; final audits use bounded
|
||||||
|
resolved-layout queries.
|
||||||
|
- Support iterations R22–R30 exposed successive failures: FI style 0 had 767, then 27, then 5 blocked /
|
||||||
|
33 unsupported samples; an offset experiment caused 746 unsupported samples; a control-point capsule
|
||||||
|
reference left 7 blocked samples. Correcting the capsule reference cleared those. R30 still had SW
|
||||||
|
bridge sections 254/255, four SW style-1 mouth blocks, and an invalid SW style-2 bridge thickness
|
||||||
|
sample. R31 corrected them; final matrix is clean.
|
||||||
|
- The first final audit batch labeled FI JSON files as Floating Islands but omitted `--recipe`, so those
|
||||||
|
invocations used SurfaceWorld. All 16 style audits were rerun with explicit recipe flags. An initial
|
||||||
|
proof run used `--force-generate`, which disables L9 block auditing; it was repeated without that flag.
|
||||||
|
The validator-only command also exited 2 until the required streaming flags were included; all four
|
||||||
|
corrected strict-validator runs passed.
|
||||||
|
- `FloorProfileBlend` still reports its L25 alternate-style failures: SurfaceWorld style 1 is 2.544 vox
|
||||||
|
over the 2.000 threshold and style 3 is 3.645 vox. Both values are identical in the L25 logs; they are
|
||||||
|
pre-existing and are not capsule, support, open-air or naturalness failures.
|
||||||
|
- Initial capture attempts used a wrong yaw and missed/partially missed the FI export volume. After
|
||||||
|
correcting the view and centering the export, the final captures above were rendered. Several inside
|
||||||
|
camera placements showed wall-only or void-only views; the selected interior frame is at the first
|
||||||
|
bridged segment.
|
||||||
|
|
||||||
|
No run exceeded 30 minutes or remained stuck for 15 minutes. Exports and logs are under `Saved/Linux/`;
|
||||||
|
this round added only this report and the four small JPEGs under `Standalone/`. An unrelated pre-existing
|
||||||
|
untracked `Linux18/exports/` directory was left untouched. No Unreal edits, commit, push or stash were made.
|
||||||
|
|
||||||
|
## Orchestrator review (Claude) — side branch, not merged
|
||||||
|
|
||||||
|
- The audits pass (8/8 rows at 0/0/0), but the SW "natural bridge" is a ~2 km rock ramp spiralling through the
|
||||||
|
open SurfaceWorld sky from the ceiling (≈−143 m) to the ground (≈−497 m). `sw-natural-bridge-wide.jpg` and
|
||||||
|
`sw-passage-outside.jpg` show fragmented floating slabs with thin shard spikes: not natural, and visible
|
||||||
|
from far away.
|
||||||
|
- Decision (delegated by the owner): one shared mechanism for every strate. When a passage must cross a large
|
||||||
|
open void inside a strate, it descends inside a natural rock column (ceiling-to-ground, like a
|
||||||
|
stalactite/stalagmite pair joined), with the spiral tunnel inside, generalising the L25 FI anchor root.
|
||||||
|
No long bridges through open air. Implemented in L27 on `wip/l25-helix`.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
@@ -10,7 +10,11 @@
|
|||||||
#include "VoxelTerrainOpDefinition.h"
|
#include "VoxelTerrainOpDefinition.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
struct FStandaloneParitySample
|
struct FStandaloneParitySample
|
||||||
{
|
{
|
||||||
@@ -20,6 +24,23 @@ struct FStandaloneParitySample
|
|||||||
int32 StrateIndex = INDEX_NONE;
|
int32 StrateIndex = INDEX_NONE;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct FStandalonePassageBridgeRange
|
||||||
|
{
|
||||||
|
std::size_t passageIndex=0;
|
||||||
|
std::size_t firstSegment=0;
|
||||||
|
std::size_t lastSegment=0;
|
||||||
|
std::uint64_t unsupportedSamples=0;
|
||||||
|
double unsupportedLengthMetres=0.0;
|
||||||
|
std::array<double,4> startPoint{};
|
||||||
|
std::array<double,4> endPoint{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FStandalonePassageBridgeCache
|
||||||
|
{
|
||||||
|
std::mutex mutex;
|
||||||
|
std::unordered_map<std::string,std::vector<FStandalonePassageBridgeRange>> byLayout;
|
||||||
|
};
|
||||||
|
|
||||||
struct FStandaloneOwnerWorld
|
struct FStandaloneOwnerWorld
|
||||||
{
|
{
|
||||||
std::unique_ptr<UVoxelSettings> Settings;
|
std::unique_ptr<UVoxelSettings> Settings;
|
||||||
@@ -45,6 +66,8 @@ struct FStandaloneOwnerWorld
|
|||||||
std::uint32_t CoreVoxelSizeMicrons = 250000;
|
std::uint32_t CoreVoxelSizeMicrons = 250000;
|
||||||
std::uint32_t StrateLayoutChunkSizeMicrometres = 8000000;
|
std::uint32_t StrateLayoutChunkSizeMicrometres = 8000000;
|
||||||
std::int64_t StrateLayoutChunkWidthSubvoxels = 8192;
|
std::int64_t StrateLayoutChunkWidthSubvoxels = 8192;
|
||||||
|
std::shared_ptr<FStandalonePassageBridgeCache> PassageBridgeCache =
|
||||||
|
std::make_shared<FStandalonePassageBridgeCache>();
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace StandaloneOwnerLoader
|
namespace StandaloneOwnerLoader
|
||||||
|
|||||||
@@ -458,15 +458,26 @@ namespace StandaloneStreamBenchmark
|
|||||||
bool hasDeepUnsupported=false;
|
bool hasDeepUnsupported=false;
|
||||||
bool passed=false;
|
bool passed=false;
|
||||||
};
|
};
|
||||||
|
struct FPassageSegmentSupportAudit
|
||||||
|
{
|
||||||
|
std::uint64_t centers=0,unsupported=0,nativeUnsupported=0;
|
||||||
|
std::array<double,3> firstUnsupported{};
|
||||||
|
std::array<double,4> firstNativeDensities{};
|
||||||
|
std::uint32_t firstNativeSupportedProbes=0;
|
||||||
|
bool hasUnsupported=false;
|
||||||
|
};
|
||||||
const double coreMetres=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
|
const double coreMetres=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
|
||||||
const auto auditPlayerPath=[&](const VoxelForgeCore::World& Field,
|
const auto auditPlayerPath=[&](const VoxelForgeCore::World& Field,
|
||||||
const std::vector<std::array<double,3>>& path,
|
const std::vector<std::array<double,3>>& path,
|
||||||
double supportStartMetres,double supportStepMetres,
|
double supportStartMetres,double supportStepMetres,
|
||||||
double supportMaximumMetres,std::uint32_t minimumSupportProbes,
|
double supportMaximumMetres,std::uint32_t minimumSupportProbes,
|
||||||
double supportProbeRadiusMetres)
|
double supportProbeRadiusMetres,
|
||||||
|
const VoxelForgeCore::World* nativeField=nullptr,
|
||||||
|
std::vector<FPassageSegmentSupportAudit>* segmentSupport=nullptr)
|
||||||
{
|
{
|
||||||
FWalkAudit audit;
|
FWalkAudit audit;
|
||||||
if(path.size()<2)return audit;
|
if(path.size()<2)return audit;
|
||||||
|
if(segmentSupport)segmentSupport->assign(path.size()-1,{});
|
||||||
constexpr std::array<double,7> heights{{-0.84,-0.60,-0.30,0.0,0.30,0.60,0.84}};
|
constexpr std::array<double,7> heights{{-0.84,-0.60,-0.30,0.0,0.30,0.60,0.84}};
|
||||||
constexpr std::array<std::array<double,2>,8> directions{{
|
constexpr std::array<std::array<double,2>,8> directions{{
|
||||||
{{1.0,0.0}},{{0.7071067811865475,0.7071067811865475}},{{0.0,1.0}},
|
{{1.0,0.0}},{{0.7071067811865475,0.7071067811865475}},{{0.0,1.0}},
|
||||||
@@ -510,6 +521,9 @@ namespace StandaloneStreamBenchmark
|
|||||||
const double y=(a[1]+dy*t)*coreMetres;
|
const double y=(a[1]+dy*t)*coreMetres;
|
||||||
const double z=(a[2]+dz*t)*coreMetres;
|
const double z=(a[2]+dz*t)*coreMetres;
|
||||||
++audit.centers;
|
++audit.centers;
|
||||||
|
FPassageSegmentSupportAudit* segmentAudit=segmentSupport
|
||||||
|
?&(*segmentSupport)[segment-1]:nullptr;
|
||||||
|
if(segmentAudit)++segmentAudit->centers;
|
||||||
for(const double h:heights)
|
for(const double h:heights)
|
||||||
{
|
{
|
||||||
const double capOffset=std::max(0.0,std::abs(h)-0.54);
|
const double capOffset=std::max(0.0,std::abs(h)-0.54);
|
||||||
@@ -526,6 +540,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
{{0.0,supportProbeRadiusMetres}},
|
{{0.0,supportProbeRadiusMetres}},
|
||||||
{{0.0,-supportProbeRadiusMetres}}}};
|
{{0.0,-supportProbeRadiusMetres}}}};
|
||||||
std::uint32_t supportedProbes=0;
|
std::uint32_t supportedProbes=0;
|
||||||
|
std::uint32_t nativeSupportedProbes=0;
|
||||||
double firstUnsupportedX=x,firstUnsupportedY=y,lastUnsupportedDensity=0.0;
|
double firstUnsupportedX=x,firstUnsupportedY=y,lastUnsupportedDensity=0.0;
|
||||||
std::array<bool,5> supportMask{};
|
std::array<bool,5> supportMask{};
|
||||||
for(std::size_t offsetIndex=0;offsetIndex<supportOffsets.size();++offsetIndex)
|
for(std::size_t offsetIndex=0;offsetIndex<supportOffsets.size();++offsetIndex)
|
||||||
@@ -544,12 +559,41 @@ namespace StandaloneStreamBenchmark
|
|||||||
supported=true;break;
|
supported=true;break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(nativeField)
|
||||||
|
{
|
||||||
|
for(double depth=supportStartMetres;
|
||||||
|
depth<=supportMaximumMetres+1.0e-6;depth+=supportStepMetres)
|
||||||
|
if(nativeField->densityAt({supportX/coreMetres,
|
||||||
|
supportY/coreMetres,(footZ-depth)/coreMetres})<0.0f)
|
||||||
|
{++nativeSupportedProbes;break;}
|
||||||
|
}
|
||||||
if(supported){++supportedProbes;supportMask[offsetIndex]=true;}
|
if(supported){++supportedProbes;supportMask[offsetIndex]=true;}
|
||||||
else{firstUnsupportedX=supportX;firstUnsupportedY=supportY;}
|
else{firstUnsupportedX=supportX;firstUnsupportedY=supportY;}
|
||||||
}
|
}
|
||||||
|
if(nativeField&&segmentAudit
|
||||||
|
&&nativeSupportedProbes<minimumSupportProbes)
|
||||||
|
++segmentAudit->nativeUnsupported;
|
||||||
if(supportedProbes<minimumSupportProbes)
|
if(supportedProbes<minimumSupportProbes)
|
||||||
{
|
{
|
||||||
++audit.unsupported;
|
++audit.unsupported;
|
||||||
|
if(segmentAudit)
|
||||||
|
{
|
||||||
|
++segmentAudit->unsupported;
|
||||||
|
if(!segmentAudit->hasUnsupported)
|
||||||
|
{
|
||||||
|
segmentAudit->hasUnsupported=true;
|
||||||
|
segmentAudit->firstUnsupported={x,y,footZ};
|
||||||
|
segmentAudit->firstNativeSupportedProbes=nativeSupportedProbes;
|
||||||
|
if(nativeField)
|
||||||
|
{
|
||||||
|
constexpr std::array<double,4> depths{{0.125,1.0,2.0,4.0}};
|
||||||
|
for(std::size_t depthIndex=0;depthIndex<depths.size();++depthIndex)
|
||||||
|
segmentAudit->firstNativeDensities[depthIndex]=nativeField->densityAt({
|
||||||
|
x/coreMetres,y/coreMetres,
|
||||||
|
(footZ-depths[depthIndex])/coreMetres});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if(!audit.hasDeepUnsupported&&footZ<-80.0)
|
if(!audit.hasDeepUnsupported&&footZ<-80.0)
|
||||||
{
|
{
|
||||||
audit.hasDeepUnsupported=true;
|
audit.hasDeepUnsupported=true;
|
||||||
@@ -682,7 +726,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
}
|
}
|
||||||
struct FPassageOpenAirAudit
|
struct FPassageOpenAirAudit
|
||||||
{
|
{
|
||||||
std::uint64_t supportSamples=0,violations=0,legacyViolations=0;
|
std::uint64_t supportSamples=0,violations=0,legacyViolations=0,bridgeSamples=0;
|
||||||
std::uint64_t routeSupportSamples=0,routeSupportViolations=0;
|
std::uint64_t routeSupportSamples=0,routeSupportViolations=0;
|
||||||
std::array<std::array<double,4>,8> firstViolations{};
|
std::array<std::array<double,4>,8> firstViolations{};
|
||||||
std::size_t firstViolationCount=0;
|
std::size_t firstViolationCount=0;
|
||||||
@@ -708,11 +752,13 @@ namespace StandaloneStreamBenchmark
|
|||||||
{{0.9238795325112867,-0.3826834323650898}}}};
|
{{0.9238795325112867,-0.3826834323650898}}}};
|
||||||
const auto auditPassageOpenAir=[&](const VoxelForgeCore::World& Field,
|
const auto auditPassageOpenAir=[&](const VoxelForgeCore::World& Field,
|
||||||
const VoxelForgeCore::World& Native,
|
const VoxelForgeCore::World& Native,
|
||||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
||||||
|
std::size_t passageIndex)
|
||||||
{
|
{
|
||||||
FPassageOpenAirAudit audit;
|
FPassageOpenAirAudit audit;
|
||||||
const auto inspect=[&](double xMetres,double yMetres,double floorMetres,
|
const auto inspect=[&](double xMetres,double yMetres,double floorMetres,
|
||||||
double depthMetres,double supportThicknessMetres,bool routeSupport=false)
|
double depthMetres,double supportThicknessMetres,bool routeSupport=false,
|
||||||
|
std::size_t passageSegment=std::numeric_limits<std::size_t>::max())
|
||||||
{
|
{
|
||||||
if(depthMetres<=0.0||depthMetres>supportThicknessMetres+1.0e-6)return;
|
if(depthMetres<=0.0||depthMetres>supportThicknessMetres+1.0e-6)return;
|
||||||
++audit.supportSamples;
|
++audit.supportSamples;
|
||||||
@@ -727,6 +773,10 @@ namespace StandaloneStreamBenchmark
|
|||||||
++audit.legacyViolations;
|
++audit.legacyViolations;
|
||||||
if(nativeDensity>=0.0f&&passageDensity<0.0f)
|
if(nativeDensity>=0.0f&&passageDensity<0.0f)
|
||||||
{
|
{
|
||||||
|
const bool connectedBridge=!routeSupport
|
||||||
|
&&passageSegment!=std::numeric_limits<std::size_t>::max()
|
||||||
|
&&Field.isPassageBridgeSample(passageIndex,passageSegment,sample);
|
||||||
|
if(connectedBridge){++audit.bridgeSamples;return;}
|
||||||
audit.routeSupportViolations+=routeSupport;
|
audit.routeSupportViolations+=routeSupport;
|
||||||
if(audit.firstViolationCount<audit.firstViolations.size())
|
if(audit.firstViolationCount<audit.firstViolations.size())
|
||||||
audit.firstViolations[audit.firstViolationCount++]={xMetres,yMetres,
|
audit.firstViolations[audit.firstViolationCount++]={xMetres,yMetres,
|
||||||
@@ -736,7 +786,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
};
|
};
|
||||||
const auto inspectFloorDisc=[&](double xMetres,double yMetres,double floorMetres,
|
const auto inspectFloorDisc=[&](double xMetres,double yMetres,double floorMetres,
|
||||||
double supportRadiusMetres,double depthMetres,double supportThicknessMetres,
|
double supportRadiusMetres,double depthMetres,double supportThicknessMetres,
|
||||||
bool routeSupport=false)
|
bool routeSupport=false,
|
||||||
|
std::size_t passageSegment=std::numeric_limits<std::size_t>::max())
|
||||||
{
|
{
|
||||||
const std::size_t radialSteps=static_cast<std::size_t>(std::ceil(
|
const std::size_t radialSteps=static_cast<std::size_t>(std::ceil(
|
||||||
supportRadiusMetres/PassageSupportAuditSpacingMetres));
|
supportRadiusMetres/PassageSupportAuditSpacingMetres));
|
||||||
@@ -746,7 +797,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
radialStep*PassageSupportAuditSpacingMetres);
|
radialStep*PassageSupportAuditSpacingMetres);
|
||||||
for(const auto& direction:PassageSupportAuditDirections)
|
for(const auto& direction:PassageSupportAuditDirections)
|
||||||
inspect(xMetres+direction[0]*radial,yMetres+direction[1]*radial,
|
inspect(xMetres+direction[0]*radial,yMetres+direction[1]*radial,
|
||||||
floorMetres,depthMetres,supportThicknessMetres,routeSupport);
|
floorMetres,depthMetres,supportThicknessMetres,routeSupport,
|
||||||
|
passageSegment);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for(std::size_t segment=1;segment<passage.controlPoints.size();++segment)
|
for(std::size_t segment=1;segment<passage.controlPoints.size();++segment)
|
||||||
@@ -772,7 +824,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
+(qToMetres(b.radiusSubvoxels)-qToMetres(a.radiusSubvoxels))*t;
|
+(qToMetres(b.radiusSubvoxels)-qToMetres(a.radiusSubvoxels))*t;
|
||||||
const double floorZ=centerZ-radius+0.20;
|
const double floorZ=centerZ-radius+0.20;
|
||||||
for(const double depth:PassageSupportAuditDepthsMetres)
|
for(const double depth:PassageSupportAuditDepthsMetres)
|
||||||
inspectFloorDisc(centerX,centerY,floorZ,radius,depth,1.0);
|
inspectFloorDisc(centerX,centerY,floorZ,radius,depth,1.0,
|
||||||
|
false,segment-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||||
@@ -841,6 +894,124 @@ namespace StandaloneStreamBenchmark
|
|||||||
}
|
}
|
||||||
return audit;
|
return audit;
|
||||||
};
|
};
|
||||||
|
struct FPassageBridgeAudit
|
||||||
|
{
|
||||||
|
std::uint64_t spans=0,connectedEnds=0,crossSections=0,crossSectionFailures=0;
|
||||||
|
double minimumThicknessMetres=std::numeric_limits<double>::infinity();
|
||||||
|
double maximumThicknessMetres=0.0;
|
||||||
|
double thicknessReliefPeakToPeakMetres=0.0;
|
||||||
|
bool passed=true;
|
||||||
|
};
|
||||||
|
const auto auditPassageBridges=[&](const VoxelForgeCore::World& Field,
|
||||||
|
const VoxelForgeCore::World& Native,
|
||||||
|
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
||||||
|
std::size_t passageIndex)
|
||||||
|
{
|
||||||
|
FPassageBridgeAudit audit;
|
||||||
|
const double qMetres=coreMetres
|
||||||
|
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||||
|
constexpr std::array<std::array<double,2>,5> supportOffsets{{
|
||||||
|
{{0.0,0.0}},{{0.45,0.0}},{{-0.45,0.0}},{{0.0,0.45}},{{0.0,-0.45}}}};
|
||||||
|
const auto endpointConnected=[&](const std::array<double,4>& point)
|
||||||
|
{
|
||||||
|
const double x=point[0]*qMetres,y=point[1]*qMetres;
|
||||||
|
const double floor=point[2]*qMetres-point[3]*qMetres+0.13;
|
||||||
|
std::uint32_t supported=0;
|
||||||
|
for(const auto& offset:supportOffsets)
|
||||||
|
{
|
||||||
|
bool solid=false;
|
||||||
|
for(const double depth:{0.125,0.25})
|
||||||
|
if(Native.densityAt({(x+offset[0])/coreMetres,
|
||||||
|
(y+offset[1])/coreMetres,(floor-depth)/coreMetres})<0.0f)
|
||||||
|
{solid=true;break;}
|
||||||
|
supported+=solid;
|
||||||
|
}
|
||||||
|
return supported>=3;
|
||||||
|
};
|
||||||
|
const auto bridgeSolidAt=[&](std::size_t segment,double x,double y,double z)
|
||||||
|
{
|
||||||
|
return Field.isPassageBridgeSample(passageIndex,segment,
|
||||||
|
{x/coreMetres,y/coreMetres,z/coreMetres});
|
||||||
|
};
|
||||||
|
const auto thicknessAt=[&](std::size_t segment,double x,double y,double floor)
|
||||||
|
{
|
||||||
|
double thickness=0.0;
|
||||||
|
for(double depth=0.125;depth<=6.0+1.0e-6;depth+=0.125)
|
||||||
|
{
|
||||||
|
if(!bridgeSolidAt(segment,x,y,floor-depth))break;
|
||||||
|
thickness=depth;
|
||||||
|
}
|
||||||
|
return thickness;
|
||||||
|
};
|
||||||
|
for(const auto& range:Field.passageBridgeRanges())
|
||||||
|
{
|
||||||
|
if(range.passageIndex!=passageIndex
|
||||||
|
||range.lastSegment+1>=passage.controlPoints.size())continue;
|
||||||
|
++audit.spans;
|
||||||
|
const bool connectedStart=endpointConnected(range.startPointSubvoxels);
|
||||||
|
const bool connectedEnd=endpointConnected(range.endPointSubvoxels);
|
||||||
|
audit.connectedEnds+=connectedStart+connectedEnd;
|
||||||
|
const std::size_t segmentCount=range.lastSegment-range.firstSegment+1;
|
||||||
|
const std::size_t probeCount=std::min<std::size_t>(256,segmentCount);
|
||||||
|
double spanMinimumThickness=std::numeric_limits<double>::infinity();
|
||||||
|
double spanMaximumThickness=0.0;
|
||||||
|
std::uint64_t spanCrossSections=0,spanCrossSectionFailures=0;
|
||||||
|
for(std::size_t probe=0;probe<probeCount;++probe)
|
||||||
|
{
|
||||||
|
const std::size_t segment=range.firstSegment
|
||||||
|
+std::min(segmentCount-1,(probe*segmentCount)/probeCount);
|
||||||
|
const auto& a=passage.controlPoints[segment];
|
||||||
|
const auto& b=passage.controlPoints[segment+1];
|
||||||
|
const double ax=qToMetres(a.positionSubvoxels[0]);
|
||||||
|
const double ay=qToMetres(a.positionSubvoxels[1]);
|
||||||
|
const double az=qToMetres(a.positionSubvoxels[2]);
|
||||||
|
const double bx=qToMetres(b.positionSubvoxels[0]);
|
||||||
|
const double by=qToMetres(b.positionSubvoxels[1]);
|
||||||
|
const double bz=qToMetres(b.positionSubvoxels[2]);
|
||||||
|
const double x=(ax+bx)*0.5,y=(ay+by)*0.5;
|
||||||
|
const double z=(az+bz)*0.5;
|
||||||
|
const double radius=(qToMetres(a.radiusSubvoxels)
|
||||||
|
+qToMetres(b.radiusSubvoxels))*0.5;
|
||||||
|
const double floor=z-radius+0.13;
|
||||||
|
const double thickness=thicknessAt(segment,x,y,floor);
|
||||||
|
spanMinimumThickness=std::min(spanMinimumThickness,thickness);
|
||||||
|
spanMaximumThickness=std::max(spanMaximumThickness,thickness);
|
||||||
|
audit.minimumThicknessMetres=std::min(audit.minimumThicknessMetres,thickness);
|
||||||
|
audit.maximumThicknessMetres=std::max(audit.maximumThicknessMetres,thickness);
|
||||||
|
const double dx=bx-ax,dy=by-ay,horizontal=std::hypot(dx,dy);
|
||||||
|
if(horizontal<=1.0e-6)continue;
|
||||||
|
const double nx=dy/horizontal,ny=-dx/horizontal;
|
||||||
|
bool crossSectionSolid=true;
|
||||||
|
for(const double offset:{-2.5,0.0,2.5})
|
||||||
|
crossSectionSolid&=bridgeSolidAt(segment,x+nx*offset,
|
||||||
|
y+ny*offset,floor-2.0);
|
||||||
|
++audit.crossSections;
|
||||||
|
++spanCrossSections;
|
||||||
|
spanCrossSectionFailures+=!crossSectionSolid;
|
||||||
|
audit.crossSectionFailures+=!crossSectionSolid;
|
||||||
|
}
|
||||||
|
const double relief=std::isfinite(spanMinimumThickness)
|
||||||
|
?spanMaximumThickness-spanMinimumThickness:0.0;
|
||||||
|
audit.thicknessReliefPeakToPeakMetres=std::max(
|
||||||
|
audit.thicknessReliefPeakToPeakMetres,relief);
|
||||||
|
const bool spanPass=connectedStart&&connectedEnd
|
||||||
|
&&spanCrossSections>=3&&spanCrossSectionFailures==0
|
||||||
|
&&spanMinimumThickness>=2.75
|
||||||
|
&&spanMaximumThickness<=6.5
|
||||||
|
&&audit.thicknessReliefPeakToPeakMetres>=0.20;
|
||||||
|
audit.passed&=spanPass;
|
||||||
|
std::printf("[PassageRockBridgeAudit] id=%s segments=%zu..%zu gap_m=%.3f connected_ends=%d/2 width_at_least_m=5.000 cross_sections=%llu/%llu thickness_m=%.3f..%.3f relief_p2p_m=%.3f status=%s\n",
|
||||||
|
passage.stableId.c_str(),range.firstSegment,range.lastSegment,
|
||||||
|
range.unsupportedLengthMetres,static_cast<int>(connectedStart)
|
||||||
|
+static_cast<int>(connectedEnd),
|
||||||
|
static_cast<unsigned long long>(spanCrossSections-spanCrossSectionFailures),
|
||||||
|
static_cast<unsigned long long>(spanCrossSections),
|
||||||
|
std::isfinite(spanMinimumThickness)?spanMinimumThickness:0.0,
|
||||||
|
spanMaximumThickness,relief,
|
||||||
|
spanPass?"PASS":"FAIL");
|
||||||
|
}
|
||||||
|
return audit;
|
||||||
|
};
|
||||||
if(Assembly)
|
if(Assembly)
|
||||||
for(const auto& stratum:ResolvedLayout->strates)
|
for(const auto& stratum:ResolvedLayout->strates)
|
||||||
{
|
{
|
||||||
@@ -939,9 +1110,12 @@ namespace StandaloneStreamBenchmark
|
|||||||
};
|
};
|
||||||
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0;
|
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0;
|
||||||
std::uint64_t openAirViolations=0,openAirLegacyViolations=0,openAirSamples=0;
|
std::uint64_t openAirViolations=0,openAirLegacyViolations=0,openAirSamples=0;
|
||||||
std::uint64_t openAirAudits=0;
|
std::uint64_t openAirAudits=0,openAirBridgeSamples=0,bridgeSpans=0,
|
||||||
for(const auto& passage:ResolvedLayout->passages)
|
bridgeConnectedEnds=0;
|
||||||
|
bool bridgeAuditsPassed=true;
|
||||||
|
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||||
{
|
{
|
||||||
|
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||||
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
||||||
for(const auto& point:passage.controlPoints)
|
for(const auto& point:passage.controlPoints)
|
||||||
{
|
{
|
||||||
@@ -951,12 +1125,21 @@ namespace StandaloneStreamBenchmark
|
|||||||
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
||||||
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
||||||
}
|
}
|
||||||
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.125,0.125,0.25,3,0.45);
|
std::vector<FPassageSegmentSupportAudit> segmentSupport;
|
||||||
|
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.125,0.125,0.25,3,0.45,
|
||||||
|
&blockedWorld,&segmentSupport);
|
||||||
const FPassageNaturalnessAudit naturalAudit=auditPassageNaturalness(Shape,passage);
|
const FPassageNaturalnessAudit naturalAudit=auditPassageNaturalness(Shape,passage);
|
||||||
const FPassageOpenAirAudit openAirAudit=auditPassageOpenAir(Shape,blockedWorld,passage);
|
const FPassageOpenAirAudit openAirAudit=auditPassageOpenAir(Shape,blockedWorld,
|
||||||
|
passage,passageIndex);
|
||||||
|
const FPassageBridgeAudit bridgeAudit=auditPassageBridges(Shape,blockedWorld,
|
||||||
|
passage,passageIndex);
|
||||||
openAirSamples+=openAirAudit.supportSamples;
|
openAirSamples+=openAirAudit.supportSamples;
|
||||||
openAirViolations+=openAirAudit.violations;
|
openAirViolations+=openAirAudit.violations;
|
||||||
openAirLegacyViolations+=openAirAudit.legacyViolations;
|
openAirLegacyViolations+=openAirAudit.legacyViolations;
|
||||||
|
openAirBridgeSamples+=openAirAudit.bridgeSamples;
|
||||||
|
bridgeSpans+=bridgeAudit.spans;
|
||||||
|
bridgeConnectedEnds+=bridgeAudit.connectedEnds;
|
||||||
|
bridgeAuditsPassed&=bridgeAudit.passed;
|
||||||
++openAirAudits;
|
++openAirAudits;
|
||||||
const auto radialSquared=[](const std::array<std::int64_t,3>& p)
|
const auto radialSquared=[](const std::array<std::int64_t,3>& p)
|
||||||
{return p[0]*p[0]+p[1]*p[1];};
|
{return p[0]*p[0]+p[1]*p[1];};
|
||||||
@@ -984,9 +1167,10 @@ namespace StandaloneStreamBenchmark
|
|||||||
static_cast<unsigned long long>(naturalAudit.wallSamples),
|
static_cast<unsigned long long>(naturalAudit.wallSamples),
|
||||||
static_cast<unsigned long long>(naturalAudit.missingWallSamples),
|
static_cast<unsigned long long>(naturalAudit.missingWallSamples),
|
||||||
naturalAudit.passed?"PASS":"FAIL");
|
naturalAudit.passed?"PASS":"FAIL");
|
||||||
std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%llu l24_ungated_violations=%llu route_support_samples=%llu route_support_violations=%llu spacing_m=%.3f status=%s\n",
|
std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%llu bridge_samples=%llu l24_ungated_violations=%llu route_support_samples=%llu route_support_violations=%llu spacing_m=%.3f status=%s\n",
|
||||||
passage.stableId.c_str(),static_cast<unsigned long long>(openAirAudit.supportSamples),
|
passage.stableId.c_str(),static_cast<unsigned long long>(openAirAudit.supportSamples),
|
||||||
static_cast<unsigned long long>(openAirAudit.violations),
|
static_cast<unsigned long long>(openAirAudit.violations),
|
||||||
|
static_cast<unsigned long long>(openAirAudit.bridgeSamples),
|
||||||
static_cast<unsigned long long>(openAirAudit.legacyViolations),
|
static_cast<unsigned long long>(openAirAudit.legacyViolations),
|
||||||
static_cast<unsigned long long>(openAirAudit.routeSupportSamples),
|
static_cast<unsigned long long>(openAirAudit.routeSupportSamples),
|
||||||
static_cast<unsigned long long>(openAirAudit.routeSupportViolations),
|
static_cast<unsigned long long>(openAirAudit.routeSupportViolations),
|
||||||
@@ -1000,8 +1184,58 @@ namespace StandaloneStreamBenchmark
|
|||||||
for(std::size_t i=0;i<passageAudit.firstBlockedCount;++i)
|
for(std::size_t i=0;i<passageAudit.firstBlockedCount;++i)
|
||||||
{
|
{
|
||||||
const auto& sample=passageAudit.firstBlocked[i];
|
const auto& sample=passageAudit.firstBlocked[i];
|
||||||
std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
double nearestDistanceSquared=std::numeric_limits<double>::infinity();
|
||||||
passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3]);
|
std::size_t nearestSegment=0;
|
||||||
|
double nearestT=0.0;
|
||||||
|
double nearestPassageSdf=std::numeric_limits<double>::infinity();
|
||||||
|
std::size_t nearestPassageSegment=0;
|
||||||
|
for(std::size_t segment=0;segment+1<path.size();++segment)
|
||||||
|
{
|
||||||
|
const auto& a=path[segment];const auto& b=path[segment+1];
|
||||||
|
const double dx=b[0]-a[0],dy=b[1]-a[1],dz=b[2]-a[2];
|
||||||
|
const double lengthSquared=dx*dx+dy*dy+dz*dz;
|
||||||
|
const double t=lengthSquared>0.0?std::clamp(
|
||||||
|
((sample[0]/coreMetres-a[0])*dx+(sample[1]/coreMetres-a[1])*dy
|
||||||
|
+(sample[2]/coreMetres-a[2])*dz)/lengthSquared,0.0,1.0):0.0;
|
||||||
|
const double ex=sample[0]/coreMetres-(a[0]+t*dx);
|
||||||
|
const double ey=sample[1]/coreMetres-(a[1]+t*dy);
|
||||||
|
const double ez=sample[2]/coreMetres-(a[2]+t*dz);
|
||||||
|
const double distanceSquared=ex*ex+ey*ey+ez*ez;
|
||||||
|
if(distanceSquared<nearestDistanceSquared)
|
||||||
|
{nearestDistanceSquared=distanceSquared;nearestSegment=segment;nearestT=t;}
|
||||||
|
}
|
||||||
|
for(std::size_t segment=0;segment+1<passage.controlPoints.size();++segment)
|
||||||
|
{
|
||||||
|
const auto& a=passage.controlPoints[segment];
|
||||||
|
const auto& b=passage.controlPoints[segment+1];
|
||||||
|
const double ax=qToMetres(a.positionSubvoxels[0]);
|
||||||
|
const double ay=qToMetres(a.positionSubvoxels[1]);
|
||||||
|
const double az=qToMetres(a.positionSubvoxels[2]);
|
||||||
|
const double dx=qToMetres(b.positionSubvoxels[0]-a.positionSubvoxels[0]);
|
||||||
|
const double dy=qToMetres(b.positionSubvoxels[1]-a.positionSubvoxels[1]);
|
||||||
|
const double dz=qToMetres(b.positionSubvoxels[2]-a.positionSubvoxels[2]);
|
||||||
|
const double lengthSquared=dx*dx+dy*dy+dz*dz;
|
||||||
|
const double t=lengthSquared>0.0?std::clamp(
|
||||||
|
((sample[0]-ax)*dx+(sample[1]-ay)*dy+(sample[2]-az)*dz)
|
||||||
|
/lengthSquared,0.0,1.0):0.0;
|
||||||
|
const double ex=sample[0]-(ax+t*dx),ey=sample[1]-(ay+t*dy),
|
||||||
|
ez=sample[2]-(az+t*dz);
|
||||||
|
const double radius=qToMetres(a.radiusSubvoxels)
|
||||||
|
+(qToMetres(b.radiusSubvoxels)-qToMetres(a.radiusSubvoxels))*t;
|
||||||
|
const double signedDistance=std::sqrt(ex*ex+ey*ey+ez*ez)-radius;
|
||||||
|
if(signedDistance<nearestPassageSdf)
|
||||||
|
{nearestPassageSdf=signedDistance;nearestPassageSegment=segment;}
|
||||||
|
}
|
||||||
|
const VoxelForgeCore::Vec3d nativePosition{sample[0]/coreMetres,
|
||||||
|
sample[1]/coreMetres,sample[2]/coreMetres};
|
||||||
|
const float nativeDensity=blockedWorld.densityAt(nativePosition);
|
||||||
|
const bool bridgeVolume=Shape.isPassageBridgeSample(passageIndex,
|
||||||
|
nearestSegment,nativePosition);
|
||||||
|
std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f native_density=%.6f segment=%zu t=%.3f passage_segment=%zu passage_sdf_m=%.3f bridge_volume=%d native_path=%s\n",
|
||||||
|
passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3],
|
||||||
|
nativeDensity,nearestSegment,nearestT,nearestPassageSegment,
|
||||||
|
nearestPassageSdf,bridgeVolume?1:0,
|
||||||
|
blockedWorld.densityDispatchPathAt(nativePosition).c_str());
|
||||||
}
|
}
|
||||||
for(std::size_t i=0;i<passageAudit.firstUnsupportedCount;++i)
|
for(std::size_t i=0;i<passageAudit.firstUnsupportedCount;++i)
|
||||||
{
|
{
|
||||||
@@ -1010,6 +1244,30 @@ namespace StandaloneStreamBenchmark
|
|||||||
passageAudit.firstUnsupported[i][1],passageAudit.firstUnsupported[i][2],
|
passageAudit.firstUnsupported[i][1],passageAudit.firstUnsupported[i][2],
|
||||||
passageAudit.firstUnsupported[i][3]);
|
passageAudit.firstUnsupported[i][3]);
|
||||||
}
|
}
|
||||||
|
for(std::size_t segment=0;segment<segmentSupport.size();++segment)
|
||||||
|
{
|
||||||
|
const auto& item=segmentSupport[segment];
|
||||||
|
if(!item.hasUnsupported)continue;
|
||||||
|
const auto& a=path[segment];const auto& b=path[segment+1];
|
||||||
|
const auto& sample=item.firstUnsupported;
|
||||||
|
const double firstX=sample[0]/coreMetres;
|
||||||
|
const double firstY=sample[1]/coreMetres;
|
||||||
|
const double firstZ=(sample[2]-0.125)/coreMetres;
|
||||||
|
const VoxelForgeCore::Vec3d bridgeProbe{sample[0]/coreMetres,
|
||||||
|
sample[1]/coreMetres,(sample[2]-0.25)/coreMetres};
|
||||||
|
const bool bridgeAtSupport=Shape.isPassageBridgeSample(passageIndex,
|
||||||
|
segment,bridgeProbe);
|
||||||
|
std::printf("[ResolvedPassageSupportSegment] id=%s segment=%zu route_z_m=%.3f..%.3f centers=%llu unsupported=%llu native_unsupported=%llu first_foot_m=(%.3f,%.3f,%.3f) bridge_at_d025=%d native_support_probes=%u/5 native_density_d0125_d1_d2_d4=(%.6f,%.6f,%.6f,%.6f) native_path=%s\n",
|
||||||
|
passage.stableId.c_str(),segment,
|
||||||
|
std::min(a[2],b[2])*coreMetres,std::max(a[2],b[2])*coreMetres,
|
||||||
|
static_cast<unsigned long long>(item.centers),
|
||||||
|
static_cast<unsigned long long>(item.unsupported),
|
||||||
|
static_cast<unsigned long long>(item.nativeUnsupported),
|
||||||
|
sample[0],sample[1],sample[2],bridgeAtSupport?1:0,
|
||||||
|
item.firstNativeSupportedProbes,item.firstNativeDensities[0],item.firstNativeDensities[1],
|
||||||
|
item.firstNativeDensities[2],item.firstNativeDensities[3],
|
||||||
|
blockedWorld.densityDispatchPathAt({firstX,firstY,firstZ}).c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0;
|
std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0;
|
||||||
struct FRouteAuditInput
|
struct FRouteAuditInput
|
||||||
@@ -1082,7 +1340,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
}
|
}
|
||||||
const FWalkAudit routeAudit=auditPlayerPath(Shape,route,supportStartMetres,
|
const FWalkAudit routeAudit=auditPlayerPath(Shape,route,supportStartMetres,
|
||||||
supportStepMetres,supportMaximumMetres,minimumSupportProbes,
|
supportStepMetres,supportMaximumMetres,minimumSupportProbes,
|
||||||
supportProbeRadiusMetres);
|
supportProbeRadiusMetres,nullptr,nullptr);
|
||||||
routeAuditInputs.push_back({stratum.strateIndex,route,supportStartMetres,
|
routeAuditInputs.push_back({stratum.strateIndex,route,supportStartMetres,
|
||||||
supportStepMetres,supportMaximumMetres,supportProbeRadiusMetres,
|
supportStepMetres,supportMaximumMetres,supportProbeRadiusMetres,
|
||||||
minimumSupportProbes});
|
minimumSupportProbes});
|
||||||
@@ -1151,7 +1409,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
||||||
}
|
}
|
||||||
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
||||||
0.125,0.125,0.25,3,0.45);
|
0.125,0.125,0.25,3,0.45,nullptr,nullptr);
|
||||||
blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed;
|
blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed;
|
||||||
std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n",
|
std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n",
|
||||||
passage.stableId.c_str(),static_cast<unsigned long long>(blockedAudit.blocked),
|
passage.stableId.c_str(),static_cast<unsigned long long>(blockedAudit.blocked),
|
||||||
@@ -1161,7 +1419,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
{
|
{
|
||||||
const FWalkAudit controlRoute=auditPlayerPath(blockedWorld,input.path,
|
const FWalkAudit controlRoute=auditPlayerPath(blockedWorld,input.path,
|
||||||
input.supportStart,input.supportStep,input.supportMaximum,
|
input.supportStart,input.supportStep,input.supportMaximum,
|
||||||
input.minimumSupportProbes,input.supportProbeRadius);
|
input.minimumSupportProbes,input.supportProbeRadius,nullptr,nullptr);
|
||||||
std::printf("[ResolvedStrateRouteBlockedControl] strate=%d blocked=%llu unsupported=%llu status=%s\n",
|
std::printf("[ResolvedStrateRouteBlockedControl] strate=%d blocked=%llu unsupported=%llu status=%s\n",
|
||||||
input.strateIndex,static_cast<unsigned long long>(controlRoute.blocked),
|
input.strateIndex,static_cast<unsigned long long>(controlRoute.blocked),
|
||||||
static_cast<unsigned long long>(controlRoute.unsupported),
|
static_cast<unsigned long long>(controlRoute.unsupported),
|
||||||
@@ -1171,13 +1429,19 @@ namespace StandaloneStreamBenchmark
|
|||||||
std::printf("[PassageOpenAirNegativeControl] source=L24_ungated_support unsupported_samples=%llu detected=%s\n",
|
std::printf("[PassageOpenAirNegativeControl] source=L24_ungated_support unsupported_samples=%llu detected=%s\n",
|
||||||
static_cast<unsigned long long>(openAirLegacyViolations),
|
static_cast<unsigned long long>(openAirLegacyViolations),
|
||||||
l24OpenAirControlDetected?"true":"false");
|
l24OpenAirControlDetected?"true":"false");
|
||||||
std::printf("[PassageOpenAirAuditSummary] interfaces=%llu samples=%llu violations=%llu l24_negative_control=%s status=%s\n",
|
std::printf("[PassageOpenAirAuditSummary] interfaces=%llu samples=%llu violations=%llu bridge_samples=%llu l24_negative_control=%s status=%s\n",
|
||||||
static_cast<unsigned long long>(openAirAudits),
|
static_cast<unsigned long long>(openAirAudits),
|
||||||
static_cast<unsigned long long>(openAirSamples),
|
static_cast<unsigned long long>(openAirSamples),
|
||||||
static_cast<unsigned long long>(openAirViolations),
|
static_cast<unsigned long long>(openAirViolations),
|
||||||
|
static_cast<unsigned long long>(openAirBridgeSamples),
|
||||||
l24OpenAirControlDetected?"DETECTED":"MISSED",
|
l24OpenAirControlDetected?"DETECTED":"MISSED",
|
||||||
openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
||||||
?"PASS":"FAIL");
|
?"PASS":"FAIL");
|
||||||
|
std::printf("[PassageRockBridgeAuditSummary] spans=%llu connected_ends=%llu/%llu status=%s\n",
|
||||||
|
static_cast<unsigned long long>(bridgeSpans),
|
||||||
|
static_cast<unsigned long long>(bridgeConnectedEnds),
|
||||||
|
static_cast<unsigned long long>(bridgeSpans*2),
|
||||||
|
bridgeAuditsPassed?"PASS":"FAIL");
|
||||||
const bool l23SmoothWallDetected=!looksNatural(0.0,0.0);
|
const bool l23SmoothWallDetected=!looksNatural(0.0,0.0);
|
||||||
std::printf("[PassageNaturalnessNegativeControl] source=L23_smooth_unrelieved_wall relief_p2p_m=0.000 threshold_m=%.3f detected=%s\n",
|
std::printf("[PassageNaturalnessNegativeControl] source=L23_smooth_unrelieved_wall relief_p2p_m=0.000 threshold_m=%.3f detected=%s\n",
|
||||||
MinimumWallReliefMetres,l23SmoothWallDetected?"true":"false");
|
MinimumWallReliefMetres,l23SmoothWallDetected?"true":"false");
|
||||||
@@ -1188,6 +1452,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
const bool auditPassed=passageCount>0&&passagePasses==passageCount
|
const bool auditPassed=passageCount>0&&passagePasses==passageCount
|
||||||
&&naturalPasses==passageCount&&l23SmoothWallDetected
|
&&naturalPasses==passageCount&&l23SmoothWallDetected
|
||||||
&&openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
&&openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
||||||
|
&&bridgeAuditsPassed
|
||||||
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
||||||
&&routeBindingFailures==0
|
&&routeBindingFailures==0
|
||||||
&&blockedCompiled&&blockedControls==passageCount;
|
&&blockedCompiled&&blockedControls==passageCount;
|
||||||
|
|||||||
Reference in new issue
Block a user