wip(assembler): round L23 -- inter-strate passages planned from the resolved layout and carved

After L22 the FI -> SW passage no longer connected the strates (legacy owner-voxel coordinates,
lower end 23 m above SW), and Core never carved passages through the bands. Now the director
plans passages from the resolved layout (PlanResolvedLayoutPassagesV1; deterministic hash of
seed/strates/connection; placement disk from PassageConfig, 8-24 m around (0,0)); shared
composition applies generator field -> bands -> bounded passage carve and support shelf, opening
the bands only along the tunnel. Mouths bind to the strate routes. Capsule audit: 0 blocked /
0 unsupported (325,962 samples FI, 284,319 TunnelNetwork), negative controls detected; all 8
generator kinds exercised. SW away from the passage is bit-identical. Layout V3, evaluator 9.
Open: look not yet natural (stepped tube in FI void, terraced SW arrival); SW-as-upper route
planning fails; Unreal adapter uncompiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-20 18:22:12 +02:00
1 parent 55c26a05a7
commit 71d09a8c52
14 files changed
+1465 -112

No files matched your search

+27
View File
@@ -1924,6 +1924,33 @@ controls continue to reject malformed bands.
Evidence and owner caveats are in
[`Standalone/Research/WorldAssemblerM3/Linux22/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux22/REPORT.md).
### 8.41 Director-resolved inter-strate passages and walkable landings — World Assembler L23
Core passage geometry is derived after the director has resolved physical strate bands and native
route anchors. `PlanResolvedLayoutPassagesV1` joins every adjacent resolved pair. Its placement hash
uses `(seed, upper strate, lower strate, connection, salt)`; `PassageConfig.DistanceMax` is the
outer radius around world `(0,0)` (24 m for the owner's first interface), while `DistanceMin` is
the inner radius used when a mouth has no route anchor. A route-bound mouth reuses the upper
strate's departure or lower strate's arrival anchor, after checking it lies within `DistanceMax`.
The legacy `UVoxelStrateManager::GeneratePassages` and its owner-voxel control points remain the
legacy generator path; Core does not import the exported legacy `passages` array. The season
manifest carries recipes and plans, and the adapter rebuilds the resolved layout and its passages
from the current manager slots and route anchors each time it attaches the Core world.
`ApplyResolvedStrateComposition` composes the current generator field, applies the resolved ceiling
and bedrock bands, then unions the passage carve and floor support. Only the hashed capsule path
opens a band; the support strip stays below the player capsule. The lower mouth index splits the
monotone descent from the short route-bound landing tail, whose height may vary with the route.
Both route and passage audits sample the 0.34 m by 1.76 m capsule, support probes, and grade; the
negative control compiles the same world without passage carving and must detect a blocked capsule.
The L23 shared-field change is `WorldAssemblyEvaluatorVersion=9`. It does not change the L22 bands
or any generator's field away from a passage. The L23 report includes exact-query/hash comparisons,
the blocked control, captures, and the Windows adapter checklist. §8.10's cache, proof, streaming,
and async rules remain unchanged.
[`Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md)
records the audit and captures.
## 9. Multiplayer model (listen-server first, dedicated-friendly)
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
+12 -1
View File
@@ -167,7 +167,7 @@ remains `0`, and lateral regions remain gated off.
| `../../Standalone/Core/src/CoreNamespacePreamble.h` | First include of every Core TU. Forces FP contraction off per compiler (Clang/MSVC/GCC pragmas; unknown compiler = `#error`) and defines `vfcore::fp_contract::selfTest`, which `World::compile` runs before building: a fused multiply-add (e.g. Clang `-ffp-contract=fast`) refuses world generation. The world depends on contraction (Linux2/Linux3 reports), so never remove either. |
| `../../VoxelForgeCore/Private/ModuleEntry/` | Unreal module entry and an isolated `CoreMinimal.h` shim for the actual engine header. |
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Standard-library-only public Core API. `VOXELFORGECORE_API` exports the API from the Unreal module and is empty for standalone builds. |
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV2`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, transition and dispatch provenance, recipe anchors, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40. |
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV3`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, transition and dispatch provenance, recipe anchors, director-planned passages, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40–8.41. |
| `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. |
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
| Step-3 scale and game validation | `step3-scale` measures the same 519³ and 259³ six-worker lattices at 12.5 cm. The cached medians are 7.347496 s and 1.169567 s respectively; determinism and brick sound-proof audits pass. `step3-game` routes in-game generation through the brick evaluator and records tile timings, spawn fill, moving frames, and profile-switch rebuilds. The visible D3D12 run measured tile generation p50/p95/max 3.326/31.045/169.867 ms, full-detail L0 fill 12.369 s for 2,765 tiles, and moving-frame p50/p95/p99/max 4.856/8.922/13.090/80.575 ms. Switching profiles rebuilt 5,815 loaded tiles. Both high-resolution screenshot requests were logged, but the harness found only one fresh PNG and it is a blank pale viewport; no lit comparison capture is accepted. Raw output: `Saved/TestHarness/step3_game_visible_final`; scale output: `Saved/Research/step3/scale/step3_scale_cacheperf_v1`. |
@@ -1155,6 +1155,17 @@ Geometry version 3 removes the FI lower all-XY seal while retaining its ceiling
upper-bound path. Versions 1 and 2 retain their serialized two-sided behavior. See ARCHITECTURE §8.37
and [`Standalone/Research/WorldAssemblerM3/Linux18/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux18/REPORT.md).
### 3.24 Resolved inter-strate passages — World Assembler M3 L23
| Symbol | Canonical file | Role |
|---|---|---|
| `PlanResolvedLayoutPassagesV1`, `ResolvedLayoutPassageV1::lowerMouthControlIndex` | `Standalone/Core/src/ResolvedLayoutInternal.h`, public record in `VoxelForgeCore.h` | Plans deterministic fixed-metre passages from adjacent director-resolved strata, binds route departure/arrival mouths, and records the derived split between the monotone descent and route-bound landing tail. |
| `FStandaloneShapeWorld::ApplyResolvedStrateComposition`, `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Applies boundary bands first, then opens the passage capsule and supported floor along the passage for every native generator. |
| `--audit-resolved-layout-passages` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples passage capsules, floor support, mouth ownership/radius, in-strate route bindings, and a blocked-passage negative control. |
The evaluator version is 9. See ARCHITECTURE §8.41 and
[`Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md).
## 4. The density pipeline (most-edited hot path)
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
@@ -991,7 +991,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
// Keep the inspectable physical-layout export separate from the manifest identity: it is
// assembled from the same resolved slots/plans, but adding it must not salt evaluation hashes.
VoxelForgeCore::ResolvedLayoutSnapshotV2 Layout;
VoxelForgeCore::ResolvedLayoutSnapshotV3 Layout;
Layout.worldIdentity = std::string("owner-world/seed/")
+ std::to_string(StrateManager->GetWorldSeed());
Layout.ownerVoxelSizeMicrometres = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
@@ -1029,9 +1029,27 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
return static_cast<std::int64_t>(std::llround(static_cast<double>(Fixed)
* ExpectedCoreVoxelMicrons / VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
};
std::vector<VoxelForgeCore::ResolvedLayoutPassageConfigV1> PassageConfigs;
for (const FStrateSlot& Slot : StrateManager->GetLayout())
{
if (Slot.Definition == nullptr) continue;
const FStratePassageConfig& AuthoredPassage = Slot.Definition->PassageConfig;
const auto OwnerVoxelsToCoreQ = [OwnerVoxelMicrons, ExpectedCoreVoxelMicrons](double Value)
{
return static_cast<std::int64_t>(std::llround(Value * OwnerVoxelMicrons
* VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel / ExpectedCoreVoxelMicrons));
};
VoxelForgeCore::ResolvedLayoutPassageConfigV1 PassageConfig;
PassageConfig.upperStrateIndex = Slot.StrateIndex;
PassageConfig.connections = AuthoredPassage.Connections;
PassageConfig.mouthRadiusSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.MouthRadius);
PassageConfig.midRadiusSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.MidRadius);
PassageConfig.reachMinimumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.ReachMin);
PassageConfig.reachMaximumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.ReachMax);
PassageConfig.distanceMinimumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.DistanceMin);
PassageConfig.distanceMaximumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.DistanceMax);
PassageConfig.wanderSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.Wander);
PassageConfigs.push_back(PassageConfig);
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ));
VoxelForgeCore::ResolvedLayoutStratumV2 Item;
@@ -1065,8 +1083,8 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Item.managerLookup = "UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate";
if (Type == ECaveGeneratorType::SurfaceWorld)
{
Item.boundDispatchPath = "BuildSurfaceStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV2";
Item.queryDispatchPath = "NativeSurfaceDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV2";
Item.boundDispatchPath = "BuildSurfaceStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV3";
Item.queryDispatchPath = "NativeSurfaceDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV3";
const auto Plan = std::find_if(Manifest->plan.strates.begin(), Manifest->plan.strates.end(),
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
if (Plan != Manifest->plan.strates.end())
@@ -1097,8 +1115,8 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
}
else if (Type == ECaveGeneratorType::FloatingIslands)
{
Item.boundDispatchPath = "BuildFloatingIslandStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV2";
Item.queryDispatchPath = "NativeFloatingIslandDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV2";
Item.boundDispatchPath = "BuildFloatingIslandStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV3";
Item.queryDispatchPath = "NativeFloatingIslandDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV3";
const auto Plan = std::find_if(Manifest->floatingIslands.begin(), Manifest->floatingIslands.end(),
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
std::int64_t SealMicrometres = 0;
@@ -1117,11 +1135,21 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Island.centerSubvoxels[2], Island.radiusSubvoxels, Island.topHalfSubvoxels };
Item.anchors.push_back(std::move(Anchor));
}
for (std::size_t RouteIndex = 0; RouteIndex < Plan->manifest.plan.route.size(); ++RouteIndex)
{
const auto& Point = Plan->manifest.plan.route[RouteIndex];
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
Anchor.kind = "route_point"; Anchor.stableId = "route/" + std::to_string(RouteIndex);
Anchor.units = "1/256 Core voxel";
Anchor.values = { Point.positionSubvoxels[0], Point.positionSubvoxels[1],
Point.positionSubvoxels[2], Point.radiusSubvoxels };
Item.anchors.push_back(std::move(Anchor));
}
}
}
else
{
Item.boundDispatchPath = "resolved manager slot scaled by ResolvedLayoutSnapshotV2 chunk width";
Item.boundDispatchPath = "resolved manager slot scaled by ResolvedLayoutSnapshotV3 chunk width";
Item.queryDispatchPath = "legacy Core route selects manager chunk by floor(CoreZ / resolved layout chunk width)";
}
const std::int64_t SealMicrometres=static_cast<std::int64_t>(std::llround(
@@ -1142,23 +1170,16 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
"none", "none" };
Item.content.bottomMicrometres+=SealMicrometres;
Item.content.topMicrometres-=SealMicrometres;
for (std::size_t PassageIndex = 0; PassageIndex < StrateManager->GetPassages().Num(); ++PassageIndex)
{
const FVoxelPassage& Passage = StrateManager->GetPassages()[static_cast<int32>(PassageIndex)];
if (Passage.UpperStrateIndex != Slot.StrateIndex && Passage.LowerStrateIndex != Slot.StrateIndex) continue;
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
Anchor.kind = "passage"; Anchor.stableId = "passage/" + std::to_string(PassageIndex);
Anchor.units = "milli owner voxels";
Anchor.values = { static_cast<std::int64_t>(std::llround(Passage.UpperPoint.X * 1000.0)),
static_cast<std::int64_t>(std::llround(Passage.UpperPoint.Y * 1000.0)),
static_cast<std::int64_t>(std::llround(Passage.UpperPoint.Z * 1000.0)),
static_cast<std::int64_t>(std::llround(Passage.LowerPoint.X * 1000.0)),
static_cast<std::int64_t>(std::llround(Passage.LowerPoint.Y * 1000.0)),
static_cast<std::int64_t>(std::llround(Passage.LowerPoint.Z * 1000.0)) };
Item.anchors.push_back(std::move(Anchor));
}
Layout.strates.push_back(std::move(Item));
}
std::string PassagePlanError;
if (!VoxelForgeCore::PlanResolvedLayoutPassagesV1(Layout,
StrateManager->GetWorldSeed(), PassageConfigs, PassagePlanError))
{
OutError = FString::Printf(TEXT("Resolved inter-strate passage planning failed: %s"),
UTF8_TO_TCHAR(PassagePlanError.c_str()));
return false;
}
std::string LayoutError;
if (!Layout.finalize(LayoutError))
{
@@ -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 7 binds plans to the physical strate-layout chunk width and shared boundary bands.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 7;
// Version 9 indexes passage landings so non-monotone route tails stay carveable.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 9;
enum class FieldChannel : std::uint32_t
{
@@ -573,8 +573,8 @@ namespace VoxelForgeCore
// Read-only, versioned account of the physical layout resolved by the director and the
// Core adapter. Coordinates are half-open micrometre intervals; recipe anchors retain their
// source fixed-point units so this record never rounds the recipe itself.
inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV2 = 2;
enum class ResolvedLayoutValidationModeV2 : std::uint8_t { compatibility, strict };
inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV3 = 3;
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
struct ResolvedLayoutAnchorV2
{
@@ -635,34 +635,73 @@ namespace VoxelForgeCore
std::string detail;
};
struct VFCORE_API ResolvedLayoutSnapshotV2
struct ResolvedLayoutPassagePointV1
{
std::uint32_t schemaVersion = ResolvedLayoutSchemaVersionV2;
std::array<std::int64_t, 3> positionSubvoxels{};
std::int64_t radiusSubvoxels = 0;
};
struct ResolvedLayoutPassageV1
{
std::string stableId;
std::int32_t upperStrateIndex = -1;
std::int32_t lowerStrateIndex = -1;
std::int64_t placementRadiusSubvoxels = 0;
std::array<std::int64_t, 3> upperMouthSubvoxels{};
std::array<std::int64_t, 3> lowerMouthSubvoxels{};
std::int64_t mouthRadiusSubvoxels = 0;
std::int64_t midRadiusSubvoxels = 0;
// Derived from the mouth binding; the main descent is monotone through this point.
std::size_t lowerMouthControlIndex = 0;
std::vector<ResolvedLayoutPassagePointV1> controlPoints;
};
struct ResolvedLayoutPassageConfigV1
{
std::int32_t upperStrateIndex = -1;
std::int32_t connections = 1;
std::int64_t mouthRadiusSubvoxels = 0;
std::int64_t midRadiusSubvoxels = 0;
std::int64_t reachMinimumSubvoxels = 0;
std::int64_t reachMaximumSubvoxels = 0;
std::int64_t distanceMinimumSubvoxels = 0;
std::int64_t distanceMaximumSubvoxels = 0;
std::int64_t wanderSubvoxels = 0;
};
struct VFCORE_API ResolvedLayoutSnapshotV3
{
std::uint32_t schemaVersion = ResolvedLayoutSchemaVersionV3;
std::string worldIdentity;
std::uint32_t ownerVoxelSizeMicrometres = 0;
std::uint32_t coreVoxelSizeMicrometres = 0;
std::uint32_t strateLayoutChunkSizeMicrometres = 8000000;
std::int64_t strateLayoutChunkWidthSubvoxels = 8192;
std::vector<ResolvedLayoutStratumV2> strates;
std::vector<ResolvedLayoutPassageV1> passages;
std::string stableIdentity;
bool finalize(std::string& error);
bool validate(ResolvedLayoutValidationModeV2 mode,
bool validate(ResolvedLayoutValidationModeV3 mode,
std::vector<ResolvedLayoutIssueV2>& issues,
std::string& error) const;
std::string ToJson() const;
static std::optional<ResolvedLayoutSnapshotV2> FromJson(
static std::optional<ResolvedLayoutSnapshotV3> FromJson(
std::string_view json, std::string& error);
};
struct ResolvedLayoutValidatorAuditV2
struct ResolvedLayoutValidatorAuditV3
{
std::uint32_t controlsRun = 0;
std::uint32_t controlsRejectedAsExpected = 0;
bool passed = false;
std::vector<ResolvedLayoutIssueV2> failures;
};
VFCORE_API ResolvedLayoutValidatorAuditV2 AuditResolvedLayoutValidatorV2();
VFCORE_API ResolvedLayoutValidatorAuditV3 AuditResolvedLayoutValidatorV3();
VFCORE_API bool PlanResolvedLayoutPassagesV1(
ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
const std::vector<ResolvedLayoutPassageConfigV1>& configs,
std::string& error);
struct FloatingIslandBoundsAuditResult
{
@@ -941,7 +980,8 @@ namespace VoxelForgeCore
explicit operator bool() const noexcept { return static_cast<bool>(data_); }
const WorldConfiguration& configuration() const;
const WorldAssemblyManifest* worldAssembly() const noexcept;
const ResolvedLayoutSnapshotV2* resolvedLayout() const noexcept;
const ResolvedLayoutSnapshotV3* resolvedLayout() const noexcept;
ResolvedWorldSnapshot withoutResolvedLayoutPassages() const;
private:
struct Data;
+526 -18
View File
@@ -4,7 +4,9 @@
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <limits>
#include <sstream>
namespace VoxelForgeCore
@@ -66,7 +68,7 @@ namespace
b.behaviour=value.Get("behaviour").String();
b.densitySide=value.Get("density_side").String();b.capPath=value.Get("cap_path").String();return b;
}
std::string LayoutBodyJson(const ResolvedLayoutSnapshotV2& s)
std::string LayoutBodyJson(const ResolvedLayoutSnapshotV3& s)
{
std::ostringstream out;out<<"{\"schema_version\":"<<s.schemaVersion
<<",\"world_identity\":"<<LayoutEscape(s.worldIdentity)
@@ -113,6 +115,29 @@ namespace
}
out<<"]}";
}
out<<"],\"passages\":[";
for(std::size_t i=0;i<s.passages.size();++i)
{
if(i)out<<',';const auto& p=s.passages[i];
out<<"{\"stable_id\":"<<LayoutEscape(p.stableId)
<<",\"upper_strate_index\":"<<p.upperStrateIndex
<<",\"lower_strate_index\":"<<p.lowerStrateIndex
<<",\"placement_radius_q\":"<<p.placementRadiusSubvoxels
<<",\"upper_mouth_q\":["<<p.upperMouthSubvoxels[0]<<','
<<p.upperMouthSubvoxels[1]<<','<<p.upperMouthSubvoxels[2]
<<"],\"lower_mouth_q\":["<<p.lowerMouthSubvoxels[0]<<','
<<p.lowerMouthSubvoxels[1]<<','<<p.lowerMouthSubvoxels[2]
<<"],\"mouth_radius_q\":"<<p.mouthRadiusSubvoxels
<<",\"mid_radius_q\":"<<p.midRadiusSubvoxels<<",\"control_points\":[";
for(std::size_t j=0;j<p.controlPoints.size();++j)
{
if(j)out<<',';const auto& point=p.controlPoints[j];
out<<"{\"position_q\":["<<point.positionSubvoxels[0]<<','
<<point.positionSubvoxels[1]<<','<<point.positionSubvoxels[2]
<<"],\"radius_q\":"<<point.radiusSubvoxels<<'}';
}
out<<"]}";
}
out<<"]}";return out.str();
}
std::int64_t LayoutInt(const VFJson::Value& obj,std::string_view key)
@@ -121,19 +146,76 @@ namespace
{return obj.Get(key).String();}
bool LayoutBool(const VFJson::Value& obj,std::string_view key,bool fallback=false)
{return obj.Get(key).Boolean(fallback);}
std::uint64_t PassageHash(std::uint64_t value)
{
value+=0x9e3779b97f4a7c15ull;
value=(value^(value>>30))*0xbf58476d1ce4e5b9ull;
value=(value^(value>>27))*0x94d049bb133111ebull;
return value^(value>>31);
}
std::uint64_t PassageSeed(std::int32_t seed,std::int32_t upper,
std::int32_t lower,std::uint32_t connection,
std::uint32_t salt)
{
std::uint64_t h=PassageHash(static_cast<std::uint32_t>(seed));
h=PassageHash(h^static_cast<std::uint32_t>(upper));
h=PassageHash(h^static_cast<std::uint32_t>(lower));
h=PassageHash(h^connection);return PassageHash(h^salt);
}
std::uint64_t PassageIsqrt(std::uint64_t n)
{
std::uint64_t x=0,bit=std::uint64_t{1}<<62;
while(bit>n)bit>>=2;
while(bit)
{
if(n>=x+bit){n-=x+bit;x=(x>>1)+bit;}
else x>>=1;
bit>>=2;
}
return x;
}
std::int64_t PassageRoundRatio(std::int64_t value,std::int64_t numerator,
std::int64_t denominator)
{
if(denominator<=0)return 0;
const bool negative=value<0;
const std::uint64_t magnitude=static_cast<std::uint64_t>(negative?-value:value);
const std::uint64_t product=magnitude*static_cast<std::uint64_t>(numerator);
const std::uint64_t rounded=(product+static_cast<std::uint64_t>(denominator)/2)
/static_cast<std::uint64_t>(denominator);
return negative?-static_cast<std::int64_t>(rounded):static_cast<std::int64_t>(rounded);
}
std::array<std::int64_t,2> PassageRandomXY(std::uint64_t seed,
std::int64_t inner,std::int64_t outer)
{
if(outer<=0)return {};
inner=std::clamp(inner,std::int64_t{0},outer);
const std::uint64_t span=static_cast<std::uint64_t>(outer)*2+1;
for(std::uint32_t attempt=0;attempt<64;++attempt)
{
const std::uint64_t hx=PassageHash(seed+2ull*attempt);
const std::uint64_t hy=PassageHash(seed+2ull*attempt+1);
const std::int64_t x=static_cast<std::int64_t>(hx%span)-outer;
const std::int64_t y=static_cast<std::int64_t>(hy%span)-outer;
const std::uint64_t radiusSquared=static_cast<std::uint64_t>(x*x+y*y);
if(radiusSquared<=static_cast<std::uint64_t>(outer*outer)
&&radiusSquared>=static_cast<std::uint64_t>(inner*inner))return {x,y};
}
return {outer,0};
}
}
std::string ResolvedLayoutSnapshotV2::ToJson() const
std::string ResolvedLayoutSnapshotV3::ToJson() const
{
const std::string body=LayoutBodyJson(*this);
return body.substr(0,body.size()-1)+",\"stable_identity\":"
+LayoutEscape(stableIdentity.empty()?"resolved-layout-v2-fnv64:"+LayoutHex(LayoutHash(body)):stableIdentity)+"}";
+LayoutEscape(stableIdentity.empty()?"resolved-layout-v3-fnv64:"+LayoutHex(LayoutHash(body)):stableIdentity)+"}";
}
bool ResolvedLayoutSnapshotV2::finalize(std::string& error)
bool ResolvedLayoutSnapshotV3::finalize(std::string& error)
{
error.clear();
if(schemaVersion!=ResolvedLayoutSchemaVersionV2||worldIdentity.empty()||strates.empty()
if(schemaVersion!=ResolvedLayoutSchemaVersionV3||worldIdentity.empty()||strates.empty()
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
{error="resolved layout has an unsupported version, empty world identity, or no strates";return false;}
std::stable_sort(strates.begin(),strates.end(),[](const auto& a,const auto& b)
@@ -149,22 +231,37 @@ bool ResolvedLayoutSnapshotV2::finalize(std::string& error)
if(x.interiorTopMicrometres<=x.interiorBottomMicrometres||x.voxelSizeMicrometres==0)
{error="resolved layout contains an invalid interior interval or voxel size";return false;}
}
const std::int64_t playerCenterAboveFloorQ=static_cast<std::int64_t>(std::llround(
1.13*WorldAssemblySubvoxelsPerVoxel*1000000.0/coreVoxelSizeMicrometres));
for(auto& passage:passages)
{
passage.lowerMouthControlIndex=passage.controlPoints.empty()?0:passage.controlPoints.size()-1;
for(std::size_t i=1;i<passage.controlPoints.size();++i)
{
const auto& point=passage.controlPoints[i];
if(point.positionSubvoxels[0]==passage.lowerMouthSubvoxels[0]
&&point.positionSubvoxels[1]==passage.lowerMouthSubvoxels[1]
&&point.positionSubvoxels[2]-point.radiusSubvoxels+playerCenterAboveFloorQ
==passage.lowerMouthSubvoxels[2])
passage.lowerMouthControlIndex=i;
}
}
stableIdentity.clear();const std::string body=LayoutBodyJson(*this);
stableIdentity="resolved-layout-v2-fnv64:"+LayoutHex(LayoutHash(body));
stableIdentity="resolved-layout-v3-fnv64:"+LayoutHex(LayoutHash(body));
std::vector<ResolvedLayoutIssueV2> issues;
return validate(ResolvedLayoutValidationModeV2::compatibility,issues,error);
return validate(ResolvedLayoutValidationModeV3::compatibility,issues,error);
}
bool ResolvedLayoutSnapshotV2::validate(ResolvedLayoutValidationModeV2 mode,
bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
std::vector<ResolvedLayoutIssueV2>& issues,std::string& error) const
{
issues.clear();error.clear();
if(schemaVersion!=ResolvedLayoutSchemaVersionV2||worldIdentity.empty()||strates.empty()
if(schemaVersion!=ResolvedLayoutSchemaVersionV3||worldIdentity.empty()||strates.empty()
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
{error="resolved layout schema/world/strata header is invalid";return false;}
const auto add=[&](std::string code,std::string detail)
{issues.push_back({std::move(code),std::move(detail)});};
const bool strict=mode==ResolvedLayoutValidationModeV2::strict;
const bool strict=mode==ResolvedLayoutValidationModeV3::strict;
for(std::size_t i=0;i<strates.size();++i)
{
const auto& s=strates[i];
@@ -262,16 +359,110 @@ bool ResolvedLayoutSnapshotV2::validate(ResolvedLayoutValidationModeV2 mode,
}
}
}
const auto findStratum=[this](std::int32_t index)->const ResolvedLayoutStratumV2*
{
const auto it=std::find_if(strates.begin(),strates.end(),[index](const auto& s)
{return s.strateIndex==index;});
return it==strates.end()?nullptr:&*it;
};
std::vector<std::string> passageIds;
for(std::size_t i=0;strict&&i+1<strates.size();++i)
{
const auto& upper=strates[i];const auto& lower=strates[i+1];
if(std::none_of(passages.begin(),passages.end(),[&](const auto& p)
{return p.upperStrateIndex==upper.strateIndex&&p.lowerStrateIndex==lower.strateIndex;}))
add("missing_passage_interface",upper.stableId+" has no passage to "+lower.stableId);
}
for(const auto& passage:passages)
{
const auto* upper=findStratum(passage.upperStrateIndex);
const auto* lower=findStratum(passage.lowerStrateIndex);
if(passage.stableId.empty()||std::find(passageIds.begin(),passageIds.end(),passage.stableId)!=passageIds.end())
add("passage_identity", "passage has an empty or duplicate stable identity");
else passageIds.push_back(passage.stableId);
if(!upper||!lower||lower->order!=upper->order+1)
add("passage_interface",passage.stableId+" does not join adjacent resolved strates");
if(!upper||!lower||passage.placementRadiusSubvoxels<=0
||passage.mouthRadiusSubvoxels<=0||passage.midRadiusSubvoxels<=0
||passage.controlPoints.size()<2)
{
add("passage_geometry",passage.stableId+" has invalid radii, placement bound, or control points");
continue;
}
const auto insidePlacement=[&](const std::array<std::int64_t,3>& p)
{
const auto x=p[0],y=p[1],r=passage.placementRadiusSubvoxels;
return std::abs(x)<=r&&std::abs(y)<=r&&x*x+y*y<=r*r;
};
const auto insideContent=[&](const ResolvedLayoutStratumV2& s,
const std::array<std::int64_t,3>& p)
{
const std::int64_t zMicrometres=p[2]*static_cast<std::int64_t>(coreVoxelSizeMicrometres)
/WorldAssemblySubvoxelsPerVoxel;
return s.content.presentInCurrentField&&zMicrometres>=s.content.bottomMicrometres
&&zMicrometres<s.content.topMicrometres;
};
if(!insidePlacement(passage.upperMouthSubvoxels)||!insidePlacement(passage.lowerMouthSubvoxels))
add("passage_placement",passage.stableId+" mouth lies outside its (0,0) placement radius");
if(!insideContent(*upper,passage.upperMouthSubvoxels)
||!insideContent(*lower,passage.lowerMouthSubvoxels))
add("passage_mouth_content",passage.stableId+" mouth is outside its owned content band");
if(passage.controlPoints.size()<2)continue;
const std::int64_t PlayerCenterAboveFloorQ=coreVoxelSizeMicrometres>0
?static_cast<std::int64_t>(std::llround(1.13*WorldAssemblySubvoxelsPerVoxel
*1000000.0/coreVoxelSizeMicrometres)):1157;
const auto floorProfile=[&](const ResolvedLayoutPassagePointV1& p)
{return p.positionSubvoxels[2]-p.radiusSubvoxels+PlayerCenterAboveFloorQ;};
std::size_t lowerMouthControlIndex=passage.controlPoints.size();
for(std::size_t i=1;i<passage.controlPoints.size();++i)
{
const auto& candidate=passage.controlPoints[i];
if(candidate.positionSubvoxels[0]==passage.lowerMouthSubvoxels[0]
&&candidate.positionSubvoxels[1]==passage.lowerMouthSubvoxels[1]
&&floorProfile(candidate)==passage.lowerMouthSubvoxels[2])
lowerMouthControlIndex=i;
}
if(lowerMouthControlIndex==passage.controlPoints.size())
{
add("passage_mouth_binding",passage.stableId+" has no control point bound to its lower mouth");
lowerMouthControlIndex=passage.controlPoints.size()-1;
}
for(std::size_t i=0;i<passage.controlPoints.size();++i)
{
const auto& point=passage.controlPoints[i];
if(point.radiusSubvoxels<=0||!insidePlacement(point.positionSubvoxels))
add("passage_control_point",passage.stableId+" has a non-positive radius or leaves its placement disk");
if(i==0||i==lowerMouthControlIndex)
{
const auto& mouth=i==0?passage.upperMouthSubvoxels:passage.lowerMouthSubvoxels;
if(point.positionSubvoxels[0]!=mouth[0]||point.positionSubvoxels[1]!=mouth[1]
||floorProfile(point)!=mouth[2])
add("passage_mouth_binding",passage.stableId+" control point does not bind to its mouth anchor");
}
if(i==0)continue;
const auto& previous=passage.controlPoints[i-1];
const std::int64_t dx=point.positionSubvoxels[0]-previous.positionSubvoxels[0];
const std::int64_t dy=point.positionSubvoxels[1]-previous.positionSubvoxels[1];
const auto horizontal=PassageIsqrt(static_cast<std::uint64_t>(dx*dx+dy*dy));
const std::int64_t signedDz=floorProfile(point)-floorProfile(previous);
const std::int64_t dz=std::abs(signedDz);
const bool landingExtension=i>lowerMouthControlIndex;
if((landingExtension?dz*65536>static_cast<std::int64_t>(horizontal)*17560
:signedDz>=0||dz*65536>static_cast<std::int64_t>(horizontal)*17560)
||horizontal==0)
add("passage_grade",passage.stableId+" is not strictly descending or exceeds the 15 degree grade limit");
}
}
if(strict&&!issues.empty()){error="strict resolved-layout validation rejected "+issues.front().code;return false;}
return true;
}
std::optional<ResolvedLayoutSnapshotV2> ResolvedLayoutSnapshotV2::FromJson(
std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
std::string_view json,std::string& error)
{
VFJson::Value root;if(!VFJson::Parser(std::string(json)).Parse(root,error))return std::nullopt;
if(!root.IsObject()){error="resolved layout JSON root must be an object";return std::nullopt;}
ResolvedLayoutSnapshotV2 s;s.schemaVersion=static_cast<std::uint32_t>(LayoutInt(root,"schema_version"));
ResolvedLayoutSnapshotV3 s;s.schemaVersion=static_cast<std::uint32_t>(LayoutInt(root,"schema_version"));
s.worldIdentity=LayoutString(root,"world_identity");
s.ownerVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"owner_voxel_size_um"));
s.coreVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"core_voxel_size_um"));
@@ -302,15 +493,314 @@ std::optional<ResolvedLayoutSnapshotV2> ResolvedLayoutSnapshotV2::FromJson(
for(const auto& value:a.Get("values").AsArray())p.values.push_back(value.Integer());x.anchors.push_back(std::move(p));}
s.strates.push_back(std::move(x));
}
for(const auto& v:root.Get("passages").AsArray())
{
ResolvedLayoutPassageV1 p;p.stableId=LayoutString(v,"stable_id");
p.upperStrateIndex=static_cast<std::int32_t>(LayoutInt(v,"upper_strate_index"));
p.lowerStrateIndex=static_cast<std::int32_t>(LayoutInt(v,"lower_strate_index"));
p.placementRadiusSubvoxels=LayoutInt(v,"placement_radius_q");
p.mouthRadiusSubvoxels=LayoutInt(v,"mouth_radius_q");p.midRadiusSubvoxels=LayoutInt(v,"mid_radius_q");
const auto& upper=v.Get("upper_mouth_q").AsArray();
const auto& lower=v.Get("lower_mouth_q").AsArray();
for(std::size_t axis=0;axis<3&&axis<upper.size();++axis)p.upperMouthSubvoxels[axis]=upper[axis].Integer();
for(std::size_t axis=0;axis<3&&axis<lower.size();++axis)p.lowerMouthSubvoxels[axis]=lower[axis].Integer();
for(const auto& q:v.Get("control_points").AsArray())
{
ResolvedLayoutPassagePointV1 point;const auto& position=q.Get("position_q").AsArray();
for(std::size_t axis=0;axis<3&&axis<position.size();++axis)
point.positionSubvoxels[axis]=position[axis].Integer();
point.radiusSubvoxels=LayoutInt(q,"radius_q");p.controlPoints.push_back(point);
}
s.passages.push_back(std::move(p));
}
const std::string serializedIdentity=LayoutString(root,"stable_identity");
if(!s.finalize(error))return std::nullopt;
if(s.stableIdentity!=serializedIdentity){error="resolved layout stable identity does not match its canonical payload";return std::nullopt;}
return s;
}
ResolvedLayoutValidatorAuditV2 AuditResolvedLayoutValidatorV2()
bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t seed,
const std::vector<ResolvedLayoutPassageConfigV1>& configs,std::string& error)
{
ResolvedLayoutValidatorAuditV2 result;
error.clear();layout.passages.clear();
if(layout.strates.size()<2)return true;
std::stable_sort(layout.strates.begin(),layout.strates.end(),[](const auto& a,const auto& b)
{return a.interiorTopMicrometres!=b.interiorTopMicrometres
?a.interiorTopMicrometres>b.interiorTopMicrometres:a.order<b.order;});
for(std::size_t i=0;i<layout.strates.size();++i)
layout.strates[i].order=static_cast<std::int32_t>(i);
if(layout.coreVoxelSizeMicrometres==0)
{error="passage planner requires the resolved Core voxel size";return false;}
const std::int64_t qPerMetre=static_cast<std::int64_t>(std::llround(
static_cast<double>(WorldAssemblySubvoxelsPerVoxel)*1000000.0
/layout.coreVoxelSizeMicrometres));
if(qPerMetre<=0){error="passage planner could not resolve fixed-point metres";return false;}
const std::int64_t playerCenterAboveFloorQ=static_cast<std::int64_t>(std::llround(1.13*qPerMetre));
const std::array<std::array<std::int32_t,2>,16> directions{{
{{65536,0}},{{60547,25080}},{{46341,46341}},{{25080,60547}},
{{0,65536}},{{-25080,60547}},{{-46341,46341}},{{-60547,25080}},
{{-65536,0}},{{-60547,-25080}},{{-46341,-46341}},{{-25080,-60547}},
{{0,-65536}},{{25080,-60547}},{{46341,-46341}},{{60547,-25080}}}};
const auto configFor=[&](std::int32_t strate)->const ResolvedLayoutPassageConfigV1*
{
const auto it=std::find_if(configs.begin(),configs.end(),[strate](const auto& c)
{return c.upperStrateIndex==strate;});
return it==configs.end()?nullptr:&*it;
};
const auto routeAnchor=[](const ResolvedLayoutStratumV2& s,bool departure,
std::array<std::int64_t,3>& out)
{
const ResolvedLayoutAnchorV2* selected=nullptr;
for(const auto& a:s.anchors)
if(a.kind=="route_point"&&a.values.size()>=3)
{selected=&a;if(!departure)break;}
if(!selected)return false;
out={selected->values[0],selected->values[1],selected->values[2]};return true;
};
const auto routeTangent=[](const ResolvedLayoutStratumV2& s,bool departure,
std::array<std::int64_t,2>& out)
{
const ResolvedLayoutAnchorV2* first=nullptr,*second=nullptr,*last=nullptr,*previous=nullptr;
for(const auto& a:s.anchors)
if(a.kind=="route_point"&&a.values.size()>=3)
{if(!first)first=&a;else if(!second)second=&a;previous=last;last=&a;}
if(!first||!second||!last||!previous)return false;
const auto* from=departure?previous:first;
const auto* to=departure?last:second;
out={to->values[0]-from->values[0],to->values[1]-from->values[1]};
return out[0]!=0||out[1]!=0;
};
const auto radialWithin=[](const std::array<std::int64_t,3>& p,std::int64_t radius)
{return radius>0&&p[0]*p[0]+p[1]*p[1]<=radius*radius;};
const auto reachValue=[](std::uint64_t hash,std::int64_t lo,std::int64_t hi)
{
if(hi<lo)std::swap(hi,lo);
return lo+(hi>lo?static_cast<std::int64_t>(hash%static_cast<std::uint64_t>(hi-lo+1)):0);
};
const auto contentZQ=[&](std::int64_t micrometres)
{return static_cast<std::int64_t>(std::llround(static_cast<double>(micrometres)
*WorldAssemblySubvoxelsPerVoxel/layout.coreVoxelSizeMicrometres));};
const auto distanceQ=[](std::int64_t dx,std::int64_t dy)
{
const auto square=static_cast<std::uint64_t>(dx*dx+dy*dy);
const auto floor=PassageIsqrt(square);return floor*floor==square?floor:floor+1;
};
for(std::size_t interface=0;interface+1<layout.strates.size();++interface)
{
const auto& upper=layout.strates[interface];const auto& lower=layout.strates[interface+1];
const auto* cfg=configFor(upper.strateIndex);
const std::int64_t outer=cfg?std::max(cfg->distanceMinimumSubvoxels,
cfg->distanceMaximumSubvoxels):24*qPerMetre;
const std::int64_t inner=cfg?std::min(cfg->distanceMinimumSubvoxels,
cfg->distanceMaximumSubvoxels):8*qPerMetre;
const std::int64_t placementRadius=std::max<std::int64_t>(1,outer);
const std::int64_t mouthRadius=std::max<std::int64_t>(static_cast<std::int64_t>(std::llround(1.25*qPerMetre)),
cfg?cfg->mouthRadiusSubvoxels:0);
const std::int64_t midRadius=std::max<std::int64_t>(mouthRadius,
cfg?cfg->midRadiusSubvoxels:0);
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 connectionCount=cfg?static_cast<std::uint32_t>(std::clamp(cfg->connections,1,12)):1;
for(std::uint32_t connection=0;connection<connectionCount;++connection)
{
ResolvedLayoutPassageV1 passage;
passage.stableId="passage/"+std::to_string(upper.strateIndex)+"-"
+std::to_string(lower.strateIndex)+"/"+std::to_string(connection);
passage.upperStrateIndex=upper.strateIndex;passage.lowerStrateIndex=lower.strateIndex;
passage.placementRadiusSubvoxels=placementRadius;
passage.mouthRadiusSubvoxels=mouthRadius;passage.midRadiusSubvoxels=midRadius;
std::array<std::int64_t,3> upperMouth{},lowerMouth{};
const bool hasUpperRoute=connection==0&&routeAnchor(upper,true,upperMouth);
const bool hasLowerRoute=connection==0&&routeAnchor(lower,false,lowerMouth);
if(!hasUpperRoute||!hasLowerRoute)
{
const auto xy=PassageRandomXY(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
connection,0x504c4143u),inner,placementRadius);
if(!hasUpperRoute)
{
upperMouth[0]=xy[0];upperMouth[1]=xy[1];
const auto reach=reachValue(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
connection,0x55505248u),reachMinimum,reachMaximum);
upperMouth[2]=contentZQ(upper.content.bottomMicrometres)+reach;
}
if(!hasLowerRoute)
{
lowerMouth[0]=xy[0];lowerMouth[1]=xy[1];
const auto reach=reachValue(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
connection,0x4c4f5752u),reachMinimum,reachMaximum);
lowerMouth[2]=contentZQ(lower.content.topMicrometres)-reach;
}
}
if(!radialWithin(upperMouth,placementRadius)||!radialWithin(lowerMouth,placementRadius))
{error=passage.stableId+" route mouth is outside the resolved PassageConfig.DistanceMax radius";return false;}
passage.upperMouthSubvoxels=upperMouth;passage.lowerMouthSubvoxels=lowerMouth;
constexpr std::int64_t LandingExtensionQ=8;
std::array<std::int64_t,2> upperTangent{},lowerTangent{};
const bool hasUpperTangent=hasUpperRoute
&&routeTangent(upper,true,upperTangent);
const bool hasLowerTangent=hasLowerRoute
&&routeTangent(lower,false,lowerTangent);
const std::int64_t ringRadius=std::max<std::int64_t>(1,
placementRadius-mouthRadius-qPerMetre/2);
std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed,
upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16);
if(hasUpperRoute)
{
std::int64_t bestAlignment=std::numeric_limits<std::int64_t>::min();
const std::uint32_t hashedPhase=phase;
for(std::uint32_t offset=0;offset<16;++offset)
{
const std::uint32_t candidate=static_cast<std::uint32_t>(
(hashedPhase+offset)%16);
const std::int64_t alignment=(hasUpperTangent?upperTangent[0]:upperMouth[0])
*directions[candidate][0]
+(hasUpperTangent?upperTangent[1]:upperMouth[1])
*directions[candidate][1];
if(alignment>bestAlignment)
{bestAlignment=alignment;phase=candidate;}
}
}
const bool clockwise=(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
connection,0x44495245u)&1u)!=0;
const int direction=clockwise?1:-1;
const auto ringPoint=[&](std::uint32_t directionIndex)
{
return std::array<std::int64_t,2>{
static_cast<std::int64_t>(ringRadius)*directions[directionIndex][0]/65536,
static_cast<std::int64_t>(ringRadius)*directions[directionIndex][1]/65536};
};
std::vector<std::array<std::int64_t,2>> xy;
const auto pushXY=[&](std::array<std::int64_t,2> p)
{if(xy.empty()||xy.back()!=p)xy.push_back(p);};
const auto firstRing=ringPoint(phase);
pushXY({upperMouth[0],upperMouth[1]});
pushXY(firstRing);
std::uint64_t totalHorizontal=0;std::uint32_t loops=0;
const std::int64_t verticalDistance=std::abs(lowerMouth[2]-upperMouth[2]);
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree contract.
auto measurePath=[&]()
{
std::uint64_t total=0;
for(std::size_t i=1;i<xy.size();++i)
total+=distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
return total;
};
while(loops<256)
{
totalHorizontal=measurePath();
if(totalHorizontal*static_cast<std::uint64_t>(TargetGradeQ16)
>=static_cast<std::uint64_t>(verticalDistance)*65536ull)break;
for(int step=1;step<=16;++step)
{
int directionIndex=static_cast<int>(phase)+direction*step;
directionIndex=(directionIndex%16+16)%16;
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex)));
}
++loops;
}
if(hasLowerTangent)
{
std::uint32_t lowerPhase=static_cast<std::uint32_t>(PassageSeed(seed,
upper.strateIndex,lower.strateIndex,connection,0x4c505248u)%16);
std::int64_t bestAlignment=std::numeric_limits<std::int64_t>::min();
const std::uint32_t hashedLowerPhase=lowerPhase;
for(std::uint32_t offset=0;offset<16;++offset)
{
const std::uint32_t candidate=static_cast<std::uint32_t>(
(hashedLowerPhase+offset)%16);
const auto terminal=ringPoint(candidate);
const std::int64_t dx=lowerMouth[0]-terminal[0];
const std::int64_t dy=lowerMouth[1]-terminal[1];
const std::int64_t alignment=dx*lowerTangent[0]+dy*lowerTangent[1];
const std::int64_t cross=std::abs(dx*lowerTangent[1]-dy*lowerTangent[0]);
if(alignment<=0||cross==0)continue;
const auto length=distanceQ(dx,dy);
const std::int64_t score=alignment*65536/static_cast<std::int64_t>(length);
if(score>bestAlignment)
{bestAlignment=score;lowerPhase=candidate;}
}
if(bestAlignment==std::numeric_limits<std::int64_t>::min())
{error=passage.stableId+" could not find a non-overlapping lower landing approach";return false;}
if(phase!=lowerPhase)
for(int step=1;step<=16;++step)
{
int directionIndex=static_cast<int>(phase)+direction*step;
directionIndex=(directionIndex%16+16)%16;
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex)));
if(static_cast<std::uint32_t>(directionIndex)==lowerPhase)break;
}
pushXY(ringPoint(lowerPhase));
}
pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath();
const std::size_t lowerMouthIndex=xy.size()-1;
std::vector<std::array<std::int64_t,3>> lowerLandingPoints;
if(hasLowerTangent)
{
std::array<std::int64_t,3> previousRoutePoint=lowerMouth;
std::int64_t remainingQ=LandingExtensionQ*qPerMetre;
bool skippedMouth=false;
for(const auto& anchor:lower.anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
{
const std::array<std::int64_t,3> routePoint{{anchor.values[0],
anchor.values[1],anchor.values[2]}};
if(!skippedMouth){skippedMouth=true;continue;}
if(remainingQ<=0)break;
const std::int64_t dx=routePoint[0]-previousRoutePoint[0];
const std::int64_t dy=routePoint[1]-previousRoutePoint[1];
const std::uint64_t segmentLength=distanceQ(dx,dy);
if(segmentLength==0)continue;
const std::int64_t advance=std::min<std::int64_t>(remainingQ,
static_cast<std::int64_t>(segmentLength));
std::array<std::int64_t,3> landingPoint{};
for(std::size_t axis=0;axis<3;++axis)
landingPoint[axis]=previousRoutePoint[axis]+PassageRoundRatio(
routePoint[axis]-previousRoutePoint[axis],advance,
static_cast<std::int64_t>(segmentLength));
if(!radialWithin(landingPoint,placementRadius))break;
lowerLandingPoints.push_back(landingPoint);
pushXY({landingPoint[0],landingPoint[1]});
previousRoutePoint=landingPoint;remainingQ-=advance;
if(advance<static_cast<std::int64_t>(segmentLength))break;
}
if(lowerLandingPoints.empty())
{error=passage.stableId+" lower route has no landing length inside PassageConfig.DistanceMax";return false;}
}
totalHorizontal=measurePath();
if(loops>=256||totalHorizontal==0)
{error=passage.stableId+" could not fit a supported grade inside the resolved placement radius";return false;}
std::vector<std::uint64_t> cumulative(xy.size(),0);
for(std::size_t i=1;i<xy.size();++i)
cumulative[i]=cumulative[i-1]+distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
const std::uint64_t mouthHorizontal=cumulative[lowerMouthIndex];
if(mouthHorizontal==0)
{error=passage.stableId+" has no horizontal reach before its lower mouth";return false;}
passage.controlPoints.reserve(xy.size());
for(std::size_t i=0;i<xy.size();++i)
{
const std::int64_t radius=(i<=1||i+1>=xy.size()-1)?mouthRadius:midRadius;
const std::int64_t playerZ=i<=lowerMouthIndex
?upperMouth[2]+PassageRoundRatio(lowerMouth[2]-upperMouth[2],
static_cast<std::int64_t>(cumulative[i]),static_cast<std::int64_t>(mouthHorizontal))
:lowerLandingPoints[i-lowerMouthIndex-1][2];
ResolvedLayoutPassagePointV1 point;
point.positionSubvoxels={xy[i][0],xy[i][1],playerZ+radius-playerCenterAboveFloorQ};
point.radiusSubvoxels=radius;passage.controlPoints.push_back(point);
}
passage.controlPoints.front().positionSubvoxels[2]=upperMouth[2]+mouthRadius-playerCenterAboveFloorQ;
passage.controlPoints[lowerMouthIndex].positionSubvoxels[2]=
lowerMouth[2]+mouthRadius-playerCenterAboveFloorQ;
layout.passages.push_back(std::move(passage));
}
}
return true;
}
ResolvedLayoutValidatorAuditV3 AuditResolvedLayoutValidatorV3()
{
ResolvedLayoutValidatorAuditV3 result;
auto make=[](std::string id,std::int32_t order,std::int64_t top,std::int64_t bottom)
{
ResolvedLayoutStratumV2 s;s.stableId=std::move(id);s.order=order;s.strateIndex=order;s.generatorKind="synthetic";
@@ -322,16 +812,34 @@ ResolvedLayoutValidatorAuditV2 AuditResolvedLayoutValidatorV2()
s.bedrock={"bedrock",bottom-1000000,bottom,true,true,false,"breakable bedrock","lower face","none"};
return s;
};
const auto expectReject=[&](std::string name,ResolvedLayoutSnapshotV2 candidate)
const auto expectReject=[&](std::string name,ResolvedLayoutSnapshotV3 candidate)
{
++result.controlsRun;std::vector<ResolvedLayoutIssueV2> issues;std::string error;
if(!candidate.validate(ResolvedLayoutValidationModeV2::strict,issues,error))++result.controlsRejectedAsExpected;
if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,error))++result.controlsRejectedAsExpected;
else result.failures.push_back({"negative_control_accepted",std::move(name)+" was accepted by strict validator"});
};
auto base=ResolvedLayoutSnapshotV2{};base.worldIdentity="synthetic";base.strates={make("a",0,10000000,0),make("b",1,-2000000,-12000000)};
auto base=ResolvedLayoutSnapshotV3{};base.worldIdentity="synthetic";base.ownerVoxelSizeMicrometres=125000;
base.coreVoxelSizeMicrometres=250000;base.strates={make("a",0,10000000,0),make("b",1,-2000000,-12000000)};
ResolvedLayoutPassageConfigV1 passageConfig;passageConfig.upperStrateIndex=0;passageConfig.connections=1;
passageConfig.mouthRadiusSubvoxels=1280;passageConfig.midRadiusSubvoxels=1280;
passageConfig.reachMinimumSubvoxels=2048;passageConfig.reachMaximumSubvoxels=2048;
passageConfig.distanceMinimumSubvoxels=1024;passageConfig.distanceMaximumSubvoxels=10240;
std::string planError;
if(!PlanResolvedLayoutPassagesV1(base,19,{passageConfig},planError))
result.failures.push_back({"synthetic_passage_plan_failed",planError});
{std::vector<ResolvedLayoutIssueV2> issues;std::string error;
if(!base.validate(ResolvedLayoutValidationModeV2::strict,issues,error))
if(!base.validate(ResolvedLayoutValidationModeV3::strict,issues,error))
result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"});}
auto missingPassage=base;missingPassage.passages.clear();expectReject("missing interface passage",missingPassage);
auto outsidePassage=base;
if(!outsidePassage.passages.empty())
{
outsidePassage.passages.front().upperMouthSubvoxels[0]
=outsidePassage.passages.front().placementRadiusSubvoxels+1;
outsidePassage.passages.front().controlPoints.front().positionSubvoxels[0]
=outsidePassage.passages.front().upperMouthSubvoxels[0];
}
expectReject("passage mouth outside placement radius",outsidePassage);
auto boundaryGap=base;boundaryGap.strates[1].ceiling.topMicrometres-=1;
expectReject("bedrock/next-ceiling gap",boundaryGap);
auto missingBedrock=base;missingBedrock.strates[0].bedrock.presentInCurrentField=false;expectReject("missing bedrock",missingBedrock);
+188 -22
View File
@@ -33,7 +33,7 @@
class FStandaloneShapeWorld
{
public:
const VoxelForgeCore::ResolvedLayoutSnapshotV2* ResolvedLayout=nullptr;
const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr;
struct FResolvedStratumBounds
{
double BottomZ=0.0;
@@ -97,6 +97,170 @@ public:
return -std::max(0.05f,static_cast<float>(band->solidDensityQ16)/65536.0f);
}
float ApplyResolvedStratePassageCarves(float density,float X,float Y,float Z) const
{
if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X)
||!std::isfinite(Y)||!std::isfinite(Z))return density;
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double qx=static_cast<double>(X)*Q,qy=static_cast<double>(Y)*Q,qz=static_cast<double>(Z)*Q;
const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
const double floorThicknessVoxels=qPerMetre/Q;
const double playerCenterAboveFloorQ=1.13*qPerMetre;
float composed=density;
for(const auto& passage:ResolvedLayout->passages)
{
if(passage.controlPoints.size()<2)continue;
const double placementRadiusQ=static_cast<double>(passage.placementRadiusSubvoxels)
+static_cast<double>(passage.midRadiusSubvoxels);
if(qx*qx+qy*qy>placementRadiusQ*placementRadiusQ)continue;
const double firstFloorQ=passage.controlPoints.front().positionSubvoxels[2]
-passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ;
const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2]
-passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ;
if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels
||qz<lastFloorQ-2.0*passage.mouthRadiusSubvoxels)continue;
const auto floorProfile=[&](const auto& point)
{return point.positionSubvoxels[2]-point.radiusSubvoxels+playerCenterAboveFloorQ;};
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(passage.controlPoints.size())-1;
const std::ptrdiff_t lowerMouthIndex=std::clamp<std::ptrdiff_t>(
static_cast<std::ptrdiff_t>(passage.lowerMouthControlIndex),1,lastIndex);
const auto mainEnd=passage.controlPoints.begin()+lowerMouthIndex+1;
const auto carveFound=std::lower_bound(passage.controlPoints.begin(),mainEnd,qz,
[&](const auto& point,double value){return floorProfile(point)>value;});
const std::ptrdiff_t carveCenter=carveFound-passage.controlPoints.begin();
const std::ptrdiff_t carveBegin=std::max<std::ptrdiff_t>(0,carveCenter-3);
const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,carveCenter+2);
double bestSignedDistance=std::numeric_limits<double>::infinity();
double bestT=0.0;
std::ptrdiff_t bestIndex=0;
bool hasSupportFloor=false;
double bestSupportFloorVoxels=-std::numeric_limits<double>::infinity();
double bestSupportDensity=0.0;
const auto considerCarveSegment=[&](std::ptrdiff_t index)
{
const auto& a=passage.controlPoints[static_cast<std::size_t>(index)];
const auto& b=passage.controlPoints[static_cast<std::size_t>(index+1)];
const double ax=static_cast<double>(a.positionSubvoxels[0]);
const double ay=static_cast<double>(a.positionSubvoxels[1]);
const double az=static_cast<double>(a.positionSubvoxels[2]);
const double dx=static_cast<double>(b.positionSubvoxels[0])-ax;
const double dy=static_cast<double>(b.positionSubvoxels[1])-ay;
const double dz=static_cast<double>(b.positionSubvoxels[2])-az;
const double lengthSq=dx*dx+dy*dy+dz*dz;
if(lengthSq<=0.0)return;
const double t=std::clamp(((qx-ax)*dx+(qy-ay)*dy+(qz-az)*dz)/lengthSq,0.0,1.0);
const double cx=ax+t*dx,cy=ay+t*dy,cz=az+t*dz;
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double radius=static_cast<double>(a.radiusSubvoxels)
+t*(b.radiusSubvoxels-a.radiusSubvoxels);
const double signedDistance=DeterministicSqrt(ex*ex+ey*ey+ez*ez)-radius;
if(signedDistance<bestSignedDistance)
{bestSignedDistance=signedDistance;bestT=t;bestIndex=index;}
};
for(std::ptrdiff_t index=carveBegin;index<=carveEnd;++index)
considerCarveSegment(index);
// The route-bound landing can rise and fall after the lower mouth. Scan
// that short tail directly; binary search is only valid on the descent.
for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index)
considerCarveSegment(index);
if(!std::isfinite(bestSignedDistance))continue;
// Positive density is air; the passage SDF is applied after both owner bands.
composed=std::max(composed,static_cast<float>(-bestSignedDistance/Q));
const auto& nearestA=passage.controlPoints[static_cast<std::size_t>(bestIndex)];
const auto& nearestB=passage.controlPoints[static_cast<std::size_t>(bestIndex+1)];
const double nearestRadius=nearestA.radiusSubvoxels+bestT*(
static_cast<double>(nearestB.radiusSubvoxels)-nearestA.radiusSubvoxels);
const double nearestPlayerCenterZ=nearestA.positionSubvoxels[2]+bestT*(
static_cast<double>(nearestB.positionSubvoxels[2])-nearestA.positionSubvoxels[2])
-nearestRadius+playerCenterAboveFloorQ;
// Only compose a support shelf below the capsule feet. This keeps support
// probes on the matching floor while capsule samples remain air at bends.
if(qz<nearestPlayerCenterZ-0.90*qPerMetre)
{
const double supportCenterQueryQ=qz+playerCenterAboveFloorQ;
const auto supportFound=std::lower_bound(passage.controlPoints.begin(),
mainEnd,supportCenterQueryQ,
[&](const auto& point,double value){return floorProfile(point)>value;});
const std::ptrdiff_t supportCenter=supportFound-passage.controlPoints.begin();
const std::ptrdiff_t supportBegin=std::max<std::ptrdiff_t>(0,supportCenter-3);
const std::ptrdiff_t supportEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,supportCenter+2);
const auto considerSupportSegment=[&](std::ptrdiff_t index)
{
const auto& segmentA=passage.controlPoints[static_cast<std::size_t>(index)];
const auto& segmentB=passage.controlPoints[static_cast<std::size_t>(index+1)];
const double dx=static_cast<double>(segmentB.positionSubvoxels[0])-segmentA.positionSubvoxels[0];
const double dy=static_cast<double>(segmentB.positionSubvoxels[1])-segmentA.positionSubvoxels[1];
const double horizontalLengthSq=dx*dx+dy*dy;
const double supportT=horizontalLengthSq>0.0
?std::clamp(((qx-segmentA.positionSubvoxels[0])*dx
+(qy-segmentA.positionSubvoxels[1])*dy)/horizontalLengthSq,0.0,1.0)
:bestT;
const double supportCenterX=segmentA.positionSubvoxels[0]+supportT*dx;
const double supportCenterY=segmentA.positionSubvoxels[1]+supportT*dy;
const double supportCenterZ=segmentA.positionSubvoxels[2]+supportT*(
static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]);
const double supportRadius=segmentA.radiusSubvoxels+supportT*(
static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels);
// Leave a 12 cm settle gap below the capsule feet while staying inside
// the route support probes' search depth.
const double floorQ=supportCenterZ-supportRadius+0.12*qPerMetre;
const double floorVoxels=floorQ/Q;
const double horizontalX=qx-supportCenterX,horizontalY=qy-supportCenterY;
const double horizontalDistance=DeterministicSqrt(horizontalX*horizontalX+horizontalY*horizontalY);
const double supportHalfWidth=std::max(0.0,supportRadius-0.15*qPerMetre);
if(horizontalDistance<=supportHalfWidth&&Z>=floorVoxels-floorThicknessVoxels
&&Z<=floorVoxels+1.0/Q)
{
if(!hasSupportFloor||floorVoxels>bestSupportFloorVoxels)
{
bestSupportFloorVoxels=floorVoxels;hasSupportFloor=true;
bestSupportDensity=std::clamp(Z-floorVoxels,-floorThicknessVoxels,0.0);
}
}
};
for(std::ptrdiff_t index=supportBegin;index<=supportEnd;++index)
considerSupportSegment(index);
for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index)
considerSupportSegment(index);
}
if(hasSupportFloor)
composed=std::min(composed,static_cast<float>(bestSupportDensity));
}
return composed;
}
float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const
{return ApplyResolvedStratePassageCarves(ApplyResolvedStrateBoundaryBand(density,X,Y,Z),X,Y,Z);}
bool ResolvedPassageIntersectsBox(const int32 minimum[3],const int32 maximum[3]) const
{
if(!ResolvedLayout||ResolvedLayout->passages.empty())return false;
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
for(const auto& passage:ResolvedLayout->passages)
{
for(std::size_t i=1;i<passage.controlPoints.size();++i)
{
const auto& a=passage.controlPoints[i-1];const auto& b=passage.controlPoints[i];
const double radius=static_cast<double>(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q;
const double floorThicknessVoxels=Owner&&Owner->CoreVoxelSizeMicrons>0
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
const double low[3]={
std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-radius,
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-radius,
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-floorThicknessVoxels};
const double high[3]={
std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+radius,
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+radius,
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius};
bool overlap=true;
for(int axis=0;axis<3;++axis)
overlap&=high[axis]>=minimum[axis]&&low[axis]<=maximum[axis];
if(overlap)return true;
}
}
return false;
}
static bool MakeResolvedStratumBounds(double BottomZ,double TopZ,double SealThickness,
FResolvedStratumBounds& Out)
{
@@ -410,7 +574,7 @@ public:
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InGpuVisualDetailTiles={},
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InCpuVisualDetailTiles={},
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly=nullptr,
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout=nullptr)
const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout=nullptr)
{
using Clock=std::chrono::steady_clock;
const auto TotalStart=Clock::now();
@@ -571,7 +735,7 @@ public:
const FIntVector& ClipMaxVoxels,EDetailVariant Variant,
bool bInRoundTunnels,
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout,
const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout,
std::string& Error)
{
using Clock=std::chrono::steady_clock;
@@ -611,7 +775,7 @@ public:
void InitializeNativeSurfaceOnly(FStandaloneOwnerWorld& World,
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout,
const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout,
const FIntVector& InMinimum,const FIntVector& InMaximum)
{
Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout;
@@ -622,7 +786,7 @@ public:
bool BuildShapeDependencyProof(FStandaloneOwnerWorld& World,
const FIntVector& MinVoxels,const FIntVector& MaxVoxels,bool bInRoundTunnels,
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout=nullptr)
const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout=nullptr)
{
Owner=&World;ResolvedLayout=InResolvedLayout;WorkerCount=1;RoundTunnelsEnabled=bInRoundTunnels;
bAnalyticOnly=true;bNativeOnly=false;Stats={};
@@ -1129,19 +1293,19 @@ public:
float NativeDensity=0.0f;
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{RecordNativeOnlyViolation();return 0.0f;}
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z);
}
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
{
++ThreadClassificationStats().NativeFloatFieldCalls;
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z),X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateComposition(IslandDensity,X,Y,Z),X,Y,Z);
}
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{
++ThreadClassificationStats().NativeFloatFieldCalls;
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z);
}
if(UsesLegacySurfaceField(X,Y,Z))
return LegacySurfaceDensityAt(X,Y,Z);
@@ -1191,21 +1355,21 @@ public:
if(!NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),NativeDensity))
{RecordNativeOnlyViolation();return 0.0f;}
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z);
}
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),IslandDensity))
{
++ThreadClassificationStats().NativeLatticeFieldCalls;
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z),X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateComposition(IslandDensity,X,Y,Z),X,Y,Z);
}
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),NativeDensity))
{
++ThreadClassificationStats().NativeLatticeFieldCalls;
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z);
}
if(UsesLegacySurfaceField(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z)))
@@ -1229,19 +1393,19 @@ public:
float NativeDensity=0.0f;
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{RecordNativeOnlyViolation();return 0.0f;}
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z);
}
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
{
++ThreadClassificationStats().NativeUnclampedFieldCalls;
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z),X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateComposition(IslandDensity,X,Y,Z),X,Y,Z);
}
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{
++ThreadClassificationStats().NativeUnclampedFieldCalls;
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z);
}
if(UsesLegacySurfaceField(X,Y,Z))
return LegacySurfaceDensityAt(X,Y,Z);
@@ -1371,13 +1535,13 @@ private:
float Density=static_cast<float>(Proxy)*(1.0f/256.0f);
if((Density<0.0f)!=bRock)
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/256.0f);
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(Density,FX,FY,FZ),FX,FY,FZ);
return ApplyEditBrushes(ApplyResolvedStrateComposition(Density,FX,FY,FZ),FX,FY,FZ);
}
// Match the fixed fallback to the edge of the stored proxy band. The
// displacement is tapered to zero there, so this avoids a magnitude
// jump along the candidate bit-mask boundary.
constexpr float ProxyBandEdge=DetailSupremum+0.125f;
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(
return ApplyEditBrushes(ApplyResolvedStrateComposition(
bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ),FX,FY,FZ);
}
@@ -1395,7 +1559,7 @@ private:
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
{
const float composed=ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z);
const float composed=ApplyResolvedStrateComposition(IslandDensity,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
}
if(UsesLegacySurfaceField(X,Y,Z))
@@ -1403,11 +1567,11 @@ private:
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{
NativeDensity=ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z);
NativeDensity=ApplyResolvedStrateComposition(NativeDensity,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(NativeDensity,X,Y,Z):NativeDensity;
}
const float Density=Owner->Generator->GetDensityAt(X,Y,Z);
const float composed=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
const float composed=ApplyResolvedStrateComposition(Density,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
}
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]
@@ -1422,7 +1586,7 @@ private:
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
bClampNearZero?0.05f:1.0e-7f);
const float composed=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
const float composed=ApplyResolvedStrateComposition(Density,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
}
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
@@ -1452,7 +1616,7 @@ private:
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
bClampNearZero?0.05f:1.0e-7f);
Density=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
Density=ApplyResolvedStrateComposition(Density,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
}
@@ -1590,6 +1754,8 @@ public:
else ++Counts.MixedBoxes;
return Result;
};
if(ResolvedPassageIntersectsBox(BMin,BMax))
return Finish(EVoxelTileClass::Mixed,true);
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
&&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2])
@@ -2319,7 +2485,7 @@ private:
// SurfaceWorld consumes biome overrides, terrain ops, water level and its sky cap in
// the legacy stack. Reuse that immutable evaluator as an exact temporary bridge.
// SurfaceWorld lit biomes, ops de terrain, niveau d'eau et plafond; reutiliser ce champ.
const float density=ApplyResolvedStrateBoundaryBand(Owner->Generator->GetDensityAt(X,Y,Z),X,Y,Z);
const float density=ApplyResolvedStrateComposition(Owner->Generator->GetDensityAt(X,Y,Z),X,Y,Z);
return ApplyEditBrushes(density,X,Y,Z);
}
+24 -12
View File
@@ -34,7 +34,7 @@ namespace VoxelForgeCore
{
std::shared_ptr<FStandaloneOwnerWorld> owner;
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
std::shared_ptr<const ResolvedLayoutSnapshotV3> resolvedLayout;
WorldConfiguration configuration;
};
@@ -869,7 +869,7 @@ namespace VoxelForgeCore
{
static ResolvedWorldSnapshot FromOwner(std::shared_ptr<FStandaloneOwnerWorld> owner,
std::shared_ptr<const WorldAssemblyManifest> worldAssembly = {},
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout = {})
std::shared_ptr<const ResolvedLayoutSnapshotV3> resolvedLayout = {})
{
if(!owner||!owner->Settings||!owner->Manager||!owner->Generator)return {};
auto data=std::make_shared<ResolvedWorldSnapshot::Data>();
@@ -894,15 +894,15 @@ namespace VoxelForgeCore
if(!parsed)return {};
worldAssembly=std::make_shared<WorldAssemblyManifest>(std::move(*parsed));
}
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
std::shared_ptr<const ResolvedLayoutSnapshotV3> resolvedLayout;
if(jsonRoot.Get("resolved_layout_json").IsString())
{
auto parsed=ResolvedLayoutSnapshotV2::FromJson(jsonRoot.Get("resolved_layout_json").String(),error);
auto parsed=ResolvedLayoutSnapshotV3::FromJson(jsonRoot.Get("resolved_layout_json").String(),error);
if(!parsed)return {};
std::vector<ResolvedLayoutIssueV2> issues;std::string strictError;
if(!parsed->validate(ResolvedLayoutValidationModeV2::strict,issues,strictError))
if(!parsed->validate(ResolvedLayoutValidationModeV3::strict,issues,strictError))
{error="strict resolved-layout validation failed: "+strictError;return {};}
resolvedLayout=std::make_shared<ResolvedLayoutSnapshotV2>(std::move(*parsed));
resolvedLayout=std::make_shared<ResolvedLayoutSnapshotV3>(std::move(*parsed));
}
const bool seedChanged=owner->Seed!=seed;
@@ -943,24 +943,36 @@ namespace VoxelForgeCore
if(!parsed)return std::nullopt;
worldAssembly=std::make_shared<WorldAssemblyManifest>(std::move(*parsed));
}
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
std::shared_ptr<const ResolvedLayoutSnapshotV3> resolvedLayout;
if(root.Get("resolved_layout_json").IsString())
{
auto parsed=ResolvedLayoutSnapshotV2::FromJson(root.Get("resolved_layout_json").String(),error);
auto parsed=ResolvedLayoutSnapshotV3::FromJson(root.Get("resolved_layout_json").String(),error);
if(!parsed)return std::nullopt;
std::vector<ResolvedLayoutIssueV2> issues;std::string strictError;
if(!parsed->validate(ResolvedLayoutValidationModeV2::strict,issues,strictError))
if(!parsed->validate(ResolvedLayoutValidationModeV3::strict,issues,strictError))
{error="strict resolved-layout validation failed: "+strictError;return std::nullopt;}
resolvedLayout=std::make_shared<ResolvedLayoutSnapshotV2>(std::move(*parsed));
resolvedLayout=std::make_shared<ResolvedLayoutSnapshotV3>(std::move(*parsed));
}
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly),std::move(resolvedLayout));
}
const WorldAssemblyManifest* ResolvedWorldSnapshot::worldAssembly() const noexcept
{ return data_?data_->worldAssembly.get():nullptr; }
const ResolvedLayoutSnapshotV2* ResolvedWorldSnapshot::resolvedLayout() const noexcept
const ResolvedLayoutSnapshotV3* ResolvedWorldSnapshot::resolvedLayout() const noexcept
{ return data_?data_->resolvedLayout.get():nullptr; }
ResolvedWorldSnapshot ResolvedWorldSnapshot::withoutResolvedLayoutPassages() const
{
if(!data_||!data_->resolvedLayout)return {};
auto copy=std::make_shared<Data>(*data_);
auto layout=std::make_shared<ResolvedLayoutSnapshotV3>(*data_->resolvedLayout);
layout->passages.clear();
std::string error;
if(!layout->finalize(error))return {};
copy->resolvedLayout=std::move(layout);
return ResolvedWorldSnapshot(std::move(copy));
}
const WorldConfiguration& ResolvedWorldSnapshot::configuration() const
{
static const WorldConfiguration empty{};
@@ -971,7 +983,7 @@ namespace VoxelForgeCore
{
std::shared_ptr<FStandaloneOwnerWorld> owner;
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
std::shared_ptr<const ResolvedLayoutSnapshotV3> resolvedLayout;
WorldConfiguration configuration;
std::unique_ptr<FStandaloneShapeWorld> shape;
std::atomic<bool> shapeReady{false};
@@ -0,0 +1,158 @@
# L23 — inter-strate passage
## Owner answer
**After L22, Core did not have a resolved-layout passage through the new bands.** L22 exported
`FVoxelPassage` points as `milli owner voxels` metadata, but the native FloatingIslands and
SurfaceWorld Core paths did not use that metadata to carve the resolved bands. The legacy manager
still has `UVoxelStrateManager::GeneratePassages`; its coordinates are in owner voxels, not metres.
The owner voxel is 12.5 cm (`OwnerVoxelSizeMicrons=125000`); `voxel_size_cm=25` is the Core voxel.
The legacy 0→1 passage therefore had upper `(-19.594, 3.137, -14.255) m` and lower
`(-19.594, 3.137, -102.959) m`, with radius 1 m. Its lower point was still in FloatingIslands
content (`-119.5…+7.5 m`), 23.29 m above SurfaceWorld content (`-625.75…-126.25 m`). It did not
open the FI bedrock (`-120…-119.5 m`) and SW ceiling (`-126.25…-120 m`). The JSON also contains a
legacy 0→0 passage; L23 plans only adjacent resolved strata.
L23 now derives the passage from the director-resolved bands and route anchors. For the FI recipe,
mouths are `(9.800, 4.571, -54.831) m` and `(9.250, 9.250, -502.608) m`. For the SurfaceWorld
recipe the unanchored upper mouth is `(10.732, -5.775, -107.766) m`; the lower mouth is the same.
Both locations are inside the actual content and within 24 m of `(0,0)`.
## Planner, radius, and routes
`UVoxelStrateManager::GeneratePassages` remains the legacy Unreal planner. L22's season manifest
carried recipes/routes, while the adapter copied manager passage points into V2 anchors with the
legacy units. L23's adapter and headless director convert each upper strate's `PassageConfig` to
Core fixed units, build a V3 layout with route anchors, then call `PlanResolvedLayoutPassagesV1`.
The planner hashes seed, upper/lower strate, connection, and salt, so peers with the same resolved
input choose the same path. Strate moves and route changes trigger a new plan; exported root
`passages` are not imported.
The placement disk comes from `PassageConfig.DistanceMax=192` owner voxels = 24 m; when no route
anchor is available, `DistanceMin=64` owner voxels = 8 m supplies the inner annulus. `OriginSpineRadius`
and spawn radius are separate settings. `MouthRadius`/`MidRadius` set tunnel cross-section; Core
enforces a 1.25 m minimum radius for the player capsule. Neither ceiling nor cap parameters changed.
In the FI snapshot, route point 28 (last of 29) equals the FI departure mouth; SW route point 0
(first of 33) equals its arrival mouth. Both route audits pass. This owner layout has exactly two
strata, so SW has no next interface/departure mouth; its free route endpoint is
`(54.750, 54.750, -502.308) m`.
## Passage audit
Capsule is 0.34 m × 1.76 m. A passage fails on any blocked capsule sample, unsupported floor probe,
grade failure, mouth outside content/radius, route-mouth mismatch, or if the no-passage control
does not detect blockage.
| Recipe | Interfaces | Center samples | Capsule samples | Blocked | Support probes / unsupported | Grade failures | Mouths in content / radius |
|---|---:|---:|---:|---:|---:|---:|---:|
| FloatingIslands → SurfaceWorld | 1/1 | 5,174 | 325,962 | 0 | 40,412 / 0 | 0 | 2/2 · 2/2 |
| TunnelNetwork → SurfaceWorld | 1/1 | 4,513 | 284,319 | 0 | 35,254 / 0 | 0 | 2/2 · 2/2 |
The deliberate no-passage controls found 17,218 blocked capsule samples (FI recipe) and 23,876
(SurfaceWorld recipe); both were detected. FI’s 29-point route and SW’s 33-point route pass with
0 blocked samples, 0 unsupported probes, 0 grade failures, and mouth binding 2/2. The
SurfaceWorld-only route passes 33 points with the same zero failures and binding 1/1.
The shared composition layer applies each generator field, then bedrock/ceiling bands, then the
bounded passage carve and support shelf. Only the tunnel path opens a band. The full 0→1 capsule
audit crosses the FI bedrock and SW ceiling; support stays below the capsule.
The audit samples the resolved Core field by interface owner and does not reuse the old ring-table
probe, so it does not pair band and content samples from different surface owners.
Runtime passage checks covered all eight generator kinds: TunnelNetwork, FlatPlain, CrystalChamber,
Maze, VerticalShafts, and Underwater as upper-strate fixtures; FloatingIslands in the FI recipe;
SurfaceWorld as the real lower strate in both owner-world recipes. The SurfaceWorld-as-upper fixture
could not produce its separate route: all 648 route candidates failed the grade filter. That fixture
is recorded as a failed route setup, not counted as a passage pass. The real SurfaceWorld lower
strate audits pass.
## Determinism, proofs, and content hashes
Clang 19 (`--fp-contract=off`) and GCC 14 (`--fp-contract=default`) builds passed. Both executables
ran the mandatory Core FP-contraction self-test without rejection. For both recipes, W1/W8/W12×5
across both compilers produced one distinct result per hash class. The same 28-run matrix passed the
24 m region crossing: 314 moving tiles, stable within each recipe. Route parity passed on both
compilers: 8/8 page pairs, 14 higher-LOD exact pairs, 782,026 exact-query samples, and one edit,
all with zero mismatches. V3 strict validation accepted both layouts with 0 issues; all 9 malformed
layout controls were rejected.
Passage-centered uniform-proof boxes are conservatively unknown/fallback (FI: 21,504 fallback and
unknown, 0 violations); no false certificate is emitted around the new carve. Away from the passage,
SurfaceWorld at `(512,512,-500) m` matches the L22 baseline bit-for-bit: 27,368 certified blocks,
36,426,808 samples, 1,304 fallbacks, 0 unknowns/violations. Full-detail hash is
`66ac781666579e3e / 914873894a0e38c4 / ec8b03ade039cd56 / a2b621b0321d43dc` (density/mesh/payload/
topology); whole-view hash is `ddc6d1942db48f52 / bce985905e70dac4 / 266f5a008bcf4491 /
e6fa520c5ad2817b`. LOD0–3 and 9 exact-query hashes also match. The earlier unbounded comparison
included the far horizon and was discarded; this comparison used render-distance 0 and max LOD 3.
Changes are limited to resolved passage planning, band openings along those paths, and supported
landings. SurfaceWorld content away from the passage is unchanged by the hashes above. L3 FP
contract setup/self-test, L4 `FLazy`, L7 shared cache, L9/L12 proof behavior, L11 readiness/crash
path, L13 route binding, L18/L19 shared ceiling, L21/L22 layout bands, ARCHITECTURE §8.10, and
`CLAUDE.md` async/Epoch rules remain intact. The route and passage audits exercise L13 binding; the
async/cache files were not edited.
## Captures
Small 1280×720 JPEGs (all render inputs/PNG intermediates stay in `Saved/Linux/linux23/`):
- [FI passage mouth](captures/fi-mouth.jpg)
- [Tunnel at the FI bedrock / SW ceiling interface](captures/tunnel-interface.jpg)
- [Arrival in SurfaceWorld](captures/sw-arrival.jpg)
## Changes and Windows checklist
Version bumps: resolved-layout snapshot V3, evaluator 9, FloatingIslands geometry 4. The only
Unreal-side source edit is `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`: it binds V3
routes/configs and invokes the resolved planner. Core and standalone sources build here; the Unreal
adapter cannot be compiled on this Linux host.
On Windows:
1. Build the Editor/plugin with the updated Core header and adapter; check UHT and link.
2. Load the owner world at seed 0 and verify strict V3 validation and stable mouth coordinates.
3. Walk the FI route, passage, and SW route with the 0.34 m × 1.76 m capsule; inspect all three
capture viewpoints in-engine.
4. Move a strate or route anchor, reload, and verify the passage is re-derived and peer-identical.
5. Confirm SW away from the passage and its ceiling/cap parameters remain unchanged.
## Failed and retried runs
Failed development audit logs (all retained under `Saved/Linux/linux23/`): FI
`audit-floating-islands.log`, `r2–r5`, `r17–r19`, `r21–r36`, `r38–r41`; SurfaceWorld
`audit-surface-world.log`, `audit-surface-world-diagnostic.log`, `audit-surface-world-diagnostic-r2/r3`,
and `audit-surface-world-r2–r7`, `r19`, `r21–r22`, `r24–r26`, `r28–r31`, `r33`, `r35`. The early
failures were blocked capsule samples or unsupported landing probes. r35 still had 10 unsupported
passage support probes; r36’s correction left 1,977 blocked capsule samples. r37 build failed on
undeclared `begin/end`; r38 regressed to 10 unsupported probes. r39–r41 diagnostics traced support
search across a non-monotone lower route tail; r42 build failed on `continue` inside a lambda. The
lower-mouth split/index fix passed r43; final Clang/GCC builds and audits pass.
Other retries: `plan-surface-world-r2.log` used the wrong data path; two layout-only calls omitted
the required benchmark/pipeline flags and exited 2. The first upper-SurfaceWorld fixture failed its
648-candidate route search as noted above. Initial offscreen renders hit the read-only Mesa cache
location and were rerun with cache disabled and the CPU/address-space limits. Initial captures were
also rerendered under affinity/memory limits. Camera trials were replaced by the three listed JPEGs.
The first SurfaceWorld away hash comparison included the far horizon and was replaced with the
bounded L22/L23 comparison. The hash parser initially reported missing rows because its regex was
not multiline; corrected parsing found 28/28 runs and zero mismatches.
Runs were pinned to CPUs 0–11 and capped at 6 GiB address space; no task exceeded 30 minutes or
stalled for 15. This shell inherited nice level 5; an attempt to restore normal priority was denied,
so I made no priority or governor change. No commit, push, or stash was made.
## Orchestrator review (Claude)
- Merged as a correctness fix: the strates were not connected after L22 (and Core never carved the
passage at all); now a planned, deterministic passage crosses FI bedrock and the SW ceiling, with a
capsule audit of 0 blocked / 0 unsupported and a detected negative control.
- **The look is not yet "natural" (my capture review):** `fi-mouth.jpg` shows a stepped tube hanging in
FI's open void (there is no rock to carve between the island-level mouth at −54.8 m and the bedrock at
−119.5 m); `sw-arrival.jpg` shows flat stacked terraces; `tunnel-interface.jpg` is too close to read.
Owner input needed on where an FI passage should start (island vs bedrock floor) and how it should
look.
- Open: the SurfaceWorld-as-upper-strate fixture can't plan its route (648/648 grade-rejected), a real
limit for future layouts.
- Unreal adapter edit uncompiled here; Windows checklist above.
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -67,6 +67,7 @@ namespace
std::filesystem::path ResolvedLayoutOutputPath;
bool ResolvedLayoutOnly=false;
bool AuditResolvedLayoutBoundaries=false;
bool AuditResolvedLayoutPassages=false;
bool AuditSurfaceClearance=false;
};
@@ -125,6 +126,7 @@ namespace
" --resolved-layout-output=FILE (canonical plan-time layout snapshot)\n"
" --resolved-layout-only (exit after plan and snapshot validation)\n"
" --audit-resolved-layout-boundaries (query Core at each recorded endpoint)\n"
" --audit-resolved-layout-passages (capsule, support, route-binding and blocked-control audit)\n"
" --audit-surface-clearance (sample biome terrain/caps against the resolved ceiling)\n"
" --auto-surface-camera (place the 27-tile cube on this seed's surface)\n"
" --mesh-coverage (report non-empty tiles and indexed triangles)\n"
@@ -145,6 +147,7 @@ namespace
if(arg=="--world-assembler-prototype")options.WorldAssemblerPrototype=true;
else if(arg=="--resolved-layout-only")options.ResolvedLayoutOnly=true;
else if(arg=="--audit-resolved-layout-boundaries")options.AuditResolvedLayoutBoundaries=true;
else if(arg=="--audit-resolved-layout-passages")options.AuditResolvedLayoutPassages=true;
else if(arg=="--audit-surface-clearance")options.AuditSurfaceClearance=true;
else if(arg=="--stream-benchmark")options.StreamBenchmark=true;
else if(arg=="--stream-pipeline")options.StreamPipeline=true;
@@ -332,14 +335,14 @@ int main(int argc,char** argv)
&&!StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly(
*ownerWorld,ownerJsonRoot,worldAssembly,error))
{std::fprintf(stderr,"Floating-island assembly setup failed: %s\n",error.c_str());return 2;}
VoxelForgeCore::ResolvedLayoutSnapshotV2 resolvedLayout;
VoxelForgeCore::ResolvedLayoutSnapshotV3 resolvedLayout;
{
const std::string_view variant=options.FloatingIslandsRecipe?"floating-islands":"surface-world";
if(!StandaloneWorldAssemblyPrototype::BuildResolvedLayoutSnapshot(
*ownerWorld,worldAssembly,variant,resolvedLayout,error))
{std::fprintf(stderr,"Resolved-layout snapshot failed: %s\n",error.c_str());return 2;}
const std::string layoutJson=resolvedLayout.ToJson();
const auto roundTrip=VoxelForgeCore::ResolvedLayoutSnapshotV2::FromJson(layoutJson,error);
const auto roundTrip=VoxelForgeCore::ResolvedLayoutSnapshotV3::FromJson(layoutJson,error);
if(!roundTrip||roundTrip->ToJson()!=layoutJson||roundTrip->stableIdentity!=resolvedLayout.stableIdentity)
{if(error.empty())error="resolved-layout canonical serialization did not round-trip";
std::fprintf(stderr,"Resolved-layout round trip failed: %s\n",error.c_str());return 2;}
@@ -352,16 +355,16 @@ int main(int argc,char** argv)
output.write(layoutJson.data(),static_cast<std::streamsize>(layoutJson.size()));
if(!output){std::fprintf(stderr,"Resolved-layout snapshot write failed: %s\n",options.ResolvedLayoutOutputPath.c_str());return 2;}
}
const auto controls=VoxelForgeCore::AuditResolvedLayoutValidatorV2();
const auto controls=VoxelForgeCore::AuditResolvedLayoutValidatorV3();
std::vector<VoxelForgeCore::ResolvedLayoutIssueV2> compatibilityIssues;
std::string compatibilityError;
const bool compatibilityAccepted=resolvedLayout.validate(
VoxelForgeCore::ResolvedLayoutValidationModeV2::compatibility,
VoxelForgeCore::ResolvedLayoutValidationModeV3::compatibility,
compatibilityIssues,compatibilityError);
std::vector<VoxelForgeCore::ResolvedLayoutIssueV2> strictIssues;
std::string strictError;
const bool strictAccepted=resolvedLayout.validate(
VoxelForgeCore::ResolvedLayoutValidationModeV2::strict,strictIssues,strictError);
VoxelForgeCore::ResolvedLayoutValidationModeV3::strict,strictIssues,strictError);
std::printf("[ResolvedLayoutSnapshot] schema=%u stable_identity=%s strata=%zu bytes=%zu round_trip=PASS\n",
resolvedLayout.schemaVersion,resolvedLayout.stableIdentity.c_str(),resolvedLayout.strates.size(),layoutJson.size());
std::printf("[ResolvedLayoutCompatibility] accepted=%s issues=%zu error=%s\n",
@@ -548,5 +551,6 @@ int main(int argc,char** argv)
options.RegionCrossing,options.AuditTileShapeReadiness,
options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence,
options.AuditRouteParity,options.AuditResolvedLayoutBoundaries,
options.AuditResolvedLayoutPassages,
&resolvedLayout);
}
+345 -1
View File
@@ -255,7 +255,8 @@ namespace StandaloneStreamBenchmark
bool bAuditTileShapeReadiness=false,int DiagnosticSlowShapeCompileMs=0,
bool bAuditBasePageEquivalence=false,bool bAuditRouteParity=false,
bool bAuditResolvedLayoutBoundaries=false,
const VoxelForgeCore::ResolvedLayoutSnapshotV2* ResolvedLayout=nullptr)
bool bAuditResolvedLayoutPassages=false,
const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr)
{
using Clock=std::chrono::steady_clock;
FStandaloneOwnerWorld& World=*OwnerWorld;
@@ -440,6 +441,349 @@ namespace StandaloneStreamBenchmark
Streamer.Stop();
std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2;
}
if(bAuditResolvedLayoutPassages)
{
if(ResolvedLayout==nullptr||!CoreSnapshot.resolvedLayout())
{Streamer.Stop();std::fprintf(stderr,"[ResolvedPassageAudit] missing resolved-layout snapshot\n");return 2;}
struct FWalkAudit
{
std::uint64_t segments=0,centers=0,capsuleSamples=0,blocked=0;
std::uint64_t supportSamples=0,unsupported=0,gradeFailures=0;
std::array<std::array<double,4>,8> firstBlocked{};
std::array<std::array<double,4>,8> firstUnsupported{};
std::size_t firstBlockedCount=0,firstUnsupportedCount=0;
bool passed=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)
{
FWalkAudit audit;
if(path.size()<2)return audit;
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}},
{{-0.7071067811865475,0.7071067811865475}},{{-1.0,0.0}},
{{-0.7071067811865475,-0.7071067811865475}},{{0.0,-1.0}},
{{0.7071067811865475,-0.7071067811865475}}}};
const auto solidAt=[&](double xMetres,double yMetres,double zMetres)
{
++audit.capsuleSamples;
const VoxelForgeCore::Vec3d position{xMetres/coreMetres,yMetres/coreMetres,zMetres/coreMetres};
const float density=Field.densityAt(position);
if(density<0.0f)
{
if(audit.firstBlockedCount<audit.firstBlocked.size())
{
audit.firstBlocked[audit.firstBlockedCount++]={xMetres,yMetres,zMetres,density};
std::printf("[ResolvedWalkDispatch] kind=blocked xyz_m=(%.3f,%.3f,%.3f) path=%s\n",
xMetres,yMetres,zMetres,Field.densityDispatchPathAt(position).c_str());
}
++audit.blocked;return true;
}
return false;
};
for(std::size_t segment=1;segment<path.size();++segment)
{
++audit.segments;
const auto& a=path[segment-1];const auto& b=path[segment];
const double dx=b[0]-a[0],dy=b[1]-a[1],dz=b[2]-a[2];
const double horizontal=std::hypot(dx,dy)*coreMetres;
const double vertical=std::abs(dz)*coreMetres;
if(horizontal<=1.0e-6||vertical>horizontal*0.2679491924311227)
++audit.gradeFailures;
const double length=std::sqrt(dx*dx+dy*dy+dz*dz)*coreMetres;
const std::uint64_t intervals=std::max<std::uint64_t>(1,
static_cast<std::uint64_t>(std::ceil(length/0.5)));
for(std::uint64_t step=0;step<=intervals;++step)
{
if(segment>1&&step==0)continue;
const double t=static_cast<double>(step)/intervals;
const double x=(a[0]+dx*t)*coreMetres;
const double y=(a[1]+dy*t)*coreMetres;
const double z=(a[2]+dz*t)*coreMetres;
++audit.centers;
for(const double h:heights)
{
const double capOffset=std::max(0.0,std::abs(h)-0.54);
const double radial=std::abs(h)<=0.54?0.34
:std::sqrt(std::max(0.0,0.34*0.34-capOffset*capOffset));
solidAt(x,y,z+h);
for(const auto& direction:directions)
solidAt(x+direction[0]*radial,y+direction[1]*radial,z+h);
}
const double footZ=z-0.88;
const std::array<std::array<double,2>,5> supportOffsets{{
{{0.0,0.0}},{{supportProbeRadiusMetres,0.0}},
{{-supportProbeRadiusMetres,0.0}},
{{0.0,supportProbeRadiusMetres}},
{{0.0,-supportProbeRadiusMetres}}}};
std::uint32_t supportedProbes=0;
double firstUnsupportedX=x,firstUnsupportedY=y,lastUnsupportedDensity=0.0;
for(const auto& offset:supportOffsets)
{
const double supportX=x+offset[0],supportY=y+offset[1];
bool supported=false;
for(double depth=supportStartMetres;
depth<=supportMaximumMetres+1.0e-6;depth+=supportStepMetres)
{
++audit.supportSamples;
lastUnsupportedDensity=Field.densityAt({supportX/coreMetres,
supportY/coreMetres,(footZ-depth)/coreMetres});
if(lastUnsupportedDensity<0.0)
{
supported=true;break;
}
}
if(supported)++supportedProbes;
else{firstUnsupportedX=supportX;firstUnsupportedY=supportY;}
}
if(supportedProbes<minimumSupportProbes)
{
++audit.unsupported;
if(audit.firstUnsupportedCount<audit.firstUnsupported.size())
{
audit.firstUnsupported[audit.firstUnsupportedCount++]={
firstUnsupportedX,firstUnsupportedY,footZ,lastUnsupportedDensity};
std::printf("[ResolvedWalkDispatch] kind=unsupported xyz_m=(%.3f,%.3f,%.3f) support_probes=%u/5 path=%s\n",
firstUnsupportedX,firstUnsupportedY,footZ,supportedProbes,
Field.densityDispatchPathAt({firstUnsupportedX/coreMetres,
firstUnsupportedY/coreMetres,footZ/coreMetres}).c_str());
}
}
}
}
audit.passed=audit.centers>0&&audit.blocked==0&&audit.unsupported==0
&&audit.gradeFailures==0;
return audit;
};
const auto qToMetres=[&](std::int64_t q)
{return static_cast<double>(q)*World.CoreVoxelSizeMicrons
/(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel*1000000.0);};
const auto findStratum=[&](std::int32_t index)->const VoxelForgeCore::ResolvedLayoutStratumV2*
{
const auto it=std::find_if(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(),
[index](const auto& s){return s.strateIndex==index;});
return it==ResolvedLayout->strates.end()?nullptr:&*it;
};
const auto mouthInContent=[&](std::int32_t index,const std::array<std::int64_t,3>& mouth)
{
const auto* stratum=findStratum(index);if(!stratum)return false;
const std::int64_t zUm=static_cast<std::int64_t>(std::llround(
static_cast<double>(mouth[2])*World.CoreVoxelSizeMicrons
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
return zUm>=stratum->content.bottomMicrometres&&zUm<stratum->content.topMicrometres;
};
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0;
for(const auto& passage:ResolvedLayout->passages)
{
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 passageAudit=auditPlayerPath(Shape,path,0.125,0.125,0.25,3,0.45);
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)
<=passage.placementRadiusSubvoxels*passage.placementRadiusSubvoxels
&&radialSquared(passage.lowerMouthSubvoxels)
<=passage.placementRadiusSubvoxels*passage.placementRadiusSubvoxels;
const bool mouthsInContent=mouthInContent(passage.upperStrateIndex,passage.upperMouthSubvoxels)
&&mouthInContent(passage.lowerStrateIndex,passage.lowerMouthSubvoxels);
++passageCount;passagePasses+=passageAudit.passed&&mouthsInRadius&&mouthsInContent;
std::printf("[ResolvedPassageAudit] id=%s interface=%d-%d control_points=%zu center_samples=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu mouth_content=%d/2 mouth_radius=%d/2 walkability=%s\n",
passage.stableId.c_str(),passage.upperStrateIndex,passage.lowerStrateIndex,
passage.controlPoints.size(),static_cast<unsigned long long>(passageAudit.centers),
static_cast<unsigned long long>(passageAudit.capsuleSamples),
static_cast<unsigned long long>(passageAudit.blocked),
static_cast<unsigned long long>(passageAudit.supportSamples),
static_cast<unsigned long long>(passageAudit.unsupported),
static_cast<unsigned long long>(passageAudit.gradeFailures),mouthsInContent?2:0,
mouthsInRadius?2:0,passageAudit.passed?"PASS":"FAIL");
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]);
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]);
}
std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0;
struct FRouteAuditInput
{
std::int32_t strateIndex=0;
std::vector<std::array<double,3>> path;
double supportStart=0.125,supportStep=0.125,supportMaximum=0.25,
supportProbeRadius=0.45;
std::uint32_t minimumSupportProbes=3;
};
std::vector<FRouteAuditInput> routeAuditInputs;
for(const auto& stratum:ResolvedLayout->strates)
{
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> anchors;
for(const auto& anchor:stratum.anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)anchors.push_back(&anchor);
if(anchors.size()<2)continue;
std::vector<std::array<double,3>> route;route.reserve(anchors.size());
for(const auto* anchor:anchors)
{
route.push_back({qToMetres(anchor->values[0])/coreMetres,
qToMetres(anchor->values[1])/coreMetres,
qToMetres(anchor->values[2])/coreMetres});
++routePoints;
}
double supportStartMetres=0.125,supportStepMetres=0.125,
supportMaximumMetres=0.25,supportProbeRadiusMetres=0.45;
std::uint32_t minimumSupportProbes=3;
const VoxelForgeCore::WorldAssemblyRouteConstraintsV1* routeConstraints=nullptr;
if(Assembly)
{
const auto worldRoute=std::find_if(Assembly->plan.strates.begin(),
Assembly->plan.strates.end(),[&](const auto& candidate)
{return candidate.strateIndex==stratum.strateIndex;});
if(worldRoute!=Assembly->plan.strates.end()
&&worldRoute->routeConstraints.version==1)
routeConstraints=&worldRoute->routeConstraints;
const auto islandRoute=std::find_if(Assembly->floatingIslands.begin(),
Assembly->floatingIslands.end(),[&](const auto& candidate)
{return candidate.strateIndex==stratum.strateIndex;});
if(islandRoute!=Assembly->floatingIslands.end()
&&islandRoute->manifest.recipe.routeConstraints.version==1)
routeConstraints=&islandRoute->manifest.recipe.routeConstraints;
}
if(routeConstraints)
{
const double qMetres=coreMetres
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
minimumSupportProbes=std::clamp<std::uint32_t>(
routeConstraints->minimumSupportProbes,1,5);
supportProbeRadiusMetres=routeConstraints->supportProbeRadiusSubvoxels*qMetres;
if(stratum.generatorKind=="FloatingIslands")
{
supportStartMetres=(routeConstraints->minimumClearanceSubvoxels
+VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel/2)*qMetres;
supportStepMetres=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
*0.5*qMetres;
supportMaximumMetres=(routeConstraints->minimumClearanceSubvoxels
+routeConstraints->supportSearchDepthSubvoxels)*qMetres;
}
else
{
const std::int64_t spacing=std::min<std::int64_t>(
routeConstraints->sweepSpacingSubvoxels,
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel/4);
supportStepMetres=std::max<std::int64_t>(1,spacing)*qMetres;
supportStartMetres=supportStepMetres;
supportMaximumMetres=routeConstraints->supportSearchDepthSubvoxels*qMetres;
}
}
const FWalkAudit routeAudit=auditPlayerPath(Shape,route,supportStartMetres,
supportStepMetres,supportMaximumMetres,minimumSupportProbes,
supportProbeRadiusMetres);
routeAuditInputs.push_back({stratum.strateIndex,route,supportStartMetres,
supportStepMetres,supportMaximumMetres,supportProbeRadiusMetres,
minimumSupportProbes});
bool binding=true;
for(const auto& passage:ResolvedLayout->passages)
{
const auto equalPosition=[](const auto& a,const auto& b)
{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]}};
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]}};
binding&=equalPosition(passage.lowerMouthSubvoxels,
arrival);
}
}
++routePasses;routeWalkPasses+=routeAudit.passed;
routeBindings+=binding;routeBindingFailures+=!binding;
std::printf("[ResolvedStrateRouteAudit] strate=%d route_points=%zu center_samples=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu mouth_binding=%s status=%s\n",
stratum.strateIndex,anchors.size(),static_cast<unsigned long long>(routeAudit.centers),
static_cast<unsigned long long>(routeAudit.capsuleSamples),
static_cast<unsigned long long>(routeAudit.blocked),
static_cast<unsigned long long>(routeAudit.supportSamples),
static_cast<unsigned long long>(routeAudit.unsupported),
static_cast<unsigned long long>(routeAudit.gradeFailures),binding?"PASS":"FAIL",
routeAudit.passed&&binding?"PASS":"FAIL");
for(std::size_t i=0;i<routeAudit.firstBlockedCount;++i)
std::printf("[ResolvedStrateRouteAuditSample] strate=%d kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
stratum.strateIndex,routeAudit.firstBlocked[i][0],routeAudit.firstBlocked[i][1],
routeAudit.firstBlocked[i][2],routeAudit.firstBlocked[i][3]);
for(std::size_t i=0;i<routeAudit.firstUnsupportedCount;++i)
std::printf("[ResolvedStrateRouteAuditSample] strate=%d kind=unsupported xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
stratum.strateIndex,routeAudit.firstUnsupported[i][0],
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)
{
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 bool auditPassed=passageCount>0&&passagePasses==passageCount
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
&&routeBindingFailures==0
&&blockedCompiled&&blockedControls==passageCount;
std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu blocked_controls=%llu/%llu status=%s\n",
static_cast<unsigned long long>(passageCount),static_cast<unsigned long long>(passagePasses),
static_cast<unsigned long long>(routePasses),static_cast<unsigned long long>(routeWalkPasses),
static_cast<unsigned long long>(routePoints),
static_cast<unsigned long long>(routeBindings),static_cast<unsigned long long>(routeBindingFailures),
static_cast<unsigned long long>(blockedControls),static_cast<unsigned long long>(passageCount),
auditPassed?"PASS":"FAIL");
if(!auditPassed)
{Streamer.Stop();return 10;}
}
if(bAuditResolvedLayoutBoundaries)
{
if(ResolvedLayout==nullptr)
+83 -21
View File
@@ -43,7 +43,9 @@ namespace StandaloneWorldAssemblyPrototype
inline bool BuildFiniteStrateRoute(VoxelForgeCore::WorldAssemblyRecipeV1& recipe,
VoxelForgeCore::WorldAssemblyStratumPlanV1& stratum,std::uint64_t& tested,
std::uint64_t& gradeRejected,std::uint64_t& sweepRejected,std::string& error)
std::uint64_t& gradeRejected,std::uint64_t& placementRejected,
std::uint64_t& sweepRejected,std::int64_t arrivalRadiusSubvoxels,
std::int64_t departureRadiusSubvoxels,std::string& error)
{
using namespace VoxelForgeCore;
constexpr std::int64_t PlayerRadiusQ=348; // 0.34 m in Core's 0.25 m voxel scale.
@@ -73,7 +75,9 @@ namespace StandaloneWorldAssemblyPrototype
constraints.sweepSpacingSubvoxels=StepQ;
constraints.supportProbeRadiusSubvoxels=ProbeRadiusQ;
constraints.supportSearchDepthSubvoxels=SupportDepthQ;
constraints.minimumClearanceSubvoxels=8;
// Match the FI route contract's half-voxel body clearance. Core's native
// field can differ slightly from the recipe sweep at the lower capsule cap.
constraints.minimumClearanceSubvoxels=128;
constraints.minimumSupportProbes=3;
const Heightfield2D finalField(recipe,stratum);
@@ -90,7 +94,7 @@ namespace StandaloneWorldAssemblyPrototype
candidate.centerX=centerX;candidate.centerY=centerY;candidate.direction=directionIndex;
candidate.points.reserve(33);
std::int64_t previousGround=0,previousX=0,previousY=0;
bool gradePass=true;
bool gradePass=true,placementPass=true;
for(std::int32_t index=0;index<=32;++index)
{
const std::int64_t along=(index-16)*waypointSpacing;
@@ -98,6 +102,13 @@ namespace StandaloneWorldAssemblyPrototype
static_cast<double>(along)*directions[directionIndex].x/65536.0);
const std::int64_t y=centerY+std::llround(
static_cast<double>(along)*directions[directionIndex].y/65536.0);
const std::int64_t xQ=WorldAssemblyFixed(static_cast<double>(x));
const std::int64_t yQ=WorldAssemblyFixed(static_cast<double>(y));
const auto inRadius=[](std::int64_t xValue,std::int64_t yValue,std::int64_t radius)
{return radius<=0||xValue*xValue+yValue*yValue<=radius*radius;};
if((index==0&&!inRadius(xQ,yQ,arrivalRadiusSubvoxels))
||(index==32&&!inRadius(xQ,yQ,departureRadiusSubvoxels)))
{placementPass=false;break;}
const auto column=finalField.point(WorldAssemblyFixed(static_cast<double>(x)),
WorldAssemblyFixed(static_cast<double>(y)),0);
const std::int64_t horizontalQ=static_cast<std::int64_t>(std::llround(
@@ -121,7 +132,8 @@ namespace StandaloneWorldAssemblyPrototype
previousGround=column.groundHeightSubvoxels;previousX=x;previousY=y;
}
++tested;
if(gradePass)candidates.push_back(std::move(candidate));
if(placementPass&&gradePass)candidates.push_back(std::move(candidate));
else if(!placementPass)++placementRejected;
else ++gradeRejected;
}
std::stable_sort(candidates.begin(),candidates.end(),[](const Candidate& a,const Candidate& b)
@@ -382,14 +394,33 @@ namespace StandaloneWorldAssemblyPrototype
stratum.features.push_back(shelf);
}
}
std::uint64_t routeCandidates=0,routeGradeRejected=0,routeSweepRejected=0;
std::int64_t arrivalRadiusQ=0,departureRadiusQ=0;
const auto ownerVoxelsToCoreQ=[&](double value)
{return static_cast<std::int64_t>(std::llround(value*owner.OwnerVoxelSizeMicrons
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel/owner.CoreVoxelSizeMicrons));};
for(const FStrateSlot& slot:owner.Manager->GetLayout())
{
if(!slot.Definition)continue;
if(slot.StrateIndex+1==stratum.strateIndex)
arrivalRadiusQ=std::max(ownerVoxelsToCoreQ(slot.Definition->PassageConfig.DistanceMin),
ownerVoxelsToCoreQ(slot.Definition->PassageConfig.DistanceMax));
if(slot.StrateIndex==stratum.strateIndex+1&&definition)
departureRadiusQ=std::max(ownerVoxelsToCoreQ(definition->PassageConfig.DistanceMin),
ownerVoxelsToCoreQ(definition->PassageConfig.DistanceMax));
}
std::uint64_t routeCandidates=0,routeGradeRejected=0,routePlacementRejected=0,routeSweepRejected=0;
if(!BuildFiniteStrateRoute(recipe,stratum,routeCandidates,routeGradeRejected,
routeSweepRejected,error))return false;
std::printf("[WorldAssemblerRoutePlan] candidates=%llu grade_rejected=%llu sweep_rejected=%llu accepted_points=%zu capsule_m=0.34x1.76 corridor_radius_vox=4 support_probe_points=5 minimum_support_probes=%u status=PASS\n",
routePlacementRejected,routeSweepRejected,arrivalRadiusQ,departureRadiusQ,error))return false;
std::printf("[WorldAssemblerRoutePlan] candidates=%llu grade_rejected=%llu placement_rejected=%llu sweep_rejected=%llu accepted_points=%zu capsule_m=0.34x1.76 corridor_radius_vox=4 support_probe_points=5 minimum_support_probes=%u arrival_radius_m=%.3f departure_radius_m=%.3f status=PASS\n",
static_cast<unsigned long long>(routeCandidates),
static_cast<unsigned long long>(routeGradeRejected),
static_cast<unsigned long long>(routePlacementRejected),
static_cast<unsigned long long>(routeSweepRejected),stratum.route.size(),
stratum.routeConstraints.minimumSupportProbes);
stratum.routeConstraints.minimumSupportProbes,
static_cast<double>(arrivalRadiusQ)*owner.CoreVoxelSizeMicrons
/(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel*1000000.0),
static_cast<double>(departureRadiusQ)*owner.CoreVoxelSizeMicrons
/(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel*1000000.0));
manifest.plan.strates.push_back(std::move(stratum));
if(!manifest.finalize(error))return false;
if(outManifest)*outManifest=manifest;
@@ -537,6 +568,18 @@ namespace StandaloneWorldAssemblyPrototype
auto planned=VoxelForgeCore::PlanFloatingIslandCluster(recipe,error);
if(!planned)return false;
const VoxelForgeCore::FloatingIslandFieldV1 routeField(
planned->recipe,planned->plan);
const auto routeAudit=VoxelForgeCore::AuditFloatingIslandRoute(routeField);
std::printf("[FloatingIslandRouteAudit] points=%zu centers=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu status=%s\n",
planned->plan.route.size(),
static_cast<unsigned long long>(routeAudit.centerSamples),
static_cast<unsigned long long>(routeAudit.capsuleFieldSamples),
static_cast<unsigned long long>(routeAudit.blockedCapsuleSamples),
static_cast<unsigned long long>(routeAudit.supportProbeSamples),
static_cast<unsigned long long>(routeAudit.unsupportedCenterSamples),
static_cast<unsigned long long>(routeAudit.gradeFailures),
routeAudit.passed?"PASS":"FAIL");
const double metresPerSubvoxel=static_cast<double>(owner.CoreVoxelSizeMicrons)
/(1000000.0*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double bedrockTopMetres=(minimumZ+recipe.boundarySealSubvoxels)*metresPerSubvoxel;
@@ -598,7 +641,7 @@ namespace StandaloneWorldAssemblyPrototype
inline bool BuildResolvedLayoutSnapshot(const FStandaloneOwnerWorld& owner,
const VoxelForgeCore::WorldAssemblyManifest& assembly,std::string_view recipeVariant,
VoxelForgeCore::ResolvedLayoutSnapshotV2& out,std::string& error)
VoxelForgeCore::ResolvedLayoutSnapshotV3& out,std::string& error)
{
using namespace VoxelForgeCore;
if(!owner.Manager){error="resolved layout builder requires the initialized director";return false;}
@@ -661,6 +704,15 @@ namespace StandaloneWorldAssemblyPrototype
anchor.values={a.centerSubvoxels[0],a.centerSubvoxels[1],a.centerSubvoxels[2],a.radiusSubvoxels,a.topHalfSubvoxels};
s.anchors.push_back(std::move(anchor));
}
for(std::size_t n=0;n<it->manifest.plan.route.size();++n)
{
const auto& p=it->manifest.plan.route[n];ResolvedLayoutAnchorV2 anchor;
anchor.kind="route_point";anchor.stableId="route/"+std::to_string(n);
anchor.units="1/256 Core voxel";
anchor.values={p.positionSubvoxels[0],p.positionSubvoxels[1],
p.positionSubvoxels[2],p.radiusSubvoxels};
s.anchors.push_back(std::move(anchor));
}
}
}
s.interiorBottomMicrometres=bottom;s.interiorTopMicrometres=top;
@@ -678,8 +730,8 @@ namespace StandaloneWorldAssemblyPrototype
s.managerLookup="UVoxelStrateManager selects the slot from the director-resolved layout chunk width";
s.managerChunkWidthMicrometres=owner.StrateLayoutChunkSizeMicrometres;
s.dispatchChunkWidthMicrometres=owner.StrateLayoutChunkSizeMicrometres;
s.boundDispatchPath="all generator plans use ResolvedLayoutSnapshotV2 layoutChunkWidthSubvoxels";
s.queryDispatchPath="all generator dispatch uses floor(CoreZ / ResolvedLayoutSnapshotV2 layoutChunkWidthSubvoxels)";
s.boundDispatchPath="all generator plans use ResolvedLayoutSnapshotV3 layoutChunkWidthSubvoxels";
s.queryDispatchPath="all generator dispatch uses floor(CoreZ / ResolvedLayoutSnapshotV3 layoutChunkWidthSubvoxels)";
const std::int64_t solidDensity=std::max<std::int64_t>(1,WorldAssemblyQ16(std::max(0.05f,authored[1])));
s.ceiling={"ceiling",top-seal,top,true,true,false,"solid ceiling from strate BoundarySealThickness; breakable by default",
"interior-facing lower face","shared resolved-layout density band",solidDensity};
@@ -701,18 +753,28 @@ namespace StandaloneWorldAssemblyPrototype
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));}
}
for(std::size_t n=0;n<owner.Manager->GetPassages().Num();++n)
{
const auto& passage=owner.Manager->GetPassages()[static_cast<int32>(n)];
if(passage.UpperStrateIndex!=slot.StrateIndex&&passage.LowerStrateIndex!=slot.StrateIndex)continue;
ResolvedLayoutAnchorV2 a;a.kind="passage";a.stableId="passage/"+std::to_string(n);a.units="milli owner voxels";
a.values={std::llround(passage.UpperPoint.X*1000.0),std::llround(passage.UpperPoint.Y*1000.0),
std::llround(passage.UpperPoint.Z*1000.0),std::llround(passage.LowerPoint.X*1000.0),
std::llround(passage.LowerPoint.Y*1000.0),std::llround(passage.LowerPoint.Z*1000.0)};
s.anchors.push_back(std::move(a));
}
out.strates.push_back(std::move(s));
}
std::vector<ResolvedLayoutPassageConfigV1> passageConfigs;
for(const FStrateSlot& slot:owner.Manager->GetLayout())
{
if(!slot.Definition)continue;
const auto& authored=slot.Definition->PassageConfig;
const auto ownerVoxelsToCoreQ=[&](double value)
{return static_cast<std::int64_t>(std::llround(value*owner.OwnerVoxelSizeMicrons
*WorldAssemblySubvoxelsPerVoxel/owner.CoreVoxelSizeMicrons));};
ResolvedLayoutPassageConfigV1 config;config.upperStrateIndex=slot.StrateIndex;
config.connections=authored.Connections;
config.mouthRadiusSubvoxels=ownerVoxelsToCoreQ(authored.MouthRadius);
config.midRadiusSubvoxels=ownerVoxelsToCoreQ(authored.MidRadius);
config.reachMinimumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMin);
config.reachMaximumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMax);
config.distanceMinimumSubvoxels=ownerVoxelsToCoreQ(authored.DistanceMin);
config.distanceMaximumSubvoxels=ownerVoxelsToCoreQ(authored.DistanceMax);
config.wanderSubvoxels=ownerVoxelsToCoreQ(authored.Wander);
passageConfigs.push_back(config);
}
if(!PlanResolvedLayoutPassagesV1(out,owner.Seed,passageConfigs,error))return false;
return out.finalize(error);
}