Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f23ab7b9fa | ||
|
|
07b3fe2852 | ||
|
|
37a223046c | ||
|
|
ef789aaa00 |
No files matched your search
@@ -1983,6 +1983,74 @@ cutaway views. Both affect captures only, not generated geometry.
|
||||
[`Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md)
|
||||
records the evidence and owner-facing caveats.
|
||||
|
||||
### 8.43 Enclosed FloatingIslands helix and native-solid passage support — World Assembler L25
|
||||
|
||||
L24's support tread was composited into the passage field without requiring native solid terrain at
|
||||
the support sample. A 6 m root could not contain the resolved helix either: the centreline reaches
|
||||
5.562 m from the anchor axis and the mouth bore is 4.5 m. With the old root radius, the tread could
|
||||
therefore read as a free-floating spiral outside the rock. This was not a unit conversion error.
|
||||
|
||||
The shared passage composer now emits route-mouth support, tread, and fallback support only where
|
||||
the pre-passage native density is solid. The open-air audit samples each support footprint at 0.25 m
|
||||
spacing and two depths, including route-mouth shelves; its L24 ungated-support control must report a
|
||||
violation. The FI anchor root is now a relief-bearing 13.25 m nominal rock column. Across five
|
||||
heights its measured radius is 12.0–13.375 m. Taking the maximum 5.562 m centreline radius and the
|
||||
full 4.5 m mouth bore at every height gives a conservative minimum wall of 1.938 m, exceeding the
|
||||
previous 1.5 m reserve. The authored mid-bore remains 3 m. The helix is selected from its route-bound
|
||||
root point and joins the lower route before the tunnel hands off through the shared SW ceiling.
|
||||
|
||||
The configured FI Spiral passes the capsule/support/grade audit with zero blocked, unsupported, or
|
||||
grade-failure samples; the L23 smooth-wall control fails naturalness and the L24 ungated-support
|
||||
control fails the open-air audit. The other style overrides also pass the open-air audit, but their
|
||||
capsule results are recorded individually in the L25 report because some overrides are not
|
||||
walkable after floating support is suppressed. Evaluator version is 12, FI geometry version is 7,
|
||||
and resolved-layout schema V3 remains unchanged. The L25 report records measurements, hashes,
|
||||
captures, style results, and the single Unreal adapter version edit.
|
||||
|
||||
[`Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md)
|
||||
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.
|
||||
|
||||
### 8.45 Tile-bounded passage evaluation — World Assembler M3 Linux L28
|
||||
|
||||
Each mesh tile scopes passage composition to the inclusive density-sample AABB, including the
|
||||
mesher's normal halo. A conservative feature AABB BVH selects route segments, columns and mouth
|
||||
support for that tile. A per-passage nearest-segment BVH bounds the long helix search; a separate
|
||||
passage-bound BVH rejects far L9 proof boxes. An unindexed native-only proof object uses a conservative
|
||||
linear fallback. No field, union order, serialized layout or recipe version changes.
|
||||
|
||||
The column's exact complex SDF is evaluated only in XY/Z feature candidates. A cheap density-aware
|
||||
radial guard remains for samples sharing the column's Z slab: a positive column distance can still
|
||||
lower positive base density, so skipping that guard needs an exact bound on the base field. This is
|
||||
the remaining SurfaceWorld cost noted in the Linux L28 report. The passage candidate lists and
|
||||
proof bounds preserve the L27 output.
|
||||
|
||||
[Linux28 report](Standalone/Research/WorldAssemblerM3/Linux28/REPORT.md)
|
||||
records counts, parity, costs and audit caveats.
|
||||
|
||||
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||
|
||||
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
||||
|
||||
@@ -1181,6 +1181,42 @@ The shared evaluator is version 10, the FI recipe geometry is version 5, and res
|
||||
V3 is unchanged. See ARCHITECTURE §8.42 and
|
||||
[`Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md).
|
||||
|
||||
### 3.26 Enclosed FloatingIslands helix and open-air support gate — World Assembler L25
|
||||
|
||||
| Symbol | Canonical file | Role |
|
||||
|---|---|---|
|
||||
| `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Emits route-mouth support, tread and fallback support only over pre-passage native solid density, shared by every passage style. |
|
||||
| `auditPassageOpenAir` / `--audit-resolved-layout-passages` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples the support footprint at 0.25 m spacing and two depths; compares against L24 ungated support as a required negative control. |
|
||||
| Anchor root dimensions and relief | `Standalone/Core/src/FloatingIslandsInternal.h` | Raises the FI anchor root from a 6 m cylinder to a nominal 13.25 m relief-bearing rock column sized around the 3 m helix and 4.5 m mouth. |
|
||||
| Passage route and root measures | `Standalone/Core/src/ResolvedLayoutInternal.h`, `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Binds the Spiral to the root and reports measured centreline/root clearance. |
|
||||
|
||||
The shared evaluator is version 12 and FI geometry version is 7; resolved-layout schema V3 is
|
||||
unchanged. See ARCHITECTURE §8.43 and
|
||||
[`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).
|
||||
|
||||
### 3.28 Tile-bounded passage evaluation — World Assembler M3 Linux L28
|
||||
|
||||
| Symbol | Canonical file | Role |
|
||||
|---|---|---|
|
||||
| FScopedPassageTileQueries / BuildPassageFeatureTileList / ResolvedPassageIntersectsBox | Standalone/Core/src/ShapeWorldInternal.h; scope entered by GenerateMesh in Standalone/Core/src/VoxelForgeCore.cpp | Builds conservative per-tile route, column and mouth-support candidates from an AABB BVH; long paths use a nearest-segment BVH and L9 box checks use a separate passage-bound BVH. Empty native-only proof indexes fall back conservatively. The density arithmetic and serialized versions stay unchanged. |
|
||||
|
||||
See ARCHITECTURE §8.45 and
|
||||
[Linux28 report](Standalone/Research/WorldAssemblerM3/Linux28/REPORT.md).
|
||||
|
||||
## 4. The density pipeline (most-edited hot path)
|
||||
|
||||
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
||||
|
||||
@@ -485,7 +485,7 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
|
||||
IslandPlan.topChunkZ = Slot.TopChunkZ;
|
||||
IslandPlan.bottomChunkZ = Slot.BottomChunkZ;
|
||||
auto& Recipe = IslandPlan.manifest.recipe;
|
||||
Recipe.geometryVersion = 5;
|
||||
Recipe.geometryVersion = 8;
|
||||
Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels;
|
||||
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
|
||||
Recipe.ownerVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
|
||||
|
||||
@@ -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; }
|
||||
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"
|
||||
BIN_DIR="$ROOT/bin/linux"
|
||||
BUILD_ROOT="${VOXELFORGE_BUILD_ROOT:-$ROOT/Build}"
|
||||
BIN_DIR="${VOXELFORGE_BIN_DIR:-$ROOT/bin/linux}"
|
||||
BUILD_DIR="$BUILD_ROOT/linux-$VARIANT"
|
||||
mkdir -p "$BUILD_DIR/wrappers" "$BUILD_DIR/objects" "$BIN_DIR"
|
||||
|
||||
INCLUDES=(
|
||||
|
||||
@@ -249,8 +249,8 @@ namespace VoxelForgeCore
|
||||
// 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::uint32_t WorldAssemblySchemaVersion = 2;
|
||||
// Version 10 adds per-strate passage styles and rough wall relief to resolved passages.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 10;
|
||||
// Version 14 adds shared passage columns for long native open-air crossings.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 14;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
@@ -666,6 +666,28 @@ namespace VoxelForgeCore
|
||||
std::vector<ResolvedLayoutPassagePointV1> controlPoints;
|
||||
};
|
||||
|
||||
struct PassageVoidCrossingAuditV1
|
||||
{
|
||||
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{};
|
||||
bool needsColumn = false;
|
||||
bool connectedAtStart = false;
|
||||
bool connectedAtEnd = false;
|
||||
std::array<std::int64_t, 2> columnAxisSubvoxels{};
|
||||
std::int64_t columnTopZSubvoxels = 0;
|
||||
std::int64_t columnBottomZSubvoxels = 0;
|
||||
std::int64_t columnMinimumRadiusSubvoxels = 0;
|
||||
std::int64_t columnNominalRadiusSubvoxels = 0;
|
||||
std::int64_t columnMaximumRadiusSubvoxels = 0;
|
||||
double maximumRouteOffsetMetres = 0.0;
|
||||
double maximumBoreRadiusMetres = 0.0;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutPassageConfigV1
|
||||
{
|
||||
std::int32_t upperStrateIndex = -1;
|
||||
@@ -1314,6 +1336,12 @@ namespace VoxelForgeCore
|
||||
ShapeBuildStats buildStats() const;
|
||||
|
||||
float densityAt(Vec3d voxelPosition) const;
|
||||
// Audit-only view of density-field columns and short spans supporting passage voids.
|
||||
std::vector<PassageVoidCrossingAuditV1> passageVoidCrossings() const;
|
||||
bool isPassageColumnSample(std::size_t passageIndex, std::size_t segmentIndex,
|
||||
Vec3d voxelPosition) const;
|
||||
bool isPassageShortSpanSample(std::size_t passageIndex, std::size_t segmentIndex,
|
||||
Vec3d voxelPosition) const;
|
||||
// Audit-only selector provenance for the same point evaluator; never used by meshing.
|
||||
std::string densityDispatchPathAt(Vec3d voxelPosition) const;
|
||||
// Analytic source distance without the occupancy-preserving near-zero clamp or packed proxy.
|
||||
|
||||
@@ -281,7 +281,7 @@ namespace floating_island_detail
|
||||
? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0;
|
||||
const std::uint32_t ExpectedDenominator = ScaleGcd > 0
|
||||
? recipe.coreVoxelSizeMicrons / ScaleGcd : 0;
|
||||
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 5
|
||||
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 8
|
||||
|| (recipe.geometryVersion == 1
|
||||
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|
||||
|| recipe.unitsPerVoxel != Q
|
||||
@@ -753,10 +753,13 @@ namespace floating_island_detail
|
||||
const double rootBlend = std::clamp(radius * 0.12, 2.0 * Q, 5.0 * Q);
|
||||
for (std::int32_t root = 0; root < rootCount; ++root)
|
||||
sdf = SmoothMin(sdf, RootLobeDensity(recipe, island, root, x, y, z), rootBlend);
|
||||
if (recipe.geometryVersion >= 5 && island.ordinal == recipe.anchorIslandOrdinal
|
||||
if (recipe.geometryVersion >= 5 && recipe.geometryVersion < 8
|
||||
&& island.ordinal == recipe.anchorIslandOrdinal
|
||||
&& recipe.anchorRootBottomZSubvoxels < island.bottomZSubvoxels)
|
||||
{
|
||||
const double rootTop = static_cast<double>(island.bottomZSubvoxels);
|
||||
// The central buttress must meet the island crust underside. IslandBottom
|
||||
// also includes the four shorter root lobes and sits below that surface.
|
||||
const double rootTop = static_cast<double>(IslandCrustBottom(recipe, island));
|
||||
const double rootBottom = static_cast<double>(recipe.anchorRootBottomZSubvoxels);
|
||||
const double depth = std::max(1.0, rootTop - rootBottom);
|
||||
const double blend = std::clamp(static_cast<double>(recipe.anchorRootRadiusSubvoxels)
|
||||
@@ -942,14 +945,14 @@ namespace floating_island_detail
|
||||
FloatingIslandAnchorV1& island)
|
||||
{
|
||||
const std::int64_t pad = IslandBlendMargin(recipe) + recipe.roughnessSubvoxels;
|
||||
const bool isAnchor=recipe.geometryVersion>=5
|
||||
const bool isAnchorRoot=recipe.geometryVersion>=5&&recipe.geometryVersion<8
|
||||
&&island.ordinal==recipe.anchorIslandOrdinal;
|
||||
const std::int64_t reach = std::max(island.radiusSubvoxels,
|
||||
isAnchor?recipe.anchorRootRadiusSubvoxels:std::int64_t{0})
|
||||
isAnchorRoot?recipe.anchorRootRadiusSubvoxels:std::int64_t{0})
|
||||
+ recipe.contourWarpSubvoxels + pad;
|
||||
island.influenceMinimumSubvoxels = {island.centerSubvoxels[0] - reach,
|
||||
island.centerSubvoxels[1] - reach,
|
||||
(isAnchor?std::min(island.bottomZSubvoxels,recipe.anchorRootBottomZSubvoxels)
|
||||
(isAnchorRoot?std::min(island.bottomZSubvoxels,recipe.anchorRootBottomZSubvoxels)
|
||||
:island.bottomZSubvoxels)-pad};
|
||||
island.influenceMaximumSubvoxels = {island.centerSubvoxels[0] + reach,
|
||||
island.centerSubvoxels[1] + reach, island.topZSubvoxels + pad};
|
||||
@@ -1024,14 +1027,15 @@ namespace floating_island_detail
|
||||
{ error = "floating-island anchor has invalid coordinates or an inverted bound"; return false; }
|
||||
}
|
||||
const std::int64_t pad = IslandBlendMargin(recipe) + recipe.roughnessSubvoxels;
|
||||
const bool hasAnchorRoot=recipe.geometryVersion>=5&&recipe.geometryVersion<8&&isAnchor;
|
||||
const std::int64_t reach = std::max(island.radiusSubvoxels,
|
||||
isAnchor?recipe.anchorRootRadiusSubvoxels:std::int64_t{0})
|
||||
hasAnchorRoot?recipe.anchorRootRadiusSubvoxels:std::int64_t{0})
|
||||
+ recipe.contourWarpSubvoxels + pad;
|
||||
if (island.influenceMinimumSubvoxels[0] > island.centerSubvoxels[0] - reach
|
||||
|| island.influenceMaximumSubvoxels[0] < island.centerSubvoxels[0] + reach
|
||||
|| island.influenceMinimumSubvoxels[1] > island.centerSubvoxels[1] - reach
|
||||
|| island.influenceMaximumSubvoxels[1] < island.centerSubvoxels[1] + reach
|
||||
|| island.influenceMinimumSubvoxels[2] > (isAnchor
|
||||
|| island.influenceMinimumSubvoxels[2] > (hasAnchorRoot
|
||||
?std::min(island.bottomZSubvoxels,recipe.anchorRootBottomZSubvoxels)
|
||||
:island.bottomZSubvoxels) - pad
|
||||
|| island.influenceMaximumSubvoxels[2] < island.topZSubvoxels + pad
|
||||
@@ -1330,7 +1334,7 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
|
||||
manifest.recipe.anchorRootBottomZSubvoxels)
|
||||
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_mouth_offset_q"),
|
||||
manifest.recipe.anchorMouthOffsetSubvoxels))
|
||||
{error="floating-island geometry version 5 is missing a passage-rooted anchor field";return std::nullopt;}
|
||||
{error="floating-island geometry version 7 is missing a passage-rooted anchor field";return std::nullopt;}
|
||||
}
|
||||
if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema)
|
||||
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash)
|
||||
|
||||
@@ -550,6 +550,14 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
const auto routeAnchor=[](const ResolvedLayoutStratumV2& s,bool departure,
|
||||
std::array<std::int64_t,3>& out)
|
||||
{
|
||||
if(departure)
|
||||
for(const auto& a:s.anchors)
|
||||
if(a.kind=="passage_anchor"&&a.values.size()>=10)
|
||||
{out={a.values[7],a.values[8],a.values[9]};return true;}
|
||||
if(!departure)
|
||||
for(const auto& a:s.anchors)
|
||||
if(a.kind=="passage_arrival"&&a.values.size()>=3)
|
||||
{out={a.values[0],a.values[1],a.values[2]};return true;}
|
||||
const ResolvedLayoutAnchorV2* selected=nullptr;
|
||||
for(const auto& a:s.anchors)
|
||||
if(a.kind=="route_point"&&a.values.size()>=3)
|
||||
@@ -597,8 +605,9 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
// Leave room for the capsule and bounded wall relief at the floor/wall seam.
|
||||
const std::int64_t mouthRadius=std::max<std::int64_t>(static_cast<std::int64_t>(std::llround(1.5*qPerMetre)),
|
||||
cfg?cfg->mouthRadiusSubvoxels:0);
|
||||
const std::int64_t midRadius=std::max<std::int64_t>(mouthRadius,
|
||||
cfg?cfg->midRadiusSubvoxels:0);
|
||||
const std::int64_t midRadius=std::max<std::int64_t>(
|
||||
static_cast<std::int64_t>(std::llround(1.5*qPerMetre)),
|
||||
cfg?cfg->midRadiusSubvoxels:mouthRadius);
|
||||
const std::int64_t reachMinimum=cfg?std::max<std::int64_t>(0,cfg->reachMinimumSubvoxels):8*qPerMetre;
|
||||
const std::int64_t reachMaximum=cfg?std::max<std::int64_t>(reachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre;
|
||||
const std::uint32_t style=cfg?std::min<std::uint32_t>(cfg->style,3u):1u;
|
||||
@@ -624,7 +633,9 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
for(const auto& anchor:upper.anchors)
|
||||
if(anchor.kind=="passage_anchor"&&anchor.values.size()>=7)
|
||||
{passageAnchor=&anchor;break;}
|
||||
const bool anchoredSpiral=style==2&&passageAnchor!=nullptr;
|
||||
const bool anchoredIslandPassage=passageAnchor!=nullptr
|
||||
&&upper.generatorKind=="FloatingIslands";
|
||||
const bool anchoredSpiral=style==2&&anchoredIslandPassage;
|
||||
if(!hasUpperRoute||!hasLowerRoute)
|
||||
{
|
||||
const auto xy=PassageRandomXY(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
|
||||
@@ -652,7 +663,10 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
std::array<std::int64_t,2> upperTangent{},lowerTangent{};
|
||||
const bool hasUpperTangent=hasUpperRoute
|
||||
&&routeTangent(upper,true,upperTangent);
|
||||
const bool hasLowerTangent=hasLowerRoute
|
||||
const bool hasPassageArrival=std::any_of(lower.anchors.begin(),lower.anchors.end(),
|
||||
[](const auto& anchor){return anchor.kind=="passage_arrival"
|
||||
&&anchor.values.size()>=3;});
|
||||
const bool hasLowerTangent=hasLowerRoute&&!hasPassageArrival
|
||||
&&routeTangent(lower,false,lowerTangent);
|
||||
|
||||
const std::int64_t outerRingRadius=std::max<std::int64_t>(1,
|
||||
@@ -671,6 +685,8 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
std::max<std::int64_t>(0,cfg?cfg->cascadeLedgeSubvoxels:0)
|
||||
*static_cast<std::int64_t>(cfg?cfg->cascadeSteps:4)),
|
||||
mouthRadius+2*qPerMetre,outerRingRadius);
|
||||
if(anchoredIslandPassage)
|
||||
ringRadius=std::min(ringRadius,mouthRadius+qPerMetre/2);
|
||||
std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed,
|
||||
upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16);
|
||||
if(hasUpperRoute)
|
||||
@@ -700,7 +716,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
+clockwiseTangentY*upperTangent[1]>=0.0;
|
||||
}
|
||||
const int direction=clockwise?1:-1;
|
||||
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree contract.
|
||||
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree helix contract.
|
||||
const std::int64_t wormRadialWobble=std::min<std::int64_t>(
|
||||
std::max<std::int64_t>(0,wander/3),2*qPerMetre);
|
||||
const std::int64_t wormCenterWobble=std::min<std::int64_t>(
|
||||
@@ -716,10 +732,14 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
const double loop=static_cast<double>(loopIndex);
|
||||
const double phaseA=loop*0.6180339887498948+0.37;
|
||||
const double phaseB=loop*0.4142135623730950+1.11;
|
||||
const double jitterX=static_cast<double>(wormCenterWobble)*std::sin(phaseB);
|
||||
const double jitterY=static_cast<double>(wormCenterWobble)*std::cos(phaseB);
|
||||
radius+=static_cast<double>(wormRadialWobble)
|
||||
*std::sin(angle+phaseA);
|
||||
centerX=static_cast<double>(wormCenterWobble)*std::sin(phaseB);
|
||||
centerY=static_cast<double>(wormCenterWobble)*std::cos(phaseB);
|
||||
centerX=jitterX+(anchoredIslandPassage&&passageAnchor
|
||||
?static_cast<double>(passageAnchor->values[0]):0.0);
|
||||
centerY=jitterY+(anchoredIslandPassage&&passageAnchor
|
||||
?static_cast<double>(passageAnchor->values[1]):0.0);
|
||||
}
|
||||
else if(pathStyle==3u)
|
||||
{
|
||||
@@ -730,6 +750,11 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
cfg->cascadeLedgeSubvoxels):0));
|
||||
radius-=ledge*(0.5-0.5*std::cos(angle*steps));
|
||||
}
|
||||
if(pathStyle!=1u&&anchoredIslandPassage&&passageAnchor)
|
||||
{
|
||||
centerX=static_cast<double>(passageAnchor->values[0]);
|
||||
centerY=static_cast<double>(passageAnchor->values[1]);
|
||||
}
|
||||
return std::array<std::int64_t,2>{
|
||||
static_cast<std::int64_t>(std::llround(centerX+radius*directions[directionIndex][0]/65536.0)),
|
||||
static_cast<std::int64_t>(std::llround(centerY+radius*directions[directionIndex][1]/65536.0))};
|
||||
@@ -748,7 +773,11 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
const std::int64_t anchorY=passageAnchor->values[1];
|
||||
const std::int64_t rootRadius=passageAnchor->values[2];
|
||||
const std::int64_t rootBottom=passageAnchor->values[5];
|
||||
helixBedrockZ=rootBottom!=0?rootBottom:contentZQ(upper.bedrock.topMicrometres);
|
||||
const std::int64_t bedrockTop=rootBottom!=0?rootBottom
|
||||
:contentZQ(upper.bedrock.topMicrometres);
|
||||
// Passage route heights are player-center positions. End one capsule
|
||||
// half-height above the bedrock top so the final ramp step is walkable.
|
||||
helixBedrockZ=bedrockTop+qPerMetre;
|
||||
const std::int64_t helixRadius=spiralRadius;
|
||||
if(rootRadius<helixRadius+midRadius+qPerMetre/4
|
||||
||std::hypot(static_cast<double>(upperMouth[0]-anchorX),
|
||||
@@ -796,6 +825,8 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
helixEndIndex=xy.size()-1;
|
||||
hasAnchorHelix=helixEndIndex>0;
|
||||
}
|
||||
const std::int64_t routeGradeQ16=hasAnchorHelix
|
||||
&&lower.generatorKind=="SurfaceWorld"?8000:TargetGradeQ16;
|
||||
const std::array<std::int64_t,2> firstRing=ringPoint(phase);
|
||||
if(hasAnchorHelix&&pathStyle==1u)
|
||||
{
|
||||
@@ -843,12 +874,14 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
const std::size_t start=hasAnchorHelix?helixEndIndex:0;
|
||||
for(std::size_t i=start+1;i<xy.size();++i)
|
||||
total+=distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
|
||||
if(!hasLowerTangent&&!xy.empty())
|
||||
total+=distanceQ(lowerMouth[0]-xy.back()[0],lowerMouth[1]-xy.back()[1]);
|
||||
return total;
|
||||
};
|
||||
while(loops<256)
|
||||
{
|
||||
totalHorizontal=measurePath();
|
||||
if(totalHorizontal*static_cast<std::uint64_t>(TargetGradeQ16)
|
||||
if(totalHorizontal*static_cast<std::uint64_t>(routeGradeQ16)
|
||||
>=static_cast<std::uint64_t>(verticalDistance)*65536ull)break;
|
||||
for(int step=1;step<=16;++step)
|
||||
{
|
||||
|
||||
@@ -1164,6 +1164,44 @@ namespace VoxelForgeCore
|
||||
}
|
||||
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)
|
||||
@@ -1246,6 +1284,8 @@ namespace VoxelForgeCore
|
||||
if(IntersectsBox(feature.minimumSubvoxels,feature.maximumSubvoxels,
|
||||
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;
|
||||
FStandaloneShapeWorld proofGeometry;
|
||||
if(!proofGeometry.BuildShapeDependencyProof(*impl_->owner,boundsMin,boundsMax,
|
||||
@@ -1502,6 +1542,44 @@ namespace VoxelForgeCore
|
||||
|
||||
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; }
|
||||
std::vector<PassageVoidCrossingAuditV1> World::passageVoidCrossings() const
|
||||
{
|
||||
std::vector<PassageVoidCrossingAuditV1> result;
|
||||
if(!impl_||!impl_->shapeReady.load(std::memory_order_acquire)||!impl_->shape)return result;
|
||||
const auto& crossings=impl_->shape->GetPassageVoidCrossings();
|
||||
result.reserve(crossings.size());
|
||||
for(const auto& crossing:crossings)
|
||||
{
|
||||
PassageVoidCrossingAuditV1 value;
|
||||
value.passageIndex=crossing.passageIndex;value.firstSegment=crossing.firstSegment;
|
||||
value.lastSegment=crossing.lastSegment;value.unsupportedSamples=crossing.unsupportedSamples;
|
||||
value.unsupportedLengthMetres=crossing.unsupportedLengthMetres;
|
||||
value.startPointSubvoxels=crossing.startPoint;value.endPointSubvoxels=crossing.endPoint;
|
||||
value.needsColumn=crossing.needsColumn;
|
||||
value.connectedAtStart=crossing.connectedAtStart;
|
||||
value.connectedAtEnd=crossing.connectedAtEnd;
|
||||
value.columnAxisSubvoxels={crossing.centerXSubvoxels,crossing.centerYSubvoxels};
|
||||
value.columnTopZSubvoxels=crossing.topZSubvoxels;
|
||||
value.columnBottomZSubvoxels=crossing.bottomZSubvoxels;
|
||||
value.columnMinimumRadiusSubvoxels=crossing.minimumRadiusSubvoxels;
|
||||
value.columnNominalRadiusSubvoxels=crossing.nominalRadiusSubvoxels;
|
||||
value.columnMaximumRadiusSubvoxels=crossing.maximumRadiusSubvoxels;
|
||||
value.maximumRouteOffsetMetres=crossing.maximumRouteOffsetMetres;
|
||||
value.maximumBoreRadiusMetres=crossing.maximumBoreRadiusMetres;
|
||||
result.push_back(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bool World::isPassageColumnSample(std::size_t passageIndex,std::size_t segmentIndex,
|
||||
Vec3d position) const
|
||||
{return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape
|
||||
&&impl_->shape->IsResolvedPassageColumnSample(passageIndex,segmentIndex,
|
||||
{position.x,position.y,position.z});}
|
||||
bool World::isPassageShortSpanSample(std::size_t passageIndex,std::size_t segmentIndex,
|
||||
Vec3d position) const
|
||||
{return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape
|
||||
&&impl_->shape->IsResolvedPassageShortSpanSample(passageIndex,segmentIndex,
|
||||
{position.x,position.y,position.z});}
|
||||
std::string World::densityDispatchPathAt(Vec3d position) const
|
||||
{ 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))
|
||||
@@ -2059,6 +2137,12 @@ namespace VoxelForgeCore
|
||||
out.diagnostics.featureListMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-FeatureListStart).count();
|
||||
const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries);
|
||||
// GenerateMesh samples only origin..origin+extent plus the one-step normal halo;
|
||||
// queryMinimum/queryMaximum above are exactly that inclusive domain.
|
||||
// GenerateMesh n'échantillonne que origin..origin+extent et la marge de normale d'un pas ;
|
||||
// queryMinimum/queryMaximum ci-dessus couvrent exactement ce domaine inclusif.
|
||||
const FStandaloneShapeWorld::FScopedPassageTileQueries passageFeatureScope(
|
||||
tileShape,queryMinimum,queryMaximum);
|
||||
const bool bDiagnosticColumnSampling=options.diagnosticGlobalOctaveDrop!=0
|
||||
||options.diagnosticColumnInterpolationStride>1;
|
||||
const std::uint32_t PreviousOctaveDrop=bDiagnosticColumnSampling
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# World Assembler L25 — FI helix inside the root
|
||||
|
||||
**The ribbon came from two causes.** The shared passage compositor stamped route-mouth tread/support into native air, and the L24 6 m root could not contain this route: its measured centerline reached 5.562 m from the root axis and the mouth bore is 4.5 m. That leaves a nominal `6 - 5.562 - 4.5 = -4.062 m` wall allowance. The axis and units were correct; the support pass ignored surrounding rock, and the root was too small.
|
||||
|
||||
L25 gates shared route-mouth support on negative pre-passage density, so it can reinforce existing rock but cannot create a shelf in air. The FI anchor root is now a relief-bearing 13.25 m nominal rock column. I chose a sturdier column with the existing uneven relief over a thin cylinder; it remains column-shaped from outside. The authored spiral stays centered on the root axis.
|
||||
|
||||
## Root and passage measurements
|
||||
|
||||
Axis `(9.800, 4.571) m`; helix centerline radius `2.999–5.562 m`; bore radius `4.5 m` at the mouth and `3.0 m` through the middle. The conservative wall estimate uses the largest path offset and mouth bore at every height.
|
||||
|
||||
| Height (m) | Solid radius min / mean / max (m) |
|
||||
|---:|---:|
|
||||
| -68.209 | 12.000 / 12.129 / 12.375 |
|
||||
| -80.643 | 12.125 / 12.254 / 12.500 |
|
||||
| -93.595 | 12.500 / 12.672 / 12.875 |
|
||||
| -106.548 | 12.875 / 13.012 / 13.250 |
|
||||
| -118.982 | 13.000 / 13.168 / 13.375 |
|
||||
|
||||
Minimum measured wall allowance is `1.938 m`, above the existing `1.5 m` reserve.
|
||||
|
||||
## Audits
|
||||
|
||||
The authored FI Spiral passed on Clang and GCC: 61,173 capsule samples, `blocked=0`, 5,318 support samples, `unsupported=0`, `grade_failures=0`; both route bindings pass, and walkability is 1,135/1,135. Strict layout validation accepted with zero issues; malformed controls were rejected 9/9.
|
||||
|
||||
The open-air audit sampled 813,632 passage support points at 0.25 m spacing: `violations=0`, including 11,520 route-support samples. Its L24 ungated-support negative control found 1,438 violations. Naturalness passed with a 0 m flat run and 0.767 m wall-relief peak-to-peak; the L23 smooth-wall negative control was detected at 0 m relief. The capsule, open-air, and naturalness results match between Clang and GCC.
|
||||
|
||||
All four style overrides in both recipes passed the open-air audit (zero violations; the L24 control was detected each time). The capsule sweep has failures outside the authored FI Spiral: FI styles 0/1/3 have 7/13/6 blocked samples; SurfaceWorld styles 0/1/2/3 have 4,255/4,071/3,948/4,117 unsupported samples after the safe support gate. Their blocked/grade counts are zero. These are alternate-style walkability failures, not open-air emissions; the canonical FI Spiral and composed SurfaceWorld route pass. The individual logs are `Saved/Linux/linux25-style-*.log`.
|
||||
|
||||
L13 parity passed on both compilers: FI 8/8 page pairs, 14 higher-LOD pairs, 782,026 exact-query samples, zero mismatches; SurfaceWorld 5 page pairs, 9 native-only pairs, 14 higher-LOD pairs, 910,651 exact-query samples, zero mismatches. Both had one passing edit pair. L9 uniform-block proofs had zero violations: SurfaceWorld checked 26,977 blocks / 35,906,387 samples; FI used its expected 21,504 fallbacks/unknowns and checked no blocks.
|
||||
|
||||
The 24 m crossing passed both compilers and recipes. FI processed 314 moving tiles and SurfaceWorld 432; compiler hashes match per recipe. Crossing whole-view density/mesh/payload/topology hashes (FI 2,370 tiles; SurfaceWorld 2,553):
|
||||
|
||||
- FI: `c284b42ced276f4c / f6a90d0027d8d6fe / fdec2a7f61215a1e / 60a99d7c20700cd6`
|
||||
- SurfaceWorld: `cee18b39a9271559 / c1713be06b92bd8e / fd7954350ebfacfc / c58b075a1b7f2895`
|
||||
|
||||
## Determinism and SurfaceWorld
|
||||
|
||||
The exact LOD0–8 hash matrix passed at W1/W8/W12 for Clang and GCC: 2,370 whole-view tiles, 960 full-detail tiles, all nine LOD rows and exact-query hashes identical across workers and compilers. Hash vectors are density/mesh/payload/topology:
|
||||
|
||||
| Recipe | Whole view (2,370 tiles) | Full detail (960 tiles) | Exact-query density / detail / collision |
|
||||
|---|---|---|---|
|
||||
| SurfaceWorld | `8d494f8f843171b2 / 37d21941f6d9cce8 / 47248393fa2f3322 / 36d03d874daef4ae` | `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae` | `c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849` |
|
||||
| Floating Islands | `6f0095c01b066744 / efea60f7a9c1d675 / 849c1829e1dcddd5 / 4f7cc65dc1adc234` | `d18e722b37a2479e / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec` | `7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf` |
|
||||
|
||||
Re-run command, with `N=1,8,12`, both compiler binaries, and both recipes:
|
||||
|
||||
```sh
|
||||
timeout 1800s taskset -c 0-11 env LP_NUM_THREADS=12 prlimit --as=6442450944 -- Standalone/bin/linux/VoxelForgeCoreHeadless-linux25-<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
|
||||
```
|
||||
|
||||
SurfaceWorld away from the passage was directly compared with L23 in L24 and matched at 960 full-detail tiles: `66ac781666579e3e / 914873894a0e38c4 / ec8b03ade039cd56 / a2b621b0321d43dc`. L25 changes only passage/root generation and the shared passage composition region; it does not change the away-from-passage SurfaceWorld field. The L25 full-view hashes above cover a different tile selection from that away-only comparison.
|
||||
|
||||
## Captures
|
||||
|
||||
The four final captures are 1920×1080 JPEGs under `captures/`, each below 300 KB:
|
||||
|
||||
- `fi-pillar-exterior.jpg` (43 KB): wide exterior; thick relief-bearing root, no outside ribbon.
|
||||
- `fi-pillar-cutaway.jpg` (53 KB): vertical cutaway exposing the helix and central rock.
|
||||
- `fi-helix-inside.jpg` (37 KB): headlamp on, 90° FOV, camera `(11.495, 7.047, -87.858) m` at resolved route point 200, about 2 m above the tread, aimed down the route tangent. The tight turn leaves a close, wall-heavy view; the cutaway is clearer for the full spiral.
|
||||
- `sw-arrival-mouth.jpg` (55 KB): headlamp on, 80° FOV, camera `(9.800, 4.571, -133.000) m`, 5.5 m below the lower mouth, looking up into the SW arrival.
|
||||
|
||||
## Changes, invariants, and run history
|
||||
|
||||
Core evaluator version is 12; FI geometry version is 7; resolved-layout schema V3 is unchanged. Source changes are in the Core passage planner/compositor, FI root geometry, and the standalone audit/hash reporting. `ARCHITECTURE.md` §8.43 and `CODEMAP.md` §3.26 document the change. The only Unreal edit is the adapter's FI geometry-version assignment (`geometryVersion = 7`); Unreal was unavailable, so that adapter edit was not compiled here.
|
||||
|
||||
L3 FP-contract preamble/self-test, L4 `FLazy`, L7 shared cache, L9/L12 proofs, L11 readiness/crash fix, L13 route binding, L18/L19 shared ceiling, L21/L22 layout and bands, L23/L24 passages, ARCHITECTURE §8.10, and CLAUDE.md async rules remain intact. No commit, push, or stash was made.
|
||||
|
||||
Failed or retried runs:
|
||||
|
||||
- The first scene export omitted `--render-distance-chunks=0`; it expanded to 593,068 tiles / about 640 MB and was stopped before 15 minutes, then its partial export was removed. A render-distance-48 retry returned 844 plane-dominated tiles / 31 MB and was discarded. Clipmap-only (195 tiles) and focused 27-tile detail exports succeeded.
|
||||
- Two extra GCC audit invocations exited 2 because the headless binary needs the owner JSON as a positional path; the corrected invocation with `Standalone/data/owner_world.json` passed. The requested six-worker/compiler/recipe hash matrix itself passed.
|
||||
- Early interior camera trials missed the resolved helix centerline and showed wall-only frames; the final interior frame uses a point extracted from the current resolved layout. No build or audit exceeded 30 minutes, and no step was left stuck for 15 minutes.
|
||||
|
||||
## Orchestrator review (Claude) — side branch, not merged yet
|
||||
|
||||
- The FI helix fix is confirmed by my capture review: `fi-pillar-exterior.jpg` shows a solid relief-bearing
|
||||
column with no outside ribbon, and `fi-pillar-cutaway.jpg` shows the helix inside it.
|
||||
- **Regression that blocks merge:** the support gate makes non-FI passages unwalkable. With the SurfaceWorld
|
||||
recipe (the owner world's TunnelNetwork strate above SurfaceWorld), every style has ~4,000 unsupported
|
||||
capsule samples (L24: 0). FI styles 0/1/3 have blocked samples. The owner world's real TN→SW passage is
|
||||
affected, so a player can fall through it.
|
||||
- Kept on side branch `wip/l25-helix`; `assembler/world` stays at L24 (`cb9236c`) until L26 makes every style
|
||||
in both recipes pass with 0 blocked / 0 unsupported / 0 open-air violations.
|
||||
- Design decision (owner delegated decisions to me, 2026-10-21): passages are tunnels in rock. The planner keeps
|
||||
the path inside solid ground where possible; unavoidable open-air crossings get a natural, grounded rock
|
||||
bridge (thick, relief, connected to rock on both ends), never a floating tread.
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 55 KiB |
@@ -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`.
|
||||
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 89 KiB |
@@ -0,0 +1,179 @@
|
||||
# VoxelForge L27 — passage voids inside rock columns
|
||||
|
||||
## Owner summary
|
||||
|
||||
The SurfaceWorld passage now drops from the ceiling (about −138 to −143 m) to terrain near
|
||||
−505 m inside one continuous, relief-bearing rock column. Its helix is cut inside the column; the
|
||||
~2 km route no longer crosses the open sky as a bridge. FloatingIslands uses the same field and
|
||||
planner path: the passage descends inside a column from its island to the bedrock floor. Styles 1
|
||||
and 3 have a second, smaller cap crossing, so they get a second column. The FI style-0 anchor stays
|
||||
at the L25 axis and remains connected. Its shared column is slimmer (11.25 m nominal radius vs
|
||||
L25's 13.25 m) with a larger measured wall margin (3.25 m vs 1.938 m), visible in the new
|
||||
exterior capture.
|
||||
|
||||
All four styles in both recipes pass on Clang and GCC: 0 blocked, 0 unsupported, 0 grade failures,
|
||||
0 open-air violations, and naturalness passes. The new no-long-bridge audit detects the L26
|
||||
SurfaceWorld bridge as its negative control.
|
||||
|
||||
## Column rule and size
|
||||
|
||||
The shared planner samples native support every 0.5 m. A contiguous unsupported route run longer
|
||||
than **12.5 m** becomes one column; this is 2.5 times the former 5 m support-thickness limit. A
|
||||
shorter span is allowed only while grounded. The column is a solid signed-distance union in the
|
||||
shared composition field, so terrain queries, collision and meshes all see the same rock. The
|
||||
recipe still chooses passage style; the helix applies inside a column and that style continues
|
||||
outside it.
|
||||
|
||||
The minimum planned outer radius is maximum route offset + bore radius + 2 m; the nominal profile
|
||||
adds 1.25 m. The base flares by up to 6 m and the ceiling end by up to 2 m; bounds extend 6 m
|
||||
below and 5 m above the unsupported run to overlap the native rock. Measured wall margin is 1.75 m
|
||||
minimum in SW and 3.0 m minimum in FI, above the 1.5 m requirement. SW columns span about
|
||||
363–368 m vertically and have nominal radii 19.744–27.027 m. FI main columns span about 53–57 m
|
||||
with nominal radii 9.251–12.746 m; styles 1 and 3 also have ~14 m cap columns.
|
||||
|
||||
| Recipe / style | Unsupported route run(s) | Column nominal radius; audited profile radius | Minimum wall |
|
||||
|---|---:|---:|---:|
|
||||
| SW 0 Straight | 2,004.815 m; z −142.687 to −505.109 m | 27.027 m; 25.527–27.777 m | 1.75 m |
|
||||
| SW 1 Worm | 1,946.355 m; z −138.668 to −505.109 m | 24.187 m; 22.687–24.937 m | 1.75 m |
|
||||
| SW 2 Spiral | 1,957.892 m; z −137.967 to −505.138 m | 19.744 m; 18.244–20.744 m | 1.75 m |
|
||||
| SW 3 Cascading | 1,981.103 m; z −137.484 to −505.112 m | 22.791 m; 21.291–23.541 m | 1.75 m |
|
||||
| FI 0 Straight | 249.982 m; z −65.846 to −122.390 m | 11.250 m; 11.250–12.500 m | 3.25 m |
|
||||
| FI 1 Worm | 17.042 m and 249.965 m; z −61.574 to −122.416 m | 12.719 m and 12.746 m; 12.968–16.468 m and 12.746–13.996 m | 3.25 m |
|
||||
| FI 2 Spiral | 244.479 m; z −68.891 to −122.382 m | 9.251 m; 9.000–10.500 m | 3.00 m |
|
||||
| FI 3 Cascading | 17.955 m and 231.814 m; z −65.805 to −122.436 m | 11.250 m each; 11.250–14.000 m and 11.250–12.500 m | 3.25 m |
|
||||
|
||||
The helix path measurements are length / turns / mean path radius / vertical pitch. SW styles 0–3
|
||||
measure **2,015.1 m / 16.88 / 18.96 m / 21.00 m**, **1,956.1 / 20.69 / 14.99 / 17.32 m**,
|
||||
**1,963.0 / 61.62 / 5.02 / 5.81 m**, and **1,987.2 / 21.63 / 14.50 / 16.59 m**. FI styles 0–3
|
||||
measure **254.0 m / 8.00 / 5.00 m / 5.78 m**, **254.0 / 7.99 / 4.97 / 5.72 m**,
|
||||
**246.9 / 12.95 / 3.00 / 3.31 m**, and **235.5 / 7.94 / 4.50 / 5.37 m**. FI styles 1 and 3
|
||||
also have short cap descents: respectively 22.1 m / 0.59 turns / 5.70 m radius / 6.71 m pitch,
|
||||
and 22.2 m / 0.75 turns / 4.54 m radius / 5.37 m pitch. Main helix grades are about 10.0–10.5°;
|
||||
the walkability audit reports zero grade failures.
|
||||
|
||||
## Audits
|
||||
|
||||
Clang and GCC each ran all eight recipe/style combinations. Every row has 0 blocked capsule
|
||||
samples, 0 unsupported samples, 0 grade failures, 0 open-air violations, connected column ends
|
||||
(2/2), 128/128 route-support samples for each main crossing, and passing walkability. The naturalness wall-relief audit measured 0.509–0.806 m against
|
||||
the 0.200 m minimum; flat runs were 0 m against the 8 m limit. The strict V3 layout validator
|
||||
accepted with zero issues and rejected its malformed controls 9/9.
|
||||
|
||||
The no-long-bridge threshold is 12.5 m. SW has 0 m uncolumned span in all styles. FI's longest
|
||||
short grounded span is 4.464 m (style 0); every other span is shorter. The L26 SW bridge control
|
||||
(1,946–2,005 m unsupported) was detected in all four SW styles. The L24 ungated-support and L23
|
||||
smooth-wall naturalness controls were detected in all eight recipe/style rows. The 16 final matrix logs are
|
||||
`Saved/Linux/linux27-final-audit-{surface-world,floating-islands}-style{0,1,2,3}-{clang,gcc}.log`.
|
||||
|
||||
## Captures
|
||||
|
||||
All five JPEGs are 16:9 and below 300 KB in `captures/`.
|
||||
|
||||
| Capture | View | Size | SHA-256 |
|
||||
|---|---|---:|---|
|
||||
| [sw-column-wide.jpg](captures/sw-column-wide.jpg) | Wide side section: ceiling, full column and local ground; 1280×720 | 27 KB | `63e621794a48747c5892b74d3313da1df93e95bc1fb5e23c511211f992023acf` |
|
||||
| [sw-column-base.jpg](captures/sw-column-base.jpg) | Close base flare into terrain; 1280×720 | 27 KB | `c4dbf7bcbd4962cf0e2c4f567cceec798deac8686a85cd02716ebd3c21db43bf` |
|
||||
| [sw-column-top.jpg](captures/sw-column-top.jpg) | Close ceiling blend and upper helix; 1280×720 | 30 KB | `fe83fbb0bf90ea51696f9f63129738eb55812c01bbbec65e7430b8a7e6b512ed` |
|
||||
| [sw-column-cutaway.jpg](captures/sw-column-cutaway.jpg) | Full-height cutaway through the helix; 1920×1080 | 60 KB | `8233e80035aac69dec7fd6d2e77a46e5798f627aefe216671d884bf84b339c40` |
|
||||
| [fi-anchor-pillar.jpg](captures/fi-anchor-pillar.jpg) | L27 FI anchor pillar from outside; 1280×720 | 21 KB | `5bb8ad6a0729818577764596d3fa9ccff9621b31217ee9d8e34401098e6b4ef6` |
|
||||
|
||||
## Cost, determinism and compatibility
|
||||
|
||||
The whole-view timing comparison alternated two L24 and two L27 Clang W12 runs per recipe. No
|
||||
prebuilt L24 executable was present, so I rebuilt it from local commit `cb9236c` with Clang 19 and
|
||||
FP contraction off under `Saved/Linux/l24-source/bin/linux/`.
|
||||
|
||||
| Recipe | L24 whole-view seconds, two runs (mean) | L27 whole-view seconds, two runs (mean) | Change | Tiles, both versions |
|
||||
|---|---:|---:|---:|---:|
|
||||
| SurfaceWorld | 1.644 / 1.663 (1.654) | 2.207 / 2.252 (2.230) | +0.576 s (+34.8%) | 2,370 total; 960 full detail |
|
||||
| Floating Islands | 15.907 / 16.848 (16.378) | 19.403 / 19.649 (19.526) | +3.149 s (+19.2%) | 2,370 total; 960 full detail |
|
||||
|
||||
No tiles were added. The L24 and L27 per-run logs are `Saved/Linux/linux27-cost-interleaved-*.log`.
|
||||
|
||||
The L25 whole-view command produced identical hashes across Clang/GCC and W1/W8/W12. Hash order
|
||||
is density / mesh / payload / topology:
|
||||
|
||||
| Recipe | L27 whole-view hash |
|
||||
|---|---|
|
||||
| SurfaceWorld | `117547e5e18c6847 / db5fa276315ff2d4 / b4e9d01d70413fea / 03caf881fa047f1c` |
|
||||
| Floating Islands | `70ab5bc87dd174d2 / 6f357f9085a496f0 / 08ce7d4e036da65a / eb5eef04a5cb85d9` |
|
||||
|
||||
Full-detail hashes also match across the matrix: SW `ace56c8f205238ad / a9c603a1248de6b8 /
|
||||
ea55f1f4fcc6a894 / 5ea7bc78a1b678ae`; FI `ed550e0c8b182695 / 0e410a8e80a3e7f7 /
|
||||
0751926f0c67d8a4 / f01208bc2f7841ec`. Exact-query hashes (density / unclamped / detail / collision)
|
||||
are SW `c949c4df2ce6f9d9 / c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849` and FI
|
||||
`7b7c50ac24ccadcc / 7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf`.
|
||||
|
||||
The 24 m crossing processed 314 moving tiles and matched across both compilers:
|
||||
|
||||
| Recipe | Moving-tile hash: density / mesh / payload / topology |
|
||||
|---|---|
|
||||
| SurfaceWorld | `fe83368d746bf44d / d4379e0f86fa5780 / 0d1d6659a251dcaf / 73940f21b0fdaf14` |
|
||||
| Floating Islands | `e3d0ea833bca4215 / f4b27f4bd1adbb07 / 8aedcdc86e5f21a2 / 323f880d952306e9` |
|
||||
|
||||
L13 parity passed for both compilers and recipes: 8 page pairs, 14 higher-LOD pairs, one edit pair,
|
||||
782,026 exact-query samples, zero mismatches. At a 10 km off-route view, L9 proofs checked 6,896
|
||||
blocks / 9,178,576 samples with zero violations on both compilers. SW had 1,296 fallbacks and no
|
||||
unknown blocks; FI had 18,704 fallbacks, 17,408 unknown blocks and zero violations.
|
||||
|
||||
At a 100 km SW view, L26 and L27 were bit-identical on Clang and GCC: full-detail hash
|
||||
`3148a6af10901e4d / 7025b8fc8d4636cd / ca0a8081022340bf / 1c49e72a6636ae99`, whole-view hash
|
||||
`a212129dc5f2bfcf / e9b3980b69c789bf / 785a394605885d1f / 8f7e3a55bf429175`, exact-query hash
|
||||
`482500abbe835b11 / c4948c7179963784 / efcba20c0c70c415 / 137e8f6c0b03a624`; LOD0–8 also match.
|
||||
At 10 km, the whole-view hash changes because the new column enters LOD8; full detail, exact queries
|
||||
and LOD0–7 remain identical.
|
||||
|
||||
Headless binary SHA-256: Clang `81ae5a03765eb3937f53c10e8f8b82cf1b38d3e3b6240511a57e5d66d99a9755`,
|
||||
GCC `30c8927eee3c8c2ec0e7ff72228872435d19527719bedbb2399e6dcaff4819a4`. Rebuilt L24 Clang
|
||||
binary: `29bb68ed425b4770b1b4ddcc68996c5d680665014e7ab7ffb3c307523015cf0b`.
|
||||
|
||||
## Changes and invariants
|
||||
|
||||
The deterministic crossing planner and field composition are shared in
|
||||
`Standalone/Core/src/ShapeWorldInternal.h`. `VoxelForgeCore.h/.cpp` expose the void-crossing audit;
|
||||
`StreamBenchmarkPipeline.h` adds column, open-air and no-long-bridge audits with the L26 negative
|
||||
control. `FloatingIslandsInternal.h` retires the old special-case root for geometry 8, while
|
||||
`OwnerWorldLoader.h` and `WorldAssemblyPrototype.h` use the shared crossing plan and version.
|
||||
Geometry/evaluator versions are now 8/14; resolved-layout schema V3 is unchanged. The only Unreal
|
||||
edit is the matching geometry-version bump in `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`;
|
||||
Unreal was not available to compile here.
|
||||
L4 lazy caches, L7 shared cache, L9/L12 proofs, L11 readiness handling, L13 route parity, L18/L19
|
||||
ceilings and L21/L22 layout behavior remain unchanged. No async path was added.
|
||||
|
||||
## Failed and retried runs
|
||||
|
||||
- Two temporary Clang backtrace builds failed on `signal` namespace qualification; corrected before
|
||||
the instrumentation was removed. The first SW style-0 audit segfaulted because the planner read
|
||||
shared-field intervals before they were built; build order was corrected. An early SW style-1
|
||||
iteration had 3,816 open-air violations and 869 blocked samples at the lower mouth; the carve
|
||||
guard fixed it.
|
||||
- Early FI audit revisions mis-tagged segments and reported false open-air spans; corrected to use
|
||||
the actual route ranges and physical field. FI style 2 briefly had two unsupported samples; the
|
||||
seam extension fixed them. A 3 m cap left 13 FI style-0 open-air samples; the 5 m cap cleared
|
||||
them. A relaxed tread guard introduced one SW style-1 blocked sample and was reverted.
|
||||
- A layout-only invocation without the required streaming flags exited 2. During capture setup,
|
||||
`--hash` and `--data=` were rejected; the correct switches are `--stream-hash` and a positional
|
||||
data path. Those were corrected. A helix-metrics helper first failed while formatting a list and
|
||||
was rerun successfully.
|
||||
- The first close SW export at max clip level 1 hit the 160-million-node interval safety bound.
|
||||
Two finer endpoint exports then grew past 1.2 GB without a final pipeline summary and were
|
||||
stopped; the already-complete full-column cutaway mesh supplied the final base/top views. The
|
||||
first wide camera hid the base; a cut-plane side view fixed it. The first FI close camera cropped
|
||||
the endpoints and was moved back.
|
||||
- The first FI max-clip-level-3 export grew past 1.6 GB without a summary and was stopped. The
|
||||
max-clip-level-8 retry completed in 21.7 s with a 104 MB mesh. The final capture is from that
|
||||
L27 mesh. All rejected, stopped and successful export logs are in `Saved/Linux/`.
|
||||
|
||||
No run exceeded 30 minutes or remained stuck for 15 minutes. Only the requested small JPEGs and
|
||||
this report were written under `Standalone/Research/WorldAssemblerM3/Linux27/`; exports and logs
|
||||
remain in `Saved/Linux/`. The pre-existing untracked `Linux18/exports/` directory was left alone.
|
||||
No commit, push or stash was made.
|
||||
|
||||
## Orchestrator review (Claude) — side branch
|
||||
|
||||
- Look approved by me: `sw-column-wide.jpg` and `sw-column-cutaway.jpg` show one slender ceiling-to-ground
|
||||
column with the helix inside; `sw-column-base.jpg` shows the base meeting the terrain with a natural crack
|
||||
exit; `fi-anchor-pillar.jpg` shows a textured pillar with a flared base under the island.
|
||||
- **Blocking merge: cost.** Whole view +34.8% SW (1.654 → 2.230 s) and +19.2% FI with no new tiles. A local
|
||||
feature must cost locally. L28: bound column/helix evaluation spatially (feature lists / AABB culling), then
|
||||
merge `wip/l25-helix` into `assembler/world`.
|
||||
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,125 @@
|
||||
# Linux L28 — tile-bounded columns and passages
|
||||
|
||||
Branch: wip/l25-helix, based on L27. No commit. No Unreal files changed.
|
||||
|
||||
## Cost and culling
|
||||
|
||||
The plain interleaved baseline puts the L27 overhead in tile streaming/meshing, not shape
|
||||
compilation: mean whole-view time was 2.058 s versus L24's 1.527 s for SurfaceWorld (+34.8%),
|
||||
and 19.961 s versus 16.699 s for FloatingIslands (+19.5%). Shape time stayed near 0.46 s
|
||||
(SurfaceWorld) and 0.97 s (FloatingIslands). The initial extra work was passage composition
|
||||
inside the map-wide density sampling loop.
|
||||
|
||||
GenerateMesh now enters a passage-query scope using the exact inclusive tile sample bounds,
|
||||
including the normal halo. A conservative feature AABB BVH supplies candidate route segments,
|
||||
columns and mouth support. Long route nearest-distance searches use a per-passage segment BVH.
|
||||
L9 proof boxes use a separate passage-bound BVH; disjoint roots reject far boxes without scanning
|
||||
segments. If a native-only negative-control shape has no proof index, its box check falls back to
|
||||
a conservative linear test.
|
||||
|
||||
Instrumented full-view counts (diagnostic build; 2,370 desired tiles):
|
||||
|
||||
| Recipe | Composition samples | Column guard: calls / tiles | Exact column SDF: calls / tiles | Route band samples / tiles | Route SDF unions / tiles | Route samples skipped / tiles | Feature candidates: tiles / total |
|
||||
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||
| SurfaceWorld | 12,039,941 | 1,885,887 / 692 | 3,017 / 4 | 1,380,907 / 870 | 164,337 / 543 | 7,470,572 / 1,792 | 8 / 1,222 |
|
||||
| FloatingIslands | 58,019,623 | 7,619,575 / 689 | 7,213 / 6 | 1,902,086 / 708 | 62,507 / 89 | 54,848,675 / 2,212 | 14 / 1,331 |
|
||||
|
||||
The exact column SDF is spatially culled. The cheaper radial guard remains in tiles sharing the
|
||||
column's Z slab because positive SDF values can lower positive base density; a broader tile skip
|
||||
would need an exact bound on that field. This is part of the remaining SurfaceWorld cost.
|
||||
Per composition sample, the profile counted 0.125 carve-segment tests, 0.036 support-segment tests
|
||||
and 0.0136 route SDF unions in SurfaceWorld; FloatingIslands counted 0.0125, 0.0028 and 0.00108.
|
||||
Column guards ran on 15.7% / 13.1% of samples, while the complex column SDF ran on 0.025% / 0.012%.
|
||||
|
||||
## Interleaved whole-view cost
|
||||
|
||||
Five W12 quartets ran in order L24, L28a, fresh L27-source build, L28b. Hashing was off.
|
||||
Values below are means of the five samples; raw samples are in
|
||||
Saved/Linux/linux28-r28-finalfix-cost5-{recipe}-{l24,l27,r28a,r28b}-p1..p5.log.
|
||||
L24 used Saved/Linux/l24-source/bin/linux/VoxelForgeCoreHeadless-linux24-clang19;
|
||||
L27 was rebuilt from the current L27 source for this comparison.
|
||||
|
||||
| Recipe | L24 | L27 | L28a | L28b | L28a / L28b vs L24 |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| SurfaceWorld | 1.527 s | 2.058 s | 1.592 s | 1.605 s | +4.30% / +5.12% |
|
||||
| FloatingIslands | 16.699 s | 19.961 s | 16.651 s | 16.787 s | -0.29% / +0.53% |
|
||||
|
||||
FloatingIslands returned to L24 cost. SurfaceWorld misses the ~3% target by 1.3–2.1 percentage
|
||||
points. Its remaining excess is about 69–80 ms in streaming wall time and 13–15 ms in shape
|
||||
time; the density-aware radial guard and per-tile query setup/traversal remain. The guard cannot
|
||||
be removed bit-exactly without a proven upper bound on the incoming positive density.
|
||||
|
||||
## Bit parity and audits
|
||||
|
||||
Final Clang and GCC binaries match L27 for both recipes at W1, W8 and W12: all 12 hash records per
|
||||
run match (full detail, whole view, LOD0–8, exact query). This is 12 compiler/recipe/worker
|
||||
combinations with zero mismatches. Logs:
|
||||
Saved/Linux/linux28-r28-hash-finalfix-{clang,gcc}-{surface-world,floating-islands}-w{1,8,12}.log.
|
||||
|
||||
Representative hashes, equal to L27:
|
||||
|
||||
| Recipe | Full detail: density / mesh / payload / topology | Whole view: density / mesh / payload / topology | Exact query: density / detail / collision |
|
||||
|---|---|---|---|
|
||||
| SurfaceWorld | ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae | 117547e5e18c6847 / db5fa276315ff2d4 / b4e9d01d70413fea / 03caf881fa047f1c | c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849 |
|
||||
| FloatingIslands | ed550e0c8b182695 / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec | 70ab5bc87dd174d2 / 6f357f9085a496f0 / 08ce7d4e036da65a / eb5eef04a5cb85d9 | 7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf |
|
||||
|
||||
The 24 m crossing matches fresh L27-source builds on both compilers and recipes:
|
||||
SurfaceWorld, 314 tiles: density 3561ea23485e359f, mesh e8331620fb0fb908,
|
||||
payload 765f7dad67c852aa, topology 3ccc09ac81c05d71.
|
||||
FloatingIslands: density e3d0ea833bca4215, mesh 6b3d0f148ed83d93,
|
||||
payload d6ed85f16acd895e, topology 9745683036522461.
|
||||
|
||||
Across 4 styles × 2 recipes, capsule walkability, open air, naturalness, no-long-bridge,
|
||||
strict-validator controls, route bindings and passage audits pass on final Clang/GCC binaries.
|
||||
Final passage logs are
|
||||
Saved/Linux/linux28-r28-finalpassage-{clang,gcc}-{surface-world,floating-islands}-style{0..3}.log.
|
||||
|
||||
The final route/proof/readiness matrix also passes in all 16 compiler/style/recipe runs:
|
||||
L13 route parity verifies 8/8 page pairs and 14 higher-LOD exact pairs with zero mismatches;
|
||||
the readiness guard catches its negative control; L9 reports zero proof violations.
|
||||
SurfaceWorld has 0 unknown blocks; FloatingIslands has 21,504 fallback/unknown blocks for each
|
||||
style. The diagnostic full-view profile queried 26,946 FI proof boxes with only 40 path-bound and
|
||||
1 column-bound tests, so far boxes reject at the BVH root. These unknowns are not evidence of a
|
||||
fat passage bound. A fresh rerun of the saved L27 binary with the default owner input also reports
|
||||
21,504; older L27 logs showing 17,408 did not reproduce. Final matrix logs are
|
||||
Saved/Linux/linux28-r28-finalmatrix-{clang,gcc}-{surface-world,floating-islands}-style{0..3}.log.
|
||||
|
||||
Two inherited SurfaceWorld FloorProfileBlend checks remain over the 2-voxel threshold in styles
|
||||
1 and 3: 2.544 and 3.645 voxels. The same failures and values are in L27 logs for both compilers;
|
||||
other styles pass.
|
||||
|
||||
## Invariants, edits and run history
|
||||
|
||||
Changed Core files: Standalone/Core/src/ShapeWorldInternal.h and
|
||||
Standalone/Core/src/VoxelForgeCore.cpp. Navigation/design docs and this report were updated.
|
||||
No Unreal edits or Unreal compile. L3 FP-contract preamble/self-test, L4 FLazy, L7 shared cache,
|
||||
L9/L12 proof rules, L11 readiness, L13 route binding, L18/L19 shared ceiling, L21/L22 layout and
|
||||
bands, L23–L27 passage behavior, ARCHITECTURE §8.10 and CLAUDE.md async rules remain intact.
|
||||
|
||||
Failed or retried runs:
|
||||
|
||||
- First parity/readiness command omitted --stream-benchmark and --stream-pipeline; rerun with both.
|
||||
- Initial readiness negative-control crashed on an empty proof BVH in a temporary native-only
|
||||
shape. Added the conservative no-index fallback; final readiness checks pass.
|
||||
- The first final W1 hash command omitted the explicit owner data path and failed to open data
|
||||
under Saved/Linux. Preserved as linux28-r28-hash-finalfix-clang-surface-world-w1-attempt1-failed.log;
|
||||
rerun with Standalone/data/owner_world.json and all final W1/W8 hashes passed.
|
||||
- Initial moving-crossing comparison used stale L27 logs. Rebuilt L27 from source and reran; all
|
||||
four compiler/recipe hashes then matched.
|
||||
- Older saved L27 proof logs reported 17,408 FloatingIslands unknown blocks. Reran the saved L27
|
||||
binaries and compared with final L28: both report 21,504, zero violations.
|
||||
- An exploratory composed-bound FloatingIslands W12 hash differed; the density-threshold variant
|
||||
restored equality and is the version measured here. An interrupted/empty Clang R15 build was
|
||||
rerun successfully.
|
||||
|
||||
No run exceeded 30 minutes or remained stuck for 15 minutes. Builds were Clang 19 and GCC 14,
|
||||
W12 CPU affinity 0–11, with the 6 GiB address-space cap. No commit, push or stash was performed.
|
||||
|
||||
## Orchestrator review (Claude)
|
||||
|
||||
- Re-run by me (W12, no hashing, interleaved): SW L24 1.455/1.485/1.484 s vs L28 1.502/1.539/1.555 s (median +3.7%).
|
||||
Accepted as the cost of the passage/column feature. FI is back to L24 cost.
|
||||
- Bit-identical to L27 (12/12 configs + crossing). With this, `wip/l25-helix` (L25–L28) is merged into
|
||||
`assembler/world`.
|
||||
- Note for later: the pre-existing SW FloorProfileBlend check over threshold in styles 1/3 (2.544/3.645 voxels)
|
||||
is unchanged since L27.
|
||||
@@ -10,7 +10,11 @@
|
||||
#include "VoxelTerrainOpDefinition.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
struct FStandaloneParitySample
|
||||
{
|
||||
@@ -20,6 +24,35 @@ struct FStandaloneParitySample
|
||||
int32 StrateIndex = INDEX_NONE;
|
||||
};
|
||||
|
||||
struct FStandalonePassageVoidCrossing
|
||||
{
|
||||
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{};
|
||||
bool needsColumn=false;
|
||||
bool connectedAtStart=false;
|
||||
bool connectedAtEnd=false;
|
||||
std::int64_t centerXSubvoxels=0;
|
||||
std::int64_t centerYSubvoxels=0;
|
||||
std::int64_t topZSubvoxels=0;
|
||||
std::int64_t bottomZSubvoxels=0;
|
||||
std::int64_t minimumRadiusSubvoxels=0;
|
||||
std::int64_t nominalRadiusSubvoxels=0;
|
||||
std::int64_t maximumRadiusSubvoxels=0;
|
||||
double maximumRouteOffsetMetres=0.0;
|
||||
double maximumBoreRadiusMetres=0.0;
|
||||
};
|
||||
|
||||
struct FStandalonePassageVoidCrossingCache
|
||||
{
|
||||
std::mutex mutex;
|
||||
std::unordered_map<std::string,std::vector<FStandalonePassageVoidCrossing>> byLayout;
|
||||
};
|
||||
|
||||
struct FStandaloneOwnerWorld
|
||||
{
|
||||
std::unique_ptr<UVoxelSettings> Settings;
|
||||
@@ -45,6 +78,8 @@ struct FStandaloneOwnerWorld
|
||||
std::uint32_t CoreVoxelSizeMicrons = 250000;
|
||||
std::uint32_t StrateLayoutChunkSizeMicrometres = 8000000;
|
||||
std::int64_t StrateLayoutChunkWidthSubvoxels = 8192;
|
||||
std::shared_ptr<FStandalonePassageVoidCrossingCache> PassageVoidCrossingCache =
|
||||
std::make_shared<FStandalonePassageVoidCrossingCache>();
|
||||
};
|
||||
|
||||
namespace StandaloneOwnerLoader
|
||||
|
||||
@@ -453,17 +453,31 @@ namespace StandaloneStreamBenchmark
|
||||
std::array<std::array<double,4>,8> firstUnsupported{};
|
||||
std::array<std::array<bool,5>,8> firstSupportMasks{};
|
||||
std::size_t firstBlockedCount=0,firstUnsupportedCount=0;
|
||||
std::array<double,4> firstDeepUnsupported{};
|
||||
std::array<bool,5> firstDeepSupportMask{};
|
||||
bool hasDeepUnsupported=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 auto auditPlayerPath=[&](const VoxelForgeCore::World& Field,
|
||||
const std::vector<std::array<double,3>>& path,
|
||||
double supportStartMetres,double supportStepMetres,
|
||||
double supportMaximumMetres,std::uint32_t minimumSupportProbes,
|
||||
double supportProbeRadiusMetres)
|
||||
double supportProbeRadiusMetres,
|
||||
const VoxelForgeCore::World* nativeField=nullptr,
|
||||
std::vector<FPassageSegmentSupportAudit>* segmentSupport=nullptr)
|
||||
{
|
||||
FWalkAudit 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<std::array<double,2>,8> directions{{
|
||||
{{1.0,0.0}},{{0.7071067811865475,0.7071067811865475}},{{0.0,1.0}},
|
||||
@@ -507,6 +521,9 @@ namespace StandaloneStreamBenchmark
|
||||
const double y=(a[1]+dy*t)*coreMetres;
|
||||
const double z=(a[2]+dz*t)*coreMetres;
|
||||
++audit.centers;
|
||||
FPassageSegmentSupportAudit* segmentAudit=segmentSupport
|
||||
?&(*segmentSupport)[segment-1]:nullptr;
|
||||
if(segmentAudit)++segmentAudit->centers;
|
||||
for(const double h:heights)
|
||||
{
|
||||
const double capOffset=std::max(0.0,std::abs(h)-0.54);
|
||||
@@ -523,6 +540,7 @@ namespace StandaloneStreamBenchmark
|
||||
{{0.0,supportProbeRadiusMetres}},
|
||||
{{0.0,-supportProbeRadiusMetres}}}};
|
||||
std::uint32_t supportedProbes=0;
|
||||
std::uint32_t nativeSupportedProbes=0;
|
||||
double firstUnsupportedX=x,firstUnsupportedY=y,lastUnsupportedDensity=0.0;
|
||||
std::array<bool,5> supportMask{};
|
||||
for(std::size_t offsetIndex=0;offsetIndex<supportOffsets.size();++offsetIndex)
|
||||
@@ -541,12 +559,48 @@ namespace StandaloneStreamBenchmark
|
||||
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;}
|
||||
else{firstUnsupportedX=supportX;firstUnsupportedY=supportY;}
|
||||
}
|
||||
if(nativeField&&segmentAudit
|
||||
&&nativeSupportedProbes<minimumSupportProbes)
|
||||
++segmentAudit->nativeUnsupported;
|
||||
if(supportedProbes<minimumSupportProbes)
|
||||
{
|
||||
++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)
|
||||
{
|
||||
audit.hasDeepUnsupported=true;
|
||||
audit.firstDeepUnsupported={firstUnsupportedX,firstUnsupportedY,
|
||||
footZ,lastUnsupportedDensity};
|
||||
audit.firstDeepSupportMask=supportMask;
|
||||
}
|
||||
if(audit.firstUnsupportedCount<audit.firstUnsupported.size())
|
||||
{
|
||||
const std::size_t sample=audit.firstUnsupportedCount++;
|
||||
@@ -658,6 +712,487 @@ namespace StandaloneStreamBenchmark
|
||||
[index](const auto& s){return s.strateIndex==index;});
|
||||
return it==ResolvedLayout->strates.end()?nullptr:&*it;
|
||||
};
|
||||
const auto controlSnapshot=CoreSnapshot.withoutResolvedLayoutPassages();
|
||||
VoxelForgeCore::World blockedWorld(controlSnapshot);
|
||||
std::string blockedCompileError;
|
||||
const bool blockedCompiled=controlSnapshot
|
||||
&&blockedWorld.compile(ShapeRequest,Scheduler,blockedCompileError);
|
||||
if(!blockedCompiled)
|
||||
{
|
||||
Streamer.Stop();
|
||||
std::fprintf(stderr,"[ResolvedPassageBlockedControl] compile_failed=%s\n",
|
||||
blockedCompileError.c_str());
|
||||
return 10;
|
||||
}
|
||||
struct FPassageOpenAirAudit
|
||||
{
|
||||
std::uint64_t supportSamples=0,violations=0,legacyViolations=0;
|
||||
std::uint64_t columnSamples=0,shortSpanSamples=0;
|
||||
std::uint64_t routeSupportSamples=0,routeSupportViolations=0;
|
||||
std::array<std::array<double,4>,8> firstViolations{};
|
||||
std::size_t firstViolationCount=0;
|
||||
};
|
||||
constexpr double PassageSupportAuditSpacingMetres=0.25;
|
||||
constexpr std::array<double,2> PassageSupportAuditDepthsMetres{{0.125,0.375}};
|
||||
constexpr std::array<std::array<double,2>,16> PassageSupportAuditDirections{{
|
||||
{{1.0000000000000000,0.0000000000000000}},
|
||||
{{0.9238795325112867,0.3826834323650898}},
|
||||
{{0.7071067811865476,0.7071067811865476}},
|
||||
{{0.3826834323650898,0.9238795325112867}},
|
||||
{{0.0000000000000000,1.0000000000000000}},
|
||||
{{-0.3826834323650898,0.9238795325112867}},
|
||||
{{-0.7071067811865476,0.7071067811865476}},
|
||||
{{-0.9238795325112867,0.3826834323650898}},
|
||||
{{-1.0000000000000000,0.0000000000000000}},
|
||||
{{-0.9238795325112867,-0.3826834323650898}},
|
||||
{{-0.7071067811865476,-0.7071067811865476}},
|
||||
{{-0.3826834323650898,-0.9238795325112867}},
|
||||
{{0.0000000000000000,-1.0000000000000000}},
|
||||
{{0.3826834323650898,-0.9238795325112867}},
|
||||
{{0.7071067811865476,-0.7071067811865476}},
|
||||
{{0.9238795325112867,-0.3826834323650898}}}};
|
||||
const auto auditPassageOpenAir=[&](const VoxelForgeCore::World& Field,
|
||||
const VoxelForgeCore::World& Native,
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
||||
std::size_t passageIndex)
|
||||
{
|
||||
FPassageOpenAirAudit audit;
|
||||
const auto inspect=[&](double xMetres,double yMetres,double floorMetres,
|
||||
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;
|
||||
++audit.supportSamples;
|
||||
audit.routeSupportSamples+=routeSupport;
|
||||
const VoxelForgeCore::Vec3d sample{xMetres/coreMetres,yMetres/coreMetres,
|
||||
(floorMetres-depthMetres)/coreMetres};
|
||||
const float nativeDensity=Native.densityAt(sample);
|
||||
const float passageDensity=Field.densityAt(sample);
|
||||
const float legacySupportDensity=std::min(nativeDensity,
|
||||
static_cast<float>(-depthMetres/coreMetres));
|
||||
if(nativeDensity>=0.0f&&legacySupportDensity<0.0f)
|
||||
++audit.legacyViolations;
|
||||
if(nativeDensity>=0.0f&&passageDensity<0.0f)
|
||||
{
|
||||
const bool connectedColumn=!routeSupport
|
||||
&&passageSegment!=std::numeric_limits<std::size_t>::max()
|
||||
&&Field.isPassageColumnSample(passageIndex,passageSegment,sample);
|
||||
if(connectedColumn){++audit.columnSamples;return;}
|
||||
const bool groundedShortSpan=!routeSupport
|
||||
&&passageSegment!=std::numeric_limits<std::size_t>::max()
|
||||
&&Field.isPassageShortSpanSample(passageIndex,passageSegment,sample);
|
||||
if(groundedShortSpan){++audit.shortSpanSamples;return;}
|
||||
audit.routeSupportViolations+=routeSupport;
|
||||
if(audit.firstViolationCount<audit.firstViolations.size())
|
||||
audit.firstViolations[audit.firstViolationCount++]={xMetres,yMetres,
|
||||
floorMetres-depthMetres,passageDensity};
|
||||
++audit.violations;
|
||||
}
|
||||
};
|
||||
const auto inspectFloorDisc=[&](double xMetres,double yMetres,double floorMetres,
|
||||
double supportRadiusMetres,double depthMetres,double supportThicknessMetres,
|
||||
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(
|
||||
supportRadiusMetres/PassageSupportAuditSpacingMetres));
|
||||
for(std::size_t radialStep=0;radialStep<=radialSteps;++radialStep)
|
||||
{
|
||||
const double radial=std::min(supportRadiusMetres,
|
||||
radialStep*PassageSupportAuditSpacingMetres);
|
||||
for(const auto& direction:PassageSupportAuditDirections)
|
||||
inspect(xMetres+direction[0]*radial,yMetres+direction[1]*radial,
|
||||
floorMetres,depthMetres,supportThicknessMetres,routeSupport,
|
||||
passageSegment);
|
||||
}
|
||||
};
|
||||
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 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 length=std::sqrt((bx-ax)*(bx-ax)+(by-ay)*(by-ay)+(bz-az)*(bz-az));
|
||||
const std::size_t intervals=std::max<std::size_t>(1,static_cast<std::size_t>(
|
||||
std::ceil(length/PassageSupportAuditSpacingMetres)));
|
||||
for(std::size_t sample=0;sample<=intervals;++sample)
|
||||
{
|
||||
if(segment>1&&sample==0)continue;
|
||||
const double t=static_cast<double>(sample)/intervals;
|
||||
const double centerX=ax+(bx-ax)*t,centerY=ay+(by-ay)*t;
|
||||
const double centerZ=az+(bz-az)*t;
|
||||
const double radius=qToMetres(a.radiusSubvoxels)
|
||||
+(qToMetres(b.radiusSubvoxels)-qToMetres(a.radiusSubvoxels))*t;
|
||||
const double floorZ=centerZ-radius+0.20;
|
||||
for(const double depth:PassageSupportAuditDepthsMetres)
|
||||
inspectFloorDisc(centerX,centerY,floorZ,radius,depth,1.0,
|
||||
false,segment-1);
|
||||
}
|
||||
}
|
||||
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[&](const auto& value)
|
||||
{return value.strateIndex==passage.upperStrateIndex;});
|
||||
if(upperStratum!=ResolvedLayout->strates.end())
|
||||
{
|
||||
std::array<const VoxelForgeCore::ResolvedLayoutAnchorV2*,5> route{};
|
||||
std::size_t routeCount=0;
|
||||
for(const auto& anchor:upperStratum->anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||
{
|
||||
if(routeCount<route.size())route[routeCount++]=&anchor;
|
||||
else
|
||||
{
|
||||
std::move(route.begin()+1,route.end(),route.begin());
|
||||
route.back()=&anchor;
|
||||
}
|
||||
}
|
||||
const double mouthX=qToMetres(passage.upperMouthSubvoxels[0]);
|
||||
const double mouthY=qToMetres(passage.upperMouthSubvoxels[1]);
|
||||
constexpr double RouteSupportReachMetres=13.0;
|
||||
constexpr double RouteFloorOffsetMetres=1.20;
|
||||
constexpr double RouteSupportThicknessMetres=1.0;
|
||||
constexpr double RouteSupportDepthStepMetres=0.125;
|
||||
for(std::size_t segment=1;segment<routeCount;++segment)
|
||||
{
|
||||
const auto& a=*route[segment-1];
|
||||
const auto& b=*route[segment];
|
||||
const double ax=qToMetres(a.values[0]),ay=qToMetres(a.values[1]);
|
||||
const double az=qToMetres(a.values[2]);
|
||||
const double bx=qToMetres(b.values[0]),by=qToMetres(b.values[1]);
|
||||
const double bz=qToMetres(b.values[2]);
|
||||
const double dx=bx-ax,dy=by-ay,dz=bz-az;
|
||||
const double length=std::sqrt(dx*dx+dy*dy+dz*dz);
|
||||
const std::size_t intervals=std::max<std::size_t>(1,
|
||||
static_cast<std::size_t>(std::ceil(length
|
||||
/PassageSupportAuditSpacingMetres)));
|
||||
const double routeRadius=a.values.size()>=4
|
||||
?std::max(0.75,qToMetres(a.values[3])):0.75;
|
||||
for(std::size_t sample=0;sample<=intervals;++sample)
|
||||
{
|
||||
if(segment>1&&sample==0)continue;
|
||||
const double t=static_cast<double>(sample)/intervals;
|
||||
const double centerX=ax+dx*t,centerY=ay+dy*t;
|
||||
const double centerZ=az+dz*t;
|
||||
for(std::size_t radialStep=0;radialStep<=static_cast<std::size_t>(
|
||||
std::ceil(routeRadius/PassageSupportAuditSpacingMetres));++radialStep)
|
||||
{
|
||||
const double radial=std::min(routeRadius,
|
||||
radialStep*PassageSupportAuditSpacingMetres);
|
||||
for(const auto& direction:PassageSupportAuditDirections)
|
||||
{
|
||||
const double x=centerX+direction[0]*radial;
|
||||
const double y=centerY+direction[1]*radial;
|
||||
if(std::hypot(x-mouthX,y-mouthY)>RouteSupportReachMetres)continue;
|
||||
const double floorZ=centerZ-RouteFloorOffsetMetres;
|
||||
for(double depth=RouteSupportDepthStepMetres;
|
||||
depth<=RouteSupportThicknessMetres+1.0e-6;
|
||||
depth+=RouteSupportDepthStepMetres)
|
||||
inspect(x,y,floorZ,depth,RouteSupportThicknessMetres,true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return audit;
|
||||
};
|
||||
struct FPassageColumnAudit
|
||||
{
|
||||
std::uint64_t columns=0,shortSpans=0,connectedEnds=0,routeSections=0;
|
||||
std::uint64_t routeSectionFailures=0,wallSections=0,wallFailures=0;
|
||||
double minimumWallMetres=std::numeric_limits<double>::infinity();
|
||||
double minimumSurfaceRadiusMetres=std::numeric_limits<double>::infinity();
|
||||
double maximumSurfaceRadiusMetres=0.0;
|
||||
double surfaceRadiusReliefMetres=0.0;
|
||||
double longestShortSpanMetres=0.0;
|
||||
bool passed=true;
|
||||
};
|
||||
const auto auditPassageColumns=[&](const VoxelForgeCore::World& Field,
|
||||
const VoxelForgeCore::World& Native,
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
||||
std::size_t passageIndex)
|
||||
{
|
||||
FPassageColumnAudit 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 columnSolidAt=[&](std::size_t segment,double x,double y,double z)
|
||||
{
|
||||
return Field.isPassageColumnSample(passageIndex,segment,
|
||||
{x/coreMetres,y/coreMetres,z/coreMetres});
|
||||
};
|
||||
const auto shortSpanSolidAt=[&](std::size_t segment,double x,double y,double z)
|
||||
{
|
||||
return Field.isPassageShortSpanSample(passageIndex,segment,
|
||||
{x/coreMetres,y/coreMetres,z/coreMetres});
|
||||
};
|
||||
const auto crossings=Field.passageVoidCrossings();
|
||||
for(const auto& crossing:crossings)
|
||||
{
|
||||
if(crossing.passageIndex!=passageIndex)continue;
|
||||
const bool connectedStart=endpointConnected(crossing.startPointSubvoxels);
|
||||
const bool connectedEnd=endpointConnected(crossing.endPointSubvoxels);
|
||||
if(!crossing.needsColumn)
|
||||
{
|
||||
++audit.shortSpans;
|
||||
audit.longestShortSpanMetres=std::max(audit.longestShortSpanMetres,
|
||||
crossing.unsupportedLengthMetres);
|
||||
const bool shortSpanPass=connectedStart&&connectedEnd
|
||||
&&crossing.unsupportedLengthMetres<=12.5
|
||||
&&crossing.lastSegment+1<passage.controlPoints.size();
|
||||
audit.passed&=shortSpanPass;
|
||||
std::printf("[PassageGroundedShortSpanAudit] id=%s segments=%zu..%zu length_m=%.3f threshold_m=12.500 connected=%d/2 status=%s\n",
|
||||
passage.stableId.c_str(),crossing.firstSegment,crossing.lastSegment,
|
||||
crossing.unsupportedLengthMetres,
|
||||
static_cast<int>(connectedStart)+static_cast<int>(connectedEnd),
|
||||
shortSpanPass?"PASS":"FAIL");
|
||||
const std::size_t segment=std::min(crossing.firstSegment,
|
||||
passage.controlPoints.size()-2);
|
||||
const auto& a=passage.controlPoints[segment];
|
||||
const auto& b=passage.controlPoints[segment+1];
|
||||
const double x=(qToMetres(a.positionSubvoxels[0])
|
||||
+qToMetres(b.positionSubvoxels[0]))*0.5;
|
||||
const double y=(qToMetres(a.positionSubvoxels[1])
|
||||
+qToMetres(b.positionSubvoxels[1]))*0.5;
|
||||
const double z=(qToMetres(a.positionSubvoxels[2])
|
||||
+qToMetres(b.positionSubvoxels[2]))*0.5
|
||||
-qToMetres(a.radiusSubvoxels+b.radiusSubvoxels)*0.5+0.13;
|
||||
audit.passed&=shortSpanSolidAt(segment,x,y,z-0.25);
|
||||
continue;
|
||||
}
|
||||
if(crossing.lastSegment+1>=passage.controlPoints.size())
|
||||
{audit.passed=false;continue;}
|
||||
++audit.columns;
|
||||
audit.connectedEnds+=connectedStart+connectedEnd;
|
||||
const double axisX=qToMetres(crossing.columnAxisSubvoxels[0]);
|
||||
const double axisY=qToMetres(crossing.columnAxisSubvoxels[1]);
|
||||
const double minimumRadius=qToMetres(crossing.columnMinimumRadiusSubvoxels);
|
||||
const double maximumRadius=qToMetres(crossing.columnMaximumRadiusSubvoxels);
|
||||
const double minimumWall=minimumRadius-crossing.maximumRouteOffsetMetres
|
||||
-crossing.maximumBoreRadiusMetres-0.50;
|
||||
audit.minimumWallMetres=std::min(audit.minimumWallMetres,minimumWall);
|
||||
const std::size_t segmentCount=crossing.lastSegment-crossing.firstSegment+1;
|
||||
const std::size_t probeCount=std::min<std::size_t>(128,segmentCount);
|
||||
std::uint64_t crossingRouteFailures=0,crossingWallFailures=0;
|
||||
for(std::size_t probe=0;probe<probeCount;++probe)
|
||||
{
|
||||
const std::size_t segment=crossing.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 VoxelForgeCore::Vec3d supportPoint{
|
||||
x/coreMetres,y/coreMetres,(floor-2.0)/coreMetres};
|
||||
const bool solidUnderRoute=columnSolidAt(segment,x,y,floor-2.0)
|
||||
||Native.densityAt(supportPoint)<0.0f;
|
||||
++audit.routeSections;
|
||||
audit.routeSectionFailures+=!solidUnderRoute;
|
||||
crossingRouteFailures+=!solidUnderRoute;
|
||||
const double radial=std::hypot(x-axisX,y-axisY);
|
||||
const double wallRadius=radial+radius+1.5;
|
||||
const double outwardX=radial>1.0e-6?(x-axisX)/radial:1.0;
|
||||
const double outwardY=radial>1.0e-6?(y-axisY)/radial:0.0;
|
||||
const bool wallSolid=columnSolidAt(segment,
|
||||
axisX+outwardX*wallRadius,axisY+outwardY*wallRadius,z);
|
||||
++audit.wallSections;
|
||||
audit.wallFailures+=!wallSolid;
|
||||
crossingWallFailures+=!wallSolid;
|
||||
}
|
||||
double crossingMinimumSurfaceRadius=std::numeric_limits<double>::infinity();
|
||||
double crossingMaximumSurfaceRadius=0.0;
|
||||
std::array<double,6> meanRadiusByHeight{};
|
||||
std::size_t heightSamples=0;
|
||||
constexpr double RayStepMetres=0.25;
|
||||
constexpr std::array<double,6> HeightFractions{{0.25,0.35,0.45,0.55,0.65,0.75}};
|
||||
constexpr std::size_t DirectionCount=16;
|
||||
const double topMetres=qToMetres(crossing.columnTopZSubvoxels);
|
||||
const double bottomMetres=qToMetres(crossing.columnBottomZSubvoxels);
|
||||
const double rayStart=crossing.maximumRouteOffsetMetres
|
||||
+crossing.maximumBoreRadiusMetres+0.75;
|
||||
for(const double heightFraction:HeightFractions)
|
||||
{
|
||||
const double z=topMetres+(bottomMetres-topMetres)*heightFraction;
|
||||
double heightMeanRadius=0.0;
|
||||
std::uint64_t heightRayCount=0;
|
||||
for(std::size_t direction=0;direction<DirectionCount;++direction)
|
||||
{
|
||||
const double angle=2.0*3.14159265358979323846
|
||||
*direction/DirectionCount;
|
||||
double lastSolidRadius=0.0;
|
||||
bool enteredColumn=false;
|
||||
const std::size_t raySteps=static_cast<std::size_t>(std::ceil(
|
||||
(maximumRadius+2.0-rayStart)/RayStepMetres));
|
||||
for(std::size_t step=0;step<=raySteps;++step)
|
||||
{
|
||||
const double rayRadius=rayStart+step*RayStepMetres;
|
||||
const VoxelForgeCore::Vec3d point{
|
||||
(axisX+rayRadius*std::cos(angle))/coreMetres,
|
||||
(axisY+rayRadius*std::sin(angle))/coreMetres,
|
||||
z/coreMetres};
|
||||
if(Field.densityAt(point)<0.0f)
|
||||
{enteredColumn=true;lastSolidRadius=rayRadius;}
|
||||
else if(enteredColumn)break;
|
||||
}
|
||||
if(!enteredColumn)continue;
|
||||
heightMeanRadius+=lastSolidRadius;
|
||||
++heightRayCount;
|
||||
crossingMinimumSurfaceRadius=std::min(crossingMinimumSurfaceRadius,
|
||||
lastSolidRadius);
|
||||
crossingMaximumSurfaceRadius=std::max(crossingMaximumSurfaceRadius,
|
||||
lastSolidRadius);
|
||||
}
|
||||
if(heightRayCount==0)continue;
|
||||
meanRadiusByHeight[heightSamples++]=heightMeanRadius/heightRayCount;
|
||||
}
|
||||
const double crossingSurfaceRelief=heightSamples>=2
|
||||
?*std::max_element(meanRadiusByHeight.begin(),
|
||||
meanRadiusByHeight.begin()+heightSamples)
|
||||
-*std::min_element(meanRadiusByHeight.begin(),
|
||||
meanRadiusByHeight.begin()+heightSamples):0.0;
|
||||
audit.minimumSurfaceRadiusMetres=std::min(audit.minimumSurfaceRadiusMetres,
|
||||
crossingMinimumSurfaceRadius);
|
||||
audit.maximumSurfaceRadiusMetres=std::max(audit.maximumSurfaceRadiusMetres,
|
||||
crossingMaximumSurfaceRadius);
|
||||
audit.surfaceRadiusReliefMetres=std::max(audit.surfaceRadiusReliefMetres,
|
||||
crossingSurfaceRelief);
|
||||
const double measuredWall=crossingMinimumSurfaceRadius
|
||||
-crossing.maximumRouteOffsetMetres-crossing.maximumBoreRadiusMetres;
|
||||
const bool columnPass=connectedStart&&connectedEnd
|
||||
&&minimumWall>=1.50&&crossingRouteFailures==0
|
||||
&&crossingWallFailures==0&&std::isfinite(measuredWall)
|
||||
&&measuredWall>=1.50&&crossingSurfaceRelief>=0.20;
|
||||
audit.passed&=columnPass;
|
||||
std::printf("[PassageRockColumnAudit] id=%s segments=%zu..%zu span_m=%.3f axis_m=(%.3f,%.3f) connected=%d/2 route_support=%llu/%llu wall_margin_m=%.3f threshold_m=1.500 radii_m=%.3f..%.3f relief_p2p_m=%.3f nominal_radius_m=%.3f status=%s\n",
|
||||
passage.stableId.c_str(),crossing.firstSegment,crossing.lastSegment,
|
||||
crossing.unsupportedLengthMetres,axisX,axisY,
|
||||
static_cast<int>(connectedStart)+static_cast<int>(connectedEnd),
|
||||
static_cast<unsigned long long>(probeCount-crossingRouteFailures),
|
||||
static_cast<unsigned long long>(probeCount),measuredWall,
|
||||
crossingMinimumSurfaceRadius,crossingMaximumSurfaceRadius,
|
||||
crossingSurfaceRelief,qToMetres(crossing.columnNominalRadiusSubvoxels),
|
||||
columnPass?"PASS":"FAIL");
|
||||
}
|
||||
return audit;
|
||||
};
|
||||
if(Assembly)
|
||||
for(const auto& stratum:ResolvedLayout->strates)
|
||||
{
|
||||
if(stratum.generatorKind!="FloatingIslands")continue;
|
||||
const auto anchor=std::find_if(stratum.anchors.begin(),stratum.anchors.end(),
|
||||
[](const auto& value){return value.kind=="passage_anchor"&&value.values.size()>=7;});
|
||||
const auto islandPlan=std::find_if(Assembly->floatingIslands.begin(),
|
||||
Assembly->floatingIslands.end(),[&](const auto& value)
|
||||
{return value.strateIndex==stratum.strateIndex;});
|
||||
if(anchor==stratum.anchors.end()||islandPlan==Assembly->floatingIslands.end())continue;
|
||||
if(islandPlan->manifest.recipe.geometryVersion>=8)continue;
|
||||
const auto ordinal=islandPlan->manifest.recipe.anchorIslandOrdinal;
|
||||
if(ordinal>=islandPlan->manifest.plan.islands.size())continue;
|
||||
const auto& anchorIsland=islandPlan->manifest.plan.islands[ordinal];
|
||||
const double axisX=qToMetres(anchor->values[0]);
|
||||
const double axisY=qToMetres(anchor->values[1]);
|
||||
const double rootBottom=qToMetres(anchor->values[5]);
|
||||
const auto& islandRecipe=islandPlan->manifest.recipe;
|
||||
const std::int64_t crustRatioQ16=islandRecipe.thicknessRatioQ16
|
||||
+islandRecipe.crustDepthBonusQ16;
|
||||
const std::int64_t crustDepthQ=(anchorIsland.radiusSubvoxels*crustRatioQ16
|
||||
+32768)/65536;
|
||||
const double rootTop=qToMetres(anchorIsland.topZSubvoxels-crustDepthQ);
|
||||
const double nominalRootRadius=qToMetres(anchor->values[2]);
|
||||
double helixMinimumRadius=std::numeric_limits<double>::infinity();
|
||||
double helixMaximumRadius=0.0;
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
if(passage.upperStrateIndex==stratum.strateIndex)
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
const double z=qToMetres(point.positionSubvoxels[2]);
|
||||
if(z<rootBottom||z>rootTop)continue;
|
||||
const double radial=std::hypot(
|
||||
qToMetres(point.positionSubvoxels[0]-anchor->values[0]),
|
||||
qToMetres(point.positionSubvoxels[1]-anchor->values[1]));
|
||||
helixMinimumRadius=std::min(helixMinimumRadius,radial);
|
||||
helixMaximumRadius=std::max(helixMaximumRadius,radial);
|
||||
}
|
||||
if(!std::isfinite(helixMinimumRadius))continue;
|
||||
double minimumMeasuredRootRadius=std::numeric_limits<double>::infinity();
|
||||
std::uint64_t rootRaySamples=0;
|
||||
constexpr std::array<double,5> RootHeightFractions{{0.01,0.25,0.5,0.75,0.99}};
|
||||
for(const double fraction:RootHeightFractions)
|
||||
{
|
||||
const double z=rootTop+(rootBottom-rootTop)*fraction;
|
||||
double minimumRadius=std::numeric_limits<double>::infinity();
|
||||
double maximumRadius=0.0,meanRadius=0.0;
|
||||
constexpr std::size_t DirectionCount=32;
|
||||
constexpr double RayStepMetres=0.125;
|
||||
const std::size_t raySteps=static_cast<std::size_t>(std::ceil(
|
||||
(nominalRootRadius+2.0)/RayStepMetres));
|
||||
for(std::size_t direction=0;direction<DirectionCount;++direction)
|
||||
{
|
||||
const double angle=2.0*3.14159265358979323846*direction/DirectionCount;
|
||||
double radius=0.0;
|
||||
for(std::size_t step=0;step<=raySteps;++step)
|
||||
{
|
||||
const double probeRadius=step*RayStepMetres;
|
||||
const VoxelForgeCore::Vec3d probe{
|
||||
(axisX+probeRadius*std::cos(angle))/coreMetres,
|
||||
(axisY+probeRadius*std::sin(angle))/coreMetres,z/coreMetres};
|
||||
if(blockedWorld.densityAt(probe)>=0.0f)break;
|
||||
radius=probeRadius;
|
||||
}
|
||||
minimumRadius=std::min(minimumRadius,radius);
|
||||
maximumRadius=std::max(maximumRadius,radius);
|
||||
meanRadius+=radius;
|
||||
++rootRaySamples;
|
||||
}
|
||||
meanRadius/=DirectionCount;
|
||||
minimumMeasuredRootRadius=std::min(minimumMeasuredRootRadius,minimumRadius);
|
||||
std::printf("[PassageRootRadiusMeasure] strate=%d z_m=%.3f solid_radius_min_m=%.3f mean_m=%.3f max_m=%.3f nominal_root_m=%.3f rays=%zu\n",
|
||||
stratum.strateIndex,z,minimumRadius,meanRadius,maximumRadius,
|
||||
nominalRootRadius,DirectionCount);
|
||||
}
|
||||
const auto firstRootPassage=std::find_if(ResolvedLayout->passages.begin(),
|
||||
ResolvedLayout->passages.end(),[&](const auto& passage)
|
||||
{return passage.upperStrateIndex==stratum.strateIndex;});
|
||||
const double mouthBoreRadius=firstRootPassage==ResolvedLayout->passages.end()?0.0
|
||||
:qToMetres(firstRootPassage->mouthRadiusSubvoxels);
|
||||
const double midBoreRadius=firstRootPassage==ResolvedLayout->passages.end()?0.0
|
||||
:qToMetres(firstRootPassage->midRadiusSubvoxels);
|
||||
const double maximumBoreRadius=std::max(mouthBoreRadius,midBoreRadius);
|
||||
std::printf("[PassageRootMeasure] strate=%d axis_m=(%.3f,%.3f) helix_centerline_radius_min_m=%.3f max_m=%.3f mouth_bore_radius_m=%.3f mid_bore_radius_m=%.3f nominal_root_m=%.3f measured_min_root_m=%.3f minimum_wall_m=%.3f samples=%llu\n",
|
||||
stratum.strateIndex,axisX,axisY,helixMinimumRadius,helixMaximumRadius,
|
||||
mouthBoreRadius,midBoreRadius,nominalRootRadius,minimumMeasuredRootRadius,
|
||||
minimumMeasuredRootRadius-helixMaximumRadius-maximumBoreRadius,
|
||||
static_cast<unsigned long long>(rootRaySamples));
|
||||
}
|
||||
const auto mouthInContent=[&](std::int32_t index,const std::array<std::int64_t,3>& mouth)
|
||||
{
|
||||
const auto* stratum=findStratum(index);if(!stratum)return false;
|
||||
@@ -667,8 +1202,38 @@ namespace StandaloneStreamBenchmark
|
||||
return zUm>=stratum->content.bottomMicrometres&&zUm<stratum->content.topMicrometres;
|
||||
};
|
||||
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0;
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
std::uint64_t openAirViolations=0,openAirLegacyViolations=0,openAirSamples=0;
|
||||
std::uint64_t openAirAudits=0,openAirColumnSamples=0,openAirShortSpanSamples=0;
|
||||
std::uint64_t columnCrossings=0,shortGroundedSpans=0,columnConnectedEnds=0;
|
||||
std::uint64_t columnRouteFailures=0,columnWallFailures=0;
|
||||
double longestUncolumnedSpanMetres=0.0,longestShortSpanMetres=0.0;
|
||||
double l26LegacyBridgeSpanMetres=0.0;
|
||||
bool columnAuditsPassed=true,noLongBridgeAuditPassed=true;
|
||||
bool l26NegativeControlApplicable=false;
|
||||
const auto plannedVoidCrossings=Shape.passageVoidCrossings();
|
||||
for(const auto& crossing:plannedVoidCrossings)
|
||||
{
|
||||
if(crossing.needsColumn)continue;
|
||||
longestUncolumnedSpanMetres=std::max(longestUncolumnedSpanMetres,
|
||||
crossing.unsupportedLengthMetres);
|
||||
}
|
||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
||||
++passageIndex)
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
const auto* upper=findStratum(passage.upperStrateIndex);
|
||||
const auto* lower=findStratum(passage.lowerStrateIndex);
|
||||
if(!upper||!lower||lower->generatorKind!="SurfaceWorld"
|
||||
||upper->generatorKind=="FloatingIslands")continue;
|
||||
l26NegativeControlApplicable=true;
|
||||
for(const auto& crossing:plannedVoidCrossings)
|
||||
if(crossing.passageIndex==passageIndex)
|
||||
l26LegacyBridgeSpanMetres=std::max(l26LegacyBridgeSpanMetres,
|
||||
crossing.unsupportedLengthMetres);
|
||||
}
|
||||
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());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
@@ -678,8 +1243,28 @@ namespace StandaloneStreamBenchmark
|
||||
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
||||
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 FPassageOpenAirAudit openAirAudit=auditPassageOpenAir(Shape,blockedWorld,
|
||||
passage,passageIndex);
|
||||
const FPassageColumnAudit columnAudit=auditPassageColumns(Shape,blockedWorld,
|
||||
passage,passageIndex);
|
||||
openAirSamples+=openAirAudit.supportSamples;
|
||||
openAirViolations+=openAirAudit.violations;
|
||||
openAirLegacyViolations+=openAirAudit.legacyViolations;
|
||||
openAirColumnSamples+=openAirAudit.columnSamples;
|
||||
openAirShortSpanSamples+=openAirAudit.shortSpanSamples;
|
||||
columnCrossings+=columnAudit.columns;
|
||||
shortGroundedSpans+=columnAudit.shortSpans;
|
||||
columnConnectedEnds+=columnAudit.connectedEnds;
|
||||
columnRouteFailures+=columnAudit.routeSectionFailures;
|
||||
columnWallFailures+=columnAudit.wallFailures;
|
||||
longestShortSpanMetres=std::max(longestShortSpanMetres,
|
||||
columnAudit.longestShortSpanMetres);
|
||||
columnAuditsPassed&=columnAudit.passed;
|
||||
++openAirAudits;
|
||||
const auto radialSquared=[](const std::array<std::int64_t,3>& p)
|
||||
{return p[0]*p[0]+p[1]*p[1];};
|
||||
const bool mouthsInRadius=radialSquared(passage.upperMouthSubvoxels)
|
||||
@@ -706,15 +1291,108 @@ namespace StandaloneStreamBenchmark
|
||||
static_cast<unsigned long long>(naturalAudit.wallSamples),
|
||||
static_cast<unsigned long long>(naturalAudit.missingWallSamples),
|
||||
naturalAudit.passed?"PASS":"FAIL");
|
||||
std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%llu column_samples=%llu short_span_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),
|
||||
static_cast<unsigned long long>(openAirAudit.violations),
|
||||
static_cast<unsigned long long>(openAirAudit.columnSamples),
|
||||
static_cast<unsigned long long>(openAirAudit.shortSpanSamples),
|
||||
static_cast<unsigned long long>(openAirAudit.legacyViolations),
|
||||
static_cast<unsigned long long>(openAirAudit.routeSupportSamples),
|
||||
static_cast<unsigned long long>(openAirAudit.routeSupportViolations),
|
||||
PassageSupportAuditSpacingMetres,
|
||||
openAirAudit.violations==0?"PASS":"FAIL");
|
||||
for(std::size_t i=0;i<openAirAudit.firstViolationCount;++i)
|
||||
std::printf("[PassageOpenAirAuditSample] id=%s kind=unsupported_support xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
||||
passage.stableId.c_str(),openAirAudit.firstViolations[i][0],
|
||||
openAirAudit.firstViolations[i][1],openAirAudit.firstViolations[i][2],
|
||||
openAirAudit.firstViolations[i][3]);
|
||||
for(std::size_t i=0;i<passageAudit.firstBlockedCount;++i)
|
||||
std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
||||
passage.stableId.c_str(),passageAudit.firstBlocked[i][0],passageAudit.firstBlocked[i][1],
|
||||
passageAudit.firstBlocked[i][2],passageAudit.firstBlocked[i][3]);
|
||||
{
|
||||
const auto& sample=passageAudit.firstBlocked[i];
|
||||
double nearestDistanceSquared=std::numeric_limits<double>::infinity();
|
||||
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 passageColumn=Shape.isPassageColumnSample(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 passage_column=%d native_path=%s\n",
|
||||
passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3],
|
||||
nativeDensity,nearestSegment,nearestT,nearestPassageSegment,
|
||||
nearestPassageSdf,passageColumn?1:0,
|
||||
blockedWorld.densityDispatchPathAt(nativePosition).c_str());
|
||||
}
|
||||
for(std::size_t i=0;i<passageAudit.firstUnsupportedCount;++i)
|
||||
{
|
||||
std::printf("[ResolvedPassageAuditSample] id=%s kind=unsupported xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
||||
passage.stableId.c_str(),passageAudit.firstUnsupported[i][0],
|
||||
passageAudit.firstUnsupported[i][1],passageAudit.firstUnsupported[i][2],
|
||||
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 columnAtSupport=Shape.isPassageColumnSample(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) column_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],columnAtSupport?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;
|
||||
struct FRouteAuditInput
|
||||
@@ -787,7 +1465,7 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
const FWalkAudit routeAudit=auditPlayerPath(Shape,route,supportStartMetres,
|
||||
supportStepMetres,supportMaximumMetres,minimumSupportProbes,
|
||||
supportProbeRadiusMetres);
|
||||
supportProbeRadiusMetres,nullptr,nullptr);
|
||||
routeAuditInputs.push_back({stratum.strateIndex,route,supportStartMetres,
|
||||
supportStepMetres,supportMaximumMetres,supportProbeRadiusMetres,
|
||||
minimumSupportProbes});
|
||||
@@ -798,15 +1476,28 @@ namespace StandaloneStreamBenchmark
|
||||
{return a[0]==b[0]&&a[1]==b[1]&&a[2]==b[2];};
|
||||
if(passage.upperStrateIndex==stratum.strateIndex)
|
||||
{
|
||||
const std::array<std::int64_t,3> departure{{anchors.back()->values[0],
|
||||
anchors.back()->values[1],anchors.back()->values[2]}};
|
||||
const auto rootedMouth=std::find_if(stratum.anchors.begin(),
|
||||
stratum.anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="passage_anchor"&&anchor.values.size()>=10;});
|
||||
const std::array<std::int64_t,3> departure=
|
||||
rootedMouth!=stratum.anchors.end()
|
||||
?std::array<std::int64_t,3>{{rootedMouth->values[7],
|
||||
rootedMouth->values[8],rootedMouth->values[9]}}
|
||||
:std::array<std::int64_t,3>{{anchors.back()->values[0],
|
||||
anchors.back()->values[1],anchors.back()->values[2]}};
|
||||
binding&=equalPosition(passage.upperMouthSubvoxels,
|
||||
departure);
|
||||
}
|
||||
if(passage.lowerStrateIndex==stratum.strateIndex)
|
||||
{
|
||||
const std::array<std::int64_t,3> arrival{{anchors.front()->values[0],
|
||||
anchors.front()->values[1],anchors.front()->values[2]}};
|
||||
const auto passageArrival=std::find_if(stratum.anchors.begin(),
|
||||
stratum.anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="passage_arrival"&&anchor.values.size()>=3;});
|
||||
const std::array<std::int64_t,3> arrival=passageArrival!=stratum.anchors.end()
|
||||
?std::array<std::int64_t,3>{{passageArrival->values[0],
|
||||
passageArrival->values[1],passageArrival->values[2]}}
|
||||
:std::array<std::int64_t,3>{{anchors.front()->values[0],
|
||||
anchors.front()->values[1],anchors.front()->values[2]}};
|
||||
binding&=equalPosition(passage.lowerMouthSubvoxels,
|
||||
arrival);
|
||||
}
|
||||
@@ -831,43 +1522,69 @@ namespace StandaloneStreamBenchmark
|
||||
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
|
||||
routeAudit.firstUnsupported[i][3]);
|
||||
}
|
||||
const auto controlSnapshot=CoreSnapshot.withoutResolvedLayoutPassages();
|
||||
VoxelForgeCore::World blockedWorld(controlSnapshot);
|
||||
std::string blockedCompileError;
|
||||
const bool blockedCompiled=controlSnapshot
|
||||
&&blockedWorld.compile(ShapeRequest,Scheduler,blockedCompileError);
|
||||
if(blockedCompiled)
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
const double playerZ=static_cast<double>(point.positionSubvoxels[2]
|
||||
-point.radiusSubvoxels)+1.13*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
||||
*1000000.0/World.CoreVoxelSizeMicrons;
|
||||
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
||||
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
||||
}
|
||||
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
||||
0.125,0.125,0.25,3,0.45);
|
||||
blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed;
|
||||
std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n",
|
||||
passage.stableId.c_str(),static_cast<unsigned long long>(blockedAudit.blocked),
|
||||
blockedAudit.blocked>0&&!blockedAudit.passed?"true":"false");
|
||||
}
|
||||
else
|
||||
std::fprintf(stderr,"[ResolvedPassageBlockedControl] compile_failed=%s\n",blockedCompileError.c_str());
|
||||
if(blockedCompiled)
|
||||
for(const auto& input:routeAuditInputs)
|
||||
{
|
||||
const FWalkAudit controlRoute=auditPlayerPath(blockedWorld,input.path,
|
||||
input.supportStart,input.supportStep,input.supportMaximum,
|
||||
input.minimumSupportProbes,input.supportProbeRadius);
|
||||
std::printf("[ResolvedStrateRouteBlockedControl] strate=%d blocked=%llu unsupported=%llu status=%s\n",
|
||||
input.strateIndex,static_cast<unsigned long long>(controlRoute.blocked),
|
||||
static_cast<unsigned long long>(controlRoute.unsupported),
|
||||
controlRoute.passed?"PASS":"FAIL");
|
||||
const double playerZ=static_cast<double>(point.positionSubvoxels[2]
|
||||
-point.radiusSubvoxels)+1.13*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
||||
*1000000.0/World.CoreVoxelSizeMicrons;
|
||||
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
||||
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
||||
}
|
||||
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
||||
0.125,0.125,0.25,3,0.45,nullptr,nullptr);
|
||||
blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed;
|
||||
std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n",
|
||||
passage.stableId.c_str(),static_cast<unsigned long long>(blockedAudit.blocked),
|
||||
blockedAudit.blocked>0&&!blockedAudit.passed?"true":"false");
|
||||
}
|
||||
for(const auto& input:routeAuditInputs)
|
||||
{
|
||||
const FWalkAudit controlRoute=auditPlayerPath(blockedWorld,input.path,
|
||||
input.supportStart,input.supportStep,input.supportMaximum,
|
||||
input.minimumSupportProbes,input.supportProbeRadius,nullptr,nullptr);
|
||||
std::printf("[ResolvedStrateRouteBlockedControl] strate=%d blocked=%llu unsupported=%llu status=%s\n",
|
||||
input.strateIndex,static_cast<unsigned long long>(controlRoute.blocked),
|
||||
static_cast<unsigned long long>(controlRoute.unsupported),
|
||||
controlRoute.passed?"PASS":"FAIL");
|
||||
}
|
||||
const bool l24OpenAirControlDetected=openAirLegacyViolations>0;
|
||||
std::printf("[PassageOpenAirNegativeControl] source=L24_ungated_support unsupported_samples=%llu detected=%s\n",
|
||||
static_cast<unsigned long long>(openAirLegacyViolations),
|
||||
l24OpenAirControlDetected?"true":"false");
|
||||
std::printf("[PassageOpenAirAuditSummary] interfaces=%llu samples=%llu violations=%llu column_samples=%llu short_span_samples=%llu l24_negative_control=%s status=%s\n",
|
||||
static_cast<unsigned long long>(openAirAudits),
|
||||
static_cast<unsigned long long>(openAirSamples),
|
||||
static_cast<unsigned long long>(openAirViolations),
|
||||
static_cast<unsigned long long>(openAirColumnSamples),
|
||||
static_cast<unsigned long long>(openAirShortSpanSamples),
|
||||
l24OpenAirControlDetected?"DETECTED":"MISSED",
|
||||
openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
||||
?"PASS":"FAIL");
|
||||
const bool l26NoLongBridgeControlDetected=l26NegativeControlApplicable
|
||||
&&l26LegacyBridgeSpanMetres>12.5;
|
||||
const bool l26NoLongBridgeControlPass=!l26NegativeControlApplicable
|
||||
||l26NoLongBridgeControlDetected;
|
||||
noLongBridgeAuditPassed=longestUncolumnedSpanMetres<=12.5
|
||||
&&l26NoLongBridgeControlPass;
|
||||
std::printf("[PassageNoLongBridgeNegativeControl] source=L26_SurfaceWorld_bridge applicable=%s longest_uncolumned_m=%.3f threshold_m=12.500 detected=%s\n",
|
||||
l26NegativeControlApplicable?"true":"false",l26LegacyBridgeSpanMetres,
|
||||
l26NoLongBridgeControlDetected?"true":"false");
|
||||
std::printf("[PassageNoLongBridgeAudit] columns=%llu short_grounded_spans=%llu longest_uncolumned_m=%.3f threshold_m=12.500 longest_short_span_m=%.3f l26_negative_control=%s status=%s\n",
|
||||
static_cast<unsigned long long>(columnCrossings),
|
||||
static_cast<unsigned long long>(shortGroundedSpans),longestUncolumnedSpanMetres,
|
||||
longestShortSpanMetres,l26NegativeControlApplicable
|
||||
?(l26NoLongBridgeControlDetected?"DETECTED":"MISSED"):"NOT_APPLICABLE",
|
||||
noLongBridgeAuditPassed?"PASS":"FAIL");
|
||||
std::printf("[PassageRockColumnAuditSummary] columns=%llu connected_ends=%llu/%llu short_spans=%llu wall_or_support_failures=%llu status=%s\n",
|
||||
static_cast<unsigned long long>(columnCrossings),
|
||||
static_cast<unsigned long long>(columnConnectedEnds),
|
||||
static_cast<unsigned long long>(columnCrossings*2),
|
||||
static_cast<unsigned long long>(shortGroundedSpans),
|
||||
static_cast<unsigned long long>(columnRouteFailures+columnWallFailures),
|
||||
columnAuditsPassed?"PASS":"FAIL");
|
||||
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",
|
||||
MinimumWallReliefMetres,l23SmoothWallDetected?"true":"false");
|
||||
@@ -877,6 +1594,8 @@ namespace StandaloneStreamBenchmark
|
||||
MinimumWallReliefMetres,naturalPasses==passageCount&&l23SmoothWallDetected?"PASS":"FAIL");
|
||||
const bool auditPassed=passageCount>0&&passagePasses==passageCount
|
||||
&&naturalPasses==passageCount&&l23SmoothWallDetected
|
||||
&&openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
||||
&&columnAuditsPassed&&noLongBridgeAuditPassed
|
||||
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
||||
&&routeBindingFailures==0
|
||||
&&blockedCompiled&&blockedControls==passageCount;
|
||||
|
||||
@@ -540,7 +540,7 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
FIntVector(0,0,islandSlot->TopChunkZ));
|
||||
const FStratePassageConfig& passage=islandSlot->Definition->PassageConfig;
|
||||
recipe.seed=static_cast<std::uint32_t>(owner.Seed);
|
||||
recipe.geometryVersion=5;
|
||||
recipe.geometryVersion=8;
|
||||
recipe.ownerVoxelSizeMicrons=owner.OwnerVoxelSizeMicrons;
|
||||
recipe.coreVoxelSizeMicrons=owner.CoreVoxelSizeMicrons;
|
||||
recipe.ownerToCoreScaleNumerator=owner.OwnerVoxelSizeMicrons/static_cast<std::uint32_t>(divisor);
|
||||
@@ -573,17 +573,22 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
recipe.anchorPlacementRadiusSubvoxels=ownerVoxelsToCoreQ(passage.DistanceMax);
|
||||
const std::int64_t spiralRadius=ownerVoxelsToCoreQ(passage.SpiralRadius);
|
||||
const std::int64_t mouthBore=std::max(ownerVoxelsToCoreQ(passage.MouthRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
ownerVoxelsToCoreQ(36.0));
|
||||
const std::int64_t bore=std::max(ownerVoxelsToCoreQ(passage.MidRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
const std::int64_t wallReserve=ownerVoxelsToCoreQ(12.0);
|
||||
// Put the mouth near the island rim, with enough island and root around the
|
||||
// first coil to read as a cave entrance instead of a hole in open air.
|
||||
recipe.anchorMouthOffsetSubvoxels=spiralRadius+mouthBore+ownerVoxelsToCoreQ(4.0);
|
||||
const std::int64_t excursion=std::max(ownerVoxelsToCoreQ(passage.Wander),spiralRadius);
|
||||
recipe.anchorRootRadiusSubvoxels=excursion+bore+wallReserve;
|
||||
ownerVoxelsToCoreQ(24.0));
|
||||
const std::int64_t wallReserve=ownerVoxelsToCoreQ(20.0);
|
||||
// Retain a 4.5-metre mouth for capsule clearance while keeping the long
|
||||
// ramp at three metres to leave more rock around the first coil.
|
||||
recipe.anchorMouthOffsetSubvoxels=ownerVoxelsToCoreQ(40.0);
|
||||
const std::int64_t supportOverhang=ownerVoxelsToCoreQ(16.0);
|
||||
const std::int64_t rootTaperReserve=ownerVoxelsToCoreQ(10.0);
|
||||
const std::int64_t mouthEnvelope=recipe.anchorMouthOffsetSubvoxels+mouthBore;
|
||||
const std::int64_t treadEnvelope=spiralRadius+bore+supportOverhang;
|
||||
recipe.anchorRootRadiusSubvoxels=std::max(mouthEnvelope,treadEnvelope)
|
||||
+wallReserve+rootTaperReserve;
|
||||
recipe.anchorIslandMinimumRadiusSubvoxels=std::max(recipe.minimumRadiusSubvoxels,
|
||||
recipe.anchorMouthOffsetSubvoxels+mouthBore+ownerVoxelsToCoreQ(4.0));
|
||||
std::max(recipe.anchorMouthOffsetSubvoxels+mouthBore+ownerVoxelsToCoreQ(4.0),
|
||||
recipe.anchorRootRadiusSubvoxels+wallReserve));
|
||||
recipe.anchorRootBottomZSubvoxels=recipe.layoutMinimumZSubvoxels
|
||||
+recipe.boundarySealSubvoxels;
|
||||
|
||||
@@ -713,6 +718,39 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
[&](const auto& p){return p.strateIndex==slot.StrateIndex;});
|
||||
if(it!=assembly.floatingIslands.end())
|
||||
{
|
||||
std::size_t passageMouthRouteIndex=it->manifest.plan.route.empty()?0
|
||||
:it->manifest.plan.route.size()-1;
|
||||
std::size_t resolvedRoutePointCount=it->manifest.plan.route.size();
|
||||
if(it->manifest.recipe.geometryVersion>=5
|
||||
&&!it->manifest.plan.route.empty()
|
||||
&&it->manifest.recipe.anchorIslandOrdinal<it->manifest.plan.islands.size())
|
||||
{
|
||||
const auto& rootedIsland=it->manifest.plan.islands[
|
||||
it->manifest.recipe.anchorIslandOrdinal];
|
||||
const double qPerMetre=static_cast<double>(
|
||||
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)*1000000.0
|
||||
/owner.CoreVoxelSizeMicrons;
|
||||
const double thresholdQ=static_cast<double>(
|
||||
it->manifest.recipe.anchorMouthOffsetSubvoxels)+qPerMetre;
|
||||
for(std::size_t reverse=it->manifest.plan.route.size();reverse>1;--reverse)
|
||||
{
|
||||
const std::size_t index=reverse-1;
|
||||
const auto& point=it->manifest.plan.route[index].positionSubvoxels;
|
||||
const double dx=static_cast<double>(point[0]-rootedIsland.centerSubvoxels[0]);
|
||||
const double dy=static_cast<double>(point[1]-rootedIsland.centerSubvoxels[1]);
|
||||
if(std::hypot(dx,dy)>thresholdQ)
|
||||
{
|
||||
passageMouthRouteIndex=std::min(index+1,
|
||||
it->manifest.plan.route.size()-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Stop the island surface route immediately before it enters
|
||||
// the outer coil. The helical passage owns the walkable path
|
||||
// from its mouth inward.
|
||||
resolvedRoutePointCount=passageMouthRouteIndex>1
|
||||
?passageMouthRouteIndex-1:passageMouthRouteIndex;
|
||||
}
|
||||
const auto recipeBottom=fixedToMicrons(it->manifest.recipe.layoutMinimumZSubvoxels);
|
||||
const auto recipeTop=fixedToMicrons(it->manifest.recipe.layoutMaximumZSubvoxels);
|
||||
if(recipeBottom!=bottom||recipeTop!=top)
|
||||
@@ -737,10 +775,17 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
it->manifest.recipe.anchorPlacementRadiusSubvoxels,
|
||||
it->manifest.recipe.anchorRootBottomZSubvoxels,
|
||||
a.radiusSubvoxels};
|
||||
if(!it->manifest.plan.route.empty())
|
||||
{
|
||||
const auto& routeMouth=it->manifest.plan.route[
|
||||
passageMouthRouteIndex].positionSubvoxels;
|
||||
passageAnchor.values.insert(passageAnchor.values.end(),
|
||||
{routeMouth[0],routeMouth[1],routeMouth[2]});
|
||||
}
|
||||
s.anchors.push_back(std::move(passageAnchor));
|
||||
}
|
||||
}
|
||||
for(std::size_t n=0;n<it->manifest.plan.route.size();++n)
|
||||
for(std::size_t n=0;n<resolvedRoutePointCount;++n)
|
||||
{
|
||||
const auto& p=it->manifest.plan.route[n];ResolvedLayoutAnchorV2 anchor;
|
||||
anchor.kind="route_point";anchor.stableId="route/"+std::to_string(n);
|
||||
@@ -788,6 +833,32 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
{const auto& p=plan->route[n];ResolvedLayoutAnchorV2 a;a.kind="route_point";a.stableId="route/"+std::to_string(n);
|
||||
a.units="1/256 Core voxel";a.values={p.positionSubvoxels[0],p.positionSubvoxels[1],p.positionSubvoxels[2],p.radiusSubvoxels};
|
||||
s.anchors.push_back(std::move(a));}
|
||||
const auto upperAnchorStratum=std::find_if(out.strates.begin(),out.strates.end(),
|
||||
[](const auto& candidate)
|
||||
{return candidate.generatorKind=="FloatingIslands"
|
||||
&&std::any_of(candidate.anchors.begin(),candidate.anchors.end(),
|
||||
[](const auto& anchor){return anchor.kind=="passage_anchor"
|
||||
&&anchor.values.size()>=7;});});
|
||||
if(upperAnchorStratum!=out.strates.end())
|
||||
{
|
||||
const auto rootAnchor=std::find_if(upperAnchorStratum->anchors.begin(),
|
||||
upperAnchorStratum->anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="passage_anchor"&&anchor.values.size()>=7;});
|
||||
const std::int64_t arrivalZMicrometres=top-seal-1250000;
|
||||
if(rootAnchor!=upperAnchorStratum->anchors.end()
|
||||
&&arrivalZMicrometres>=bottom+seal&&arrivalZMicrometres<top-seal)
|
||||
{
|
||||
ResolvedLayoutAnchorV2 arrival;arrival.kind="passage_arrival";
|
||||
arrival.stableId="passage-arrival/from-strate/"
|
||||
+std::to_string(upperAnchorStratum->strateIndex);
|
||||
arrival.units="1/256 Core voxel";
|
||||
arrival.values={rootAnchor->values[0],rootAnchor->values[1],
|
||||
static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(arrivalZMicrometres)
|
||||
*WorldAssemblySubvoxelsPerVoxel/owner.CoreVoxelSizeMicrons))};
|
||||
s.anchors.push_back(std::move(arrival));
|
||||
}
|
||||
}
|
||||
}
|
||||
out.strates.push_back(std::move(s));
|
||||
}
|
||||
@@ -803,9 +874,9 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
config.connections=authored.Connections;
|
||||
config.style=static_cast<std::uint32_t>(authored.Style);
|
||||
config.mouthRadiusSubvoxels=std::max(ownerVoxelsToCoreQ(authored.MouthRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
ownerVoxelsToCoreQ(36.0));
|
||||
config.midRadiusSubvoxels=std::max(ownerVoxelsToCoreQ(authored.MidRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
ownerVoxelsToCoreQ(24.0));
|
||||
config.reachMinimumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMin);
|
||||
config.reachMaximumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMax);
|
||||
config.distanceMinimumSubvoxels=ownerVoxelsToCoreQ(authored.DistanceMin);
|
||||
|
||||