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:
1 parent
55c26a05a7
commit
71d09a8c52
14 files changed
+1465
-112
No files matched your search
@@ -1924,6 +1924,33 @@ controls continue to reject malformed bands.
|
|||||||
Evidence and owner caveats are in
|
Evidence and owner caveats are in
|
||||||
[`Standalone/Research/WorldAssemblerM3/Linux22/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux22/REPORT.md).
|
[`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)
|
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||||
|
|
||||||
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
||||||
|
|||||||
+12
-1
@@ -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. |
|
| `../../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. |
|
| `../../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` | 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. |
|
| `../../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.
|
| `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`. |
|
| 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
|
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).
|
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. The density pipeline (most-edited hot path)
|
||||||
|
|
||||||
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
### 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
|
// 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.
|
// 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/")
|
Layout.worldIdentity = std::string("owner-world/seed/")
|
||||||
+ std::to_string(StrateManager->GetWorldSeed());
|
+ std::to_string(StrateManager->GetWorldSeed());
|
||||||
Layout.ownerVoxelSizeMicrometres = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
|
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)
|
return static_cast<std::int64_t>(std::llround(static_cast<double>(Fixed)
|
||||||
* ExpectedCoreVoxelMicrons / VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
|
* ExpectedCoreVoxelMicrons / VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
|
||||||
};
|
};
|
||||||
|
std::vector<VoxelForgeCore::ResolvedLayoutPassageConfigV1> PassageConfigs;
|
||||||
for (const FStrateSlot& Slot : StrateManager->GetLayout())
|
for (const FStrateSlot& Slot : StrateManager->GetLayout())
|
||||||
{
|
{
|
||||||
if (Slot.Definition == nullptr) continue;
|
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(
|
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(
|
||||||
FIntVector(0, 0, Slot.TopChunkZ));
|
FIntVector(0, 0, Slot.TopChunkZ));
|
||||||
VoxelForgeCore::ResolvedLayoutStratumV2 Item;
|
VoxelForgeCore::ResolvedLayoutStratumV2 Item;
|
||||||
@@ -1065,8 +1083,8 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
|||||||
Item.managerLookup = "UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate";
|
Item.managerLookup = "UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate";
|
||||||
if (Type == ECaveGeneratorType::SurfaceWorld)
|
if (Type == ECaveGeneratorType::SurfaceWorld)
|
||||||
{
|
{
|
||||||
Item.boundDispatchPath = "BuildSurfaceStratumPlan uses 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 ResolvedLayoutSnapshotV2";
|
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(),
|
const auto Plan = std::find_if(Manifest->plan.strates.begin(), Manifest->plan.strates.end(),
|
||||||
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
|
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
|
||||||
if (Plan != Manifest->plan.strates.end())
|
if (Plan != Manifest->plan.strates.end())
|
||||||
@@ -1097,8 +1115,8 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
|||||||
}
|
}
|
||||||
else if (Type == ECaveGeneratorType::FloatingIslands)
|
else if (Type == ECaveGeneratorType::FloatingIslands)
|
||||||
{
|
{
|
||||||
Item.boundDispatchPath = "BuildFloatingIslandStratumPlan uses 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 ResolvedLayoutSnapshotV2";
|
Item.queryDispatchPath = "NativeFloatingIslandDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV3";
|
||||||
const auto Plan = std::find_if(Manifest->floatingIslands.begin(), Manifest->floatingIslands.end(),
|
const auto Plan = std::find_if(Manifest->floatingIslands.begin(), Manifest->floatingIslands.end(),
|
||||||
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
|
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
|
||||||
std::int64_t SealMicrometres = 0;
|
std::int64_t SealMicrometres = 0;
|
||||||
@@ -1117,11 +1135,21 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
|||||||
Island.centerSubvoxels[2], Island.radiusSubvoxels, Island.topHalfSubvoxels };
|
Island.centerSubvoxels[2], Island.radiusSubvoxels, Island.topHalfSubvoxels };
|
||||||
Item.anchors.push_back(std::move(Anchor));
|
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
|
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)";
|
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(
|
const std::int64_t SealMicrometres=static_cast<std::int64_t>(std::llround(
|
||||||
@@ -1142,23 +1170,16 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
|||||||
"none", "none" };
|
"none", "none" };
|
||||||
Item.content.bottomMicrometres+=SealMicrometres;
|
Item.content.bottomMicrometres+=SealMicrometres;
|
||||||
Item.content.topMicrometres-=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));
|
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;
|
std::string LayoutError;
|
||||||
if (!Layout.finalize(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.
|
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
|
||||||
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
||||||
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
|
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
|
||||||
// Version 7 binds plans to the physical strate-layout chunk width and shared boundary bands.
|
// Version 9 indexes passage landings so non-monotone route tails stay carveable.
|
||||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 7;
|
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 9;
|
||||||
|
|
||||||
enum class FieldChannel : std::uint32_t
|
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
|
// 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
|
// Core adapter. Coordinates are half-open micrometre intervals; recipe anchors retain their
|
||||||
// source fixed-point units so this record never rounds the recipe itself.
|
// source fixed-point units so this record never rounds the recipe itself.
|
||||||
inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV2 = 2;
|
inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV3 = 3;
|
||||||
enum class ResolvedLayoutValidationModeV2 : std::uint8_t { compatibility, strict };
|
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
|
||||||
|
|
||||||
struct ResolvedLayoutAnchorV2
|
struct ResolvedLayoutAnchorV2
|
||||||
{
|
{
|
||||||
@@ -635,34 +635,73 @@ namespace VoxelForgeCore
|
|||||||
std::string detail;
|
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::string worldIdentity;
|
||||||
std::uint32_t ownerVoxelSizeMicrometres = 0;
|
std::uint32_t ownerVoxelSizeMicrometres = 0;
|
||||||
std::uint32_t coreVoxelSizeMicrometres = 0;
|
std::uint32_t coreVoxelSizeMicrometres = 0;
|
||||||
std::uint32_t strateLayoutChunkSizeMicrometres = 8000000;
|
std::uint32_t strateLayoutChunkSizeMicrometres = 8000000;
|
||||||
std::int64_t strateLayoutChunkWidthSubvoxels = 8192;
|
std::int64_t strateLayoutChunkWidthSubvoxels = 8192;
|
||||||
std::vector<ResolvedLayoutStratumV2> strates;
|
std::vector<ResolvedLayoutStratumV2> strates;
|
||||||
|
std::vector<ResolvedLayoutPassageV1> passages;
|
||||||
std::string stableIdentity;
|
std::string stableIdentity;
|
||||||
|
|
||||||
bool finalize(std::string& error);
|
bool finalize(std::string& error);
|
||||||
bool validate(ResolvedLayoutValidationModeV2 mode,
|
bool validate(ResolvedLayoutValidationModeV3 mode,
|
||||||
std::vector<ResolvedLayoutIssueV2>& issues,
|
std::vector<ResolvedLayoutIssueV2>& issues,
|
||||||
std::string& error) const;
|
std::string& error) const;
|
||||||
std::string ToJson() const;
|
std::string ToJson() const;
|
||||||
static std::optional<ResolvedLayoutSnapshotV2> FromJson(
|
static std::optional<ResolvedLayoutSnapshotV3> FromJson(
|
||||||
std::string_view json, std::string& error);
|
std::string_view json, std::string& error);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ResolvedLayoutValidatorAuditV2
|
struct ResolvedLayoutValidatorAuditV3
|
||||||
{
|
{
|
||||||
std::uint32_t controlsRun = 0;
|
std::uint32_t controlsRun = 0;
|
||||||
std::uint32_t controlsRejectedAsExpected = 0;
|
std::uint32_t controlsRejectedAsExpected = 0;
|
||||||
bool passed = false;
|
bool passed = false;
|
||||||
std::vector<ResolvedLayoutIssueV2> failures;
|
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
|
struct FloatingIslandBoundsAuditResult
|
||||||
{
|
{
|
||||||
@@ -941,7 +980,8 @@ namespace VoxelForgeCore
|
|||||||
explicit operator bool() const noexcept { return static_cast<bool>(data_); }
|
explicit operator bool() const noexcept { return static_cast<bool>(data_); }
|
||||||
const WorldConfiguration& configuration() const;
|
const WorldConfiguration& configuration() const;
|
||||||
const WorldAssemblyManifest* worldAssembly() const noexcept;
|
const WorldAssemblyManifest* worldAssembly() const noexcept;
|
||||||
const ResolvedLayoutSnapshotV2* resolvedLayout() const noexcept;
|
const ResolvedLayoutSnapshotV3* resolvedLayout() const noexcept;
|
||||||
|
ResolvedWorldSnapshot withoutResolvedLayoutPassages() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Data;
|
struct Data;
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
#include "VoxelForgeCore/VoxelForgeCore.h"
|
#include "VoxelForgeCore/VoxelForgeCore.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
#include <limits>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace VoxelForgeCore
|
namespace VoxelForgeCore
|
||||||
@@ -66,7 +68,7 @@ namespace
|
|||||||
b.behaviour=value.Get("behaviour").String();
|
b.behaviour=value.Get("behaviour").String();
|
||||||
b.densitySide=value.Get("density_side").String();b.capPath=value.Get("cap_path").String();return b;
|
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
|
std::ostringstream out;out<<"{\"schema_version\":"<<s.schemaVersion
|
||||||
<<",\"world_identity\":"<<LayoutEscape(s.worldIdentity)
|
<<",\"world_identity\":"<<LayoutEscape(s.worldIdentity)
|
||||||
@@ -113,6 +115,29 @@ namespace
|
|||||||
}
|
}
|
||||||
out<<"]}";
|
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();
|
out<<"]}";return out.str();
|
||||||
}
|
}
|
||||||
std::int64_t LayoutInt(const VFJson::Value& obj,std::string_view key)
|
std::int64_t LayoutInt(const VFJson::Value& obj,std::string_view key)
|
||||||
@@ -121,19 +146,76 @@ namespace
|
|||||||
{return obj.Get(key).String();}
|
{return obj.Get(key).String();}
|
||||||
bool LayoutBool(const VFJson::Value& obj,std::string_view key,bool fallback=false)
|
bool LayoutBool(const VFJson::Value& obj,std::string_view key,bool fallback=false)
|
||||||
{return obj.Get(key).Boolean(fallback);}
|
{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);
|
const std::string body=LayoutBodyJson(*this);
|
||||||
return body.substr(0,body.size()-1)+",\"stable_identity\":"
|
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();
|
error.clear();
|
||||||
if(schemaVersion!=ResolvedLayoutSchemaVersionV2||worldIdentity.empty()||strates.empty()
|
if(schemaVersion!=ResolvedLayoutSchemaVersionV3||worldIdentity.empty()||strates.empty()
|
||||||
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
|
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
|
||||||
{error="resolved layout has an unsupported version, empty world identity, or no strates";return false;}
|
{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)
|
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)
|
if(x.interiorTopMicrometres<=x.interiorBottomMicrometres||x.voxelSizeMicrometres==0)
|
||||||
{error="resolved layout contains an invalid interior interval or voxel size";return false;}
|
{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.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;
|
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
|
std::vector<ResolvedLayoutIssueV2>& issues,std::string& error) const
|
||||||
{
|
{
|
||||||
issues.clear();error.clear();
|
issues.clear();error.clear();
|
||||||
if(schemaVersion!=ResolvedLayoutSchemaVersionV2||worldIdentity.empty()||strates.empty()
|
if(schemaVersion!=ResolvedLayoutSchemaVersionV3||worldIdentity.empty()||strates.empty()
|
||||||
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
|
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
|
||||||
{error="resolved layout schema/world/strata header is invalid";return false;}
|
{error="resolved layout schema/world/strata header is invalid";return false;}
|
||||||
const auto add=[&](std::string code,std::string detail)
|
const auto add=[&](std::string code,std::string detail)
|
||||||
{issues.push_back({std::move(code),std::move(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)
|
for(std::size_t i=0;i<strates.size();++i)
|
||||||
{
|
{
|
||||||
const auto& s=strates[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;}
|
if(strict&&!issues.empty()){error="strict resolved-layout validation rejected "+issues.front().code;return false;}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ResolvedLayoutSnapshotV2> ResolvedLayoutSnapshotV2::FromJson(
|
std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
||||||
std::string_view json,std::string& error)
|
std::string_view json,std::string& error)
|
||||||
{
|
{
|
||||||
VFJson::Value root;if(!VFJson::Parser(std::string(json)).Parse(root,error))return std::nullopt;
|
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;}
|
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.worldIdentity=LayoutString(root,"world_identity");
|
||||||
s.ownerVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"owner_voxel_size_um"));
|
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"));
|
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));}
|
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));
|
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");
|
const std::string serializedIdentity=LayoutString(root,"stable_identity");
|
||||||
if(!s.finalize(error))return std::nullopt;
|
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;}
|
if(s.stableIdentity!=serializedIdentity){error="resolved layout stable identity does not match its canonical payload";return std::nullopt;}
|
||||||
return s;
|
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)
|
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";
|
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"};
|
s.bedrock={"bedrock",bottom-1000000,bottom,true,true,false,"breakable bedrock","lower face","none"};
|
||||||
return s;
|
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;
|
++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"});
|
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;
|
{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"});}
|
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;
|
auto boundaryGap=base;boundaryGap.strates[1].ceiling.topMicrometres-=1;
|
||||||
expectReject("bedrock/next-ceiling gap",boundaryGap);
|
expectReject("bedrock/next-ceiling gap",boundaryGap);
|
||||||
auto missingBedrock=base;missingBedrock.strates[0].bedrock.presentInCurrentField=false;expectReject("missing bedrock",missingBedrock);
|
auto missingBedrock=base;missingBedrock.strates[0].bedrock.presentInCurrentField=false;expectReject("missing bedrock",missingBedrock);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
class FStandaloneShapeWorld
|
class FStandaloneShapeWorld
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
const VoxelForgeCore::ResolvedLayoutSnapshotV2* ResolvedLayout=nullptr;
|
const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr;
|
||||||
struct FResolvedStratumBounds
|
struct FResolvedStratumBounds
|
||||||
{
|
{
|
||||||
double BottomZ=0.0;
|
double BottomZ=0.0;
|
||||||
@@ -97,6 +97,170 @@ public:
|
|||||||
return -std::max(0.05f,static_cast<float>(band->solidDensityQ16)/65536.0f);
|
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,
|
static bool MakeResolvedStratumBounds(double BottomZ,double TopZ,double SealThickness,
|
||||||
FResolvedStratumBounds& Out)
|
FResolvedStratumBounds& Out)
|
||||||
{
|
{
|
||||||
@@ -410,7 +574,7 @@ public:
|
|||||||
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InGpuVisualDetailTiles={},
|
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InGpuVisualDetailTiles={},
|
||||||
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InCpuVisualDetailTiles={},
|
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InCpuVisualDetailTiles={},
|
||||||
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly=nullptr,
|
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly=nullptr,
|
||||||
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout=nullptr)
|
const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout=nullptr)
|
||||||
{
|
{
|
||||||
using Clock=std::chrono::steady_clock;
|
using Clock=std::chrono::steady_clock;
|
||||||
const auto TotalStart=Clock::now();
|
const auto TotalStart=Clock::now();
|
||||||
@@ -571,7 +735,7 @@ public:
|
|||||||
const FIntVector& ClipMaxVoxels,EDetailVariant Variant,
|
const FIntVector& ClipMaxVoxels,EDetailVariant Variant,
|
||||||
bool bInRoundTunnels,
|
bool bInRoundTunnels,
|
||||||
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
|
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
|
||||||
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout,
|
const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout,
|
||||||
std::string& Error)
|
std::string& Error)
|
||||||
{
|
{
|
||||||
using Clock=std::chrono::steady_clock;
|
using Clock=std::chrono::steady_clock;
|
||||||
@@ -611,7 +775,7 @@ public:
|
|||||||
|
|
||||||
void InitializeNativeSurfaceOnly(FStandaloneOwnerWorld& World,
|
void InitializeNativeSurfaceOnly(FStandaloneOwnerWorld& World,
|
||||||
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
|
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
|
||||||
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout,
|
const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout,
|
||||||
const FIntVector& InMinimum,const FIntVector& InMaximum)
|
const FIntVector& InMinimum,const FIntVector& InMaximum)
|
||||||
{
|
{
|
||||||
Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout;
|
Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout;
|
||||||
@@ -622,7 +786,7 @@ public:
|
|||||||
|
|
||||||
bool BuildShapeDependencyProof(FStandaloneOwnerWorld& World,
|
bool BuildShapeDependencyProof(FStandaloneOwnerWorld& World,
|
||||||
const FIntVector& MinVoxels,const FIntVector& MaxVoxels,bool bInRoundTunnels,
|
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;
|
Owner=&World;ResolvedLayout=InResolvedLayout;WorkerCount=1;RoundTunnelsEnabled=bInRoundTunnels;
|
||||||
bAnalyticOnly=true;bNativeOnly=false;Stats={};
|
bAnalyticOnly=true;bNativeOnly=false;Stats={};
|
||||||
@@ -1129,19 +1293,19 @@ public:
|
|||||||
float NativeDensity=0.0f;
|
float NativeDensity=0.0f;
|
||||||
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||||
{RecordNativeOnlyViolation();return 0.0f;}
|
{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;
|
float IslandDensity=0.0f;
|
||||||
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
||||||
{
|
{
|
||||||
++ThreadClassificationStats().NativeFloatFieldCalls;
|
++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;
|
float NativeDensity=0.0f;
|
||||||
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||||
{
|
{
|
||||||
++ThreadClassificationStats().NativeFloatFieldCalls;
|
++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))
|
if(UsesLegacySurfaceField(X,Y,Z))
|
||||||
return LegacySurfaceDensityAt(X,Y,Z);
|
return LegacySurfaceDensityAt(X,Y,Z);
|
||||||
@@ -1191,21 +1355,21 @@ public:
|
|||||||
if(!NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
if(!NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
||||||
static_cast<float>(Z),NativeDensity))
|
static_cast<float>(Z),NativeDensity))
|
||||||
{RecordNativeOnlyViolation();return 0.0f;}
|
{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;
|
float IslandDensity=0.0f;
|
||||||
if(NativeFloatingIslandDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
if(NativeFloatingIslandDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
||||||
static_cast<float>(Z),IslandDensity))
|
static_cast<float>(Z),IslandDensity))
|
||||||
{
|
{
|
||||||
++ThreadClassificationStats().NativeLatticeFieldCalls;
|
++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;
|
float NativeDensity=0.0f;
|
||||||
if(NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
if(NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
||||||
static_cast<float>(Z),NativeDensity))
|
static_cast<float>(Z),NativeDensity))
|
||||||
{
|
{
|
||||||
++ThreadClassificationStats().NativeLatticeFieldCalls;
|
++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),
|
if(UsesLegacySurfaceField(static_cast<float>(X),static_cast<float>(Y),
|
||||||
static_cast<float>(Z)))
|
static_cast<float>(Z)))
|
||||||
@@ -1229,19 +1393,19 @@ public:
|
|||||||
float NativeDensity=0.0f;
|
float NativeDensity=0.0f;
|
||||||
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||||
{RecordNativeOnlyViolation();return 0.0f;}
|
{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;
|
float IslandDensity=0.0f;
|
||||||
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
||||||
{
|
{
|
||||||
++ThreadClassificationStats().NativeUnclampedFieldCalls;
|
++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;
|
float NativeDensity=0.0f;
|
||||||
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||||
{
|
{
|
||||||
++ThreadClassificationStats().NativeUnclampedFieldCalls;
|
++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))
|
if(UsesLegacySurfaceField(X,Y,Z))
|
||||||
return LegacySurfaceDensityAt(X,Y,Z);
|
return LegacySurfaceDensityAt(X,Y,Z);
|
||||||
@@ -1371,13 +1535,13 @@ private:
|
|||||||
float Density=static_cast<float>(Proxy)*(1.0f/256.0f);
|
float Density=static_cast<float>(Proxy)*(1.0f/256.0f);
|
||||||
if((Density<0.0f)!=bRock)
|
if((Density<0.0f)!=bRock)
|
||||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/256.0f);
|
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
|
// Match the fixed fallback to the edge of the stored proxy band. The
|
||||||
// displacement is tapered to zero there, so this avoids a magnitude
|
// displacement is tapered to zero there, so this avoids a magnitude
|
||||||
// jump along the candidate bit-mask boundary.
|
// jump along the candidate bit-mask boundary.
|
||||||
constexpr float ProxyBandEdge=DetailSupremum+0.125f;
|
constexpr float ProxyBandEdge=DetailSupremum+0.125f;
|
||||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(
|
return ApplyEditBrushes(ApplyResolvedStrateComposition(
|
||||||
bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ),FX,FY,FZ);
|
bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ),FX,FY,FZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1395,7 +1559,7 @@ private:
|
|||||||
float IslandDensity=0.0f;
|
float IslandDensity=0.0f;
|
||||||
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
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;
|
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||||
}
|
}
|
||||||
if(UsesLegacySurfaceField(X,Y,Z))
|
if(UsesLegacySurfaceField(X,Y,Z))
|
||||||
@@ -1403,11 +1567,11 @@ private:
|
|||||||
float NativeDensity=0.0f;
|
float NativeDensity=0.0f;
|
||||||
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
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;
|
return bApplyEdits?ApplyEditBrushes(NativeDensity,X,Y,Z):NativeDensity;
|
||||||
}
|
}
|
||||||
const float Density=Owner->Generator->GetDensityAt(X,Y,Z);
|
const float Density=Owner->Generator->GetDensityAt(X,Y,Z);
|
||||||
const float composed=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
|
const float composed=ApplyResolvedStrateComposition(Density,X,Y,Z);
|
||||||
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||||
}
|
}
|
||||||
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]
|
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]
|
||||||
@@ -1422,7 +1586,7 @@ private:
|
|||||||
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
||||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
||||||
bClampNearZero?0.05f:1.0e-7f);
|
bClampNearZero?0.05f:1.0e-7f);
|
||||||
const float composed=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
|
const float composed=ApplyResolvedStrateComposition(Density,X,Y,Z);
|
||||||
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||||
}
|
}
|
||||||
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
|
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
|
||||||
@@ -1452,7 +1616,7 @@ private:
|
|||||||
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
||||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
||||||
bClampNearZero?0.05f:1.0e-7f);
|
bClampNearZero?0.05f:1.0e-7f);
|
||||||
Density=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
|
Density=ApplyResolvedStrateComposition(Density,X,Y,Z);
|
||||||
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
|
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1590,6 +1754,8 @@ public:
|
|||||||
else ++Counts.MixedBoxes;
|
else ++Counts.MixedBoxes;
|
||||||
return Result;
|
return Result;
|
||||||
};
|
};
|
||||||
|
if(ResolvedPassageIntersectsBox(BMin,BMax))
|
||||||
|
return Finish(EVoxelTileClass::Mixed,true);
|
||||||
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
|
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
|
||||||
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
|
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
|
||||||
&&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2])
|
&&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
|
// 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.
|
// 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.
|
// 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);
|
return ApplyEditBrushes(density,X,Y,Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace VoxelForgeCore
|
|||||||
{
|
{
|
||||||
std::shared_ptr<FStandaloneOwnerWorld> owner;
|
std::shared_ptr<FStandaloneOwnerWorld> owner;
|
||||||
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
|
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
|
||||||
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
|
std::shared_ptr<const ResolvedLayoutSnapshotV3> resolvedLayout;
|
||||||
WorldConfiguration configuration;
|
WorldConfiguration configuration;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -869,7 +869,7 @@ namespace VoxelForgeCore
|
|||||||
{
|
{
|
||||||
static ResolvedWorldSnapshot FromOwner(std::shared_ptr<FStandaloneOwnerWorld> owner,
|
static ResolvedWorldSnapshot FromOwner(std::shared_ptr<FStandaloneOwnerWorld> owner,
|
||||||
std::shared_ptr<const WorldAssemblyManifest> worldAssembly = {},
|
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 {};
|
if(!owner||!owner->Settings||!owner->Manager||!owner->Generator)return {};
|
||||||
auto data=std::make_shared<ResolvedWorldSnapshot::Data>();
|
auto data=std::make_shared<ResolvedWorldSnapshot::Data>();
|
||||||
@@ -894,15 +894,15 @@ namespace VoxelForgeCore
|
|||||||
if(!parsed)return {};
|
if(!parsed)return {};
|
||||||
worldAssembly=std::make_shared<WorldAssemblyManifest>(std::move(*parsed));
|
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())
|
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 {};
|
if(!parsed)return {};
|
||||||
std::vector<ResolvedLayoutIssueV2> issues;std::string strictError;
|
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 {};}
|
{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;
|
const bool seedChanged=owner->Seed!=seed;
|
||||||
@@ -943,24 +943,36 @@ namespace VoxelForgeCore
|
|||||||
if(!parsed)return std::nullopt;
|
if(!parsed)return std::nullopt;
|
||||||
worldAssembly=std::make_shared<WorldAssemblyManifest>(std::move(*parsed));
|
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())
|
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;
|
if(!parsed)return std::nullopt;
|
||||||
std::vector<ResolvedLayoutIssueV2> issues;std::string strictError;
|
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;}
|
{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));
|
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly),std::move(resolvedLayout));
|
||||||
}
|
}
|
||||||
|
|
||||||
const WorldAssemblyManifest* ResolvedWorldSnapshot::worldAssembly() const noexcept
|
const WorldAssemblyManifest* ResolvedWorldSnapshot::worldAssembly() const noexcept
|
||||||
{ return data_?data_->worldAssembly.get():nullptr; }
|
{ return data_?data_->worldAssembly.get():nullptr; }
|
||||||
const ResolvedLayoutSnapshotV2* ResolvedWorldSnapshot::resolvedLayout() const noexcept
|
const ResolvedLayoutSnapshotV3* ResolvedWorldSnapshot::resolvedLayout() const noexcept
|
||||||
{ return data_?data_->resolvedLayout.get():nullptr; }
|
{ 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
|
const WorldConfiguration& ResolvedWorldSnapshot::configuration() const
|
||||||
{
|
{
|
||||||
static const WorldConfiguration empty{};
|
static const WorldConfiguration empty{};
|
||||||
@@ -971,7 +983,7 @@ namespace VoxelForgeCore
|
|||||||
{
|
{
|
||||||
std::shared_ptr<FStandaloneOwnerWorld> owner;
|
std::shared_ptr<FStandaloneOwnerWorld> owner;
|
||||||
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
|
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
|
||||||
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
|
std::shared_ptr<const ResolvedLayoutSnapshotV3> resolvedLayout;
|
||||||
WorldConfiguration configuration;
|
WorldConfiguration configuration;
|
||||||
std::unique_ptr<FStandaloneShapeWorld> shape;
|
std::unique_ptr<FStandaloneShapeWorld> shape;
|
||||||
std::atomic<bool> shapeReady{false};
|
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;
|
std::filesystem::path ResolvedLayoutOutputPath;
|
||||||
bool ResolvedLayoutOnly=false;
|
bool ResolvedLayoutOnly=false;
|
||||||
bool AuditResolvedLayoutBoundaries=false;
|
bool AuditResolvedLayoutBoundaries=false;
|
||||||
|
bool AuditResolvedLayoutPassages=false;
|
||||||
bool AuditSurfaceClearance=false;
|
bool AuditSurfaceClearance=false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,6 +126,7 @@ namespace
|
|||||||
" --resolved-layout-output=FILE (canonical plan-time layout snapshot)\n"
|
" --resolved-layout-output=FILE (canonical plan-time layout snapshot)\n"
|
||||||
" --resolved-layout-only (exit after plan and snapshot validation)\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-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"
|
" --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"
|
" --auto-surface-camera (place the 27-tile cube on this seed's surface)\n"
|
||||||
" --mesh-coverage (report non-empty tiles and indexed triangles)\n"
|
" --mesh-coverage (report non-empty tiles and indexed triangles)\n"
|
||||||
@@ -145,6 +147,7 @@ namespace
|
|||||||
if(arg=="--world-assembler-prototype")options.WorldAssemblerPrototype=true;
|
if(arg=="--world-assembler-prototype")options.WorldAssemblerPrototype=true;
|
||||||
else if(arg=="--resolved-layout-only")options.ResolvedLayoutOnly=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-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=="--audit-surface-clearance")options.AuditSurfaceClearance=true;
|
||||||
else if(arg=="--stream-benchmark")options.StreamBenchmark=true;
|
else if(arg=="--stream-benchmark")options.StreamBenchmark=true;
|
||||||
else if(arg=="--stream-pipeline")options.StreamPipeline=true;
|
else if(arg=="--stream-pipeline")options.StreamPipeline=true;
|
||||||
@@ -332,14 +335,14 @@ int main(int argc,char** argv)
|
|||||||
&&!StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly(
|
&&!StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly(
|
||||||
*ownerWorld,ownerJsonRoot,worldAssembly,error))
|
*ownerWorld,ownerJsonRoot,worldAssembly,error))
|
||||||
{std::fprintf(stderr,"Floating-island assembly setup failed: %s\n",error.c_str());return 2;}
|
{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";
|
const std::string_view variant=options.FloatingIslandsRecipe?"floating-islands":"surface-world";
|
||||||
if(!StandaloneWorldAssemblyPrototype::BuildResolvedLayoutSnapshot(
|
if(!StandaloneWorldAssemblyPrototype::BuildResolvedLayoutSnapshot(
|
||||||
*ownerWorld,worldAssembly,variant,resolvedLayout,error))
|
*ownerWorld,worldAssembly,variant,resolvedLayout,error))
|
||||||
{std::fprintf(stderr,"Resolved-layout snapshot failed: %s\n",error.c_str());return 2;}
|
{std::fprintf(stderr,"Resolved-layout snapshot failed: %s\n",error.c_str());return 2;}
|
||||||
const std::string layoutJson=resolvedLayout.ToJson();
|
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(!roundTrip||roundTrip->ToJson()!=layoutJson||roundTrip->stableIdentity!=resolvedLayout.stableIdentity)
|
||||||
{if(error.empty())error="resolved-layout canonical serialization did not round-trip";
|
{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;}
|
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()));
|
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;}
|
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::vector<VoxelForgeCore::ResolvedLayoutIssueV2> compatibilityIssues;
|
||||||
std::string compatibilityError;
|
std::string compatibilityError;
|
||||||
const bool compatibilityAccepted=resolvedLayout.validate(
|
const bool compatibilityAccepted=resolvedLayout.validate(
|
||||||
VoxelForgeCore::ResolvedLayoutValidationModeV2::compatibility,
|
VoxelForgeCore::ResolvedLayoutValidationModeV3::compatibility,
|
||||||
compatibilityIssues,compatibilityError);
|
compatibilityIssues,compatibilityError);
|
||||||
std::vector<VoxelForgeCore::ResolvedLayoutIssueV2> strictIssues;
|
std::vector<VoxelForgeCore::ResolvedLayoutIssueV2> strictIssues;
|
||||||
std::string strictError;
|
std::string strictError;
|
||||||
const bool strictAccepted=resolvedLayout.validate(
|
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",
|
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());
|
resolvedLayout.schemaVersion,resolvedLayout.stableIdentity.c_str(),resolvedLayout.strates.size(),layoutJson.size());
|
||||||
std::printf("[ResolvedLayoutCompatibility] accepted=%s issues=%zu error=%s\n",
|
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.RegionCrossing,options.AuditTileShapeReadiness,
|
||||||
options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence,
|
options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence,
|
||||||
options.AuditRouteParity,options.AuditResolvedLayoutBoundaries,
|
options.AuditRouteParity,options.AuditResolvedLayoutBoundaries,
|
||||||
|
options.AuditResolvedLayoutPassages,
|
||||||
&resolvedLayout);
|
&resolvedLayout);
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
bool bAuditTileShapeReadiness=false,int DiagnosticSlowShapeCompileMs=0,
|
bool bAuditTileShapeReadiness=false,int DiagnosticSlowShapeCompileMs=0,
|
||||||
bool bAuditBasePageEquivalence=false,bool bAuditRouteParity=false,
|
bool bAuditBasePageEquivalence=false,bool bAuditRouteParity=false,
|
||||||
bool bAuditResolvedLayoutBoundaries=false,
|
bool bAuditResolvedLayoutBoundaries=false,
|
||||||
const VoxelForgeCore::ResolvedLayoutSnapshotV2* ResolvedLayout=nullptr)
|
bool bAuditResolvedLayoutPassages=false,
|
||||||
|
const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr)
|
||||||
{
|
{
|
||||||
using Clock=std::chrono::steady_clock;
|
using Clock=std::chrono::steady_clock;
|
||||||
FStandaloneOwnerWorld& World=*OwnerWorld;
|
FStandaloneOwnerWorld& World=*OwnerWorld;
|
||||||
@@ -440,6 +441,349 @@ namespace StandaloneStreamBenchmark
|
|||||||
Streamer.Stop();
|
Streamer.Stop();
|
||||||
std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2;
|
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(bAuditResolvedLayoutBoundaries)
|
||||||
{
|
{
|
||||||
if(ResolvedLayout==nullptr)
|
if(ResolvedLayout==nullptr)
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
|
|
||||||
inline bool BuildFiniteStrateRoute(VoxelForgeCore::WorldAssemblyRecipeV1& recipe,
|
inline bool BuildFiniteStrateRoute(VoxelForgeCore::WorldAssemblyRecipeV1& recipe,
|
||||||
VoxelForgeCore::WorldAssemblyStratumPlanV1& stratum,std::uint64_t& tested,
|
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;
|
using namespace VoxelForgeCore;
|
||||||
constexpr std::int64_t PlayerRadiusQ=348; // 0.34 m in Core's 0.25 m voxel scale.
|
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.sweepSpacingSubvoxels=StepQ;
|
||||||
constraints.supportProbeRadiusSubvoxels=ProbeRadiusQ;
|
constraints.supportProbeRadiusSubvoxels=ProbeRadiusQ;
|
||||||
constraints.supportSearchDepthSubvoxels=SupportDepthQ;
|
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;
|
constraints.minimumSupportProbes=3;
|
||||||
|
|
||||||
const Heightfield2D finalField(recipe,stratum);
|
const Heightfield2D finalField(recipe,stratum);
|
||||||
@@ -90,7 +94,7 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
candidate.centerX=centerX;candidate.centerY=centerY;candidate.direction=directionIndex;
|
candidate.centerX=centerX;candidate.centerY=centerY;candidate.direction=directionIndex;
|
||||||
candidate.points.reserve(33);
|
candidate.points.reserve(33);
|
||||||
std::int64_t previousGround=0,previousX=0,previousY=0;
|
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)
|
for(std::int32_t index=0;index<=32;++index)
|
||||||
{
|
{
|
||||||
const std::int64_t along=(index-16)*waypointSpacing;
|
const std::int64_t along=(index-16)*waypointSpacing;
|
||||||
@@ -98,6 +102,13 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
static_cast<double>(along)*directions[directionIndex].x/65536.0);
|
static_cast<double>(along)*directions[directionIndex].x/65536.0);
|
||||||
const std::int64_t y=centerY+std::llround(
|
const std::int64_t y=centerY+std::llround(
|
||||||
static_cast<double>(along)*directions[directionIndex].y/65536.0);
|
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)),
|
const auto column=finalField.point(WorldAssemblyFixed(static_cast<double>(x)),
|
||||||
WorldAssemblyFixed(static_cast<double>(y)),0);
|
WorldAssemblyFixed(static_cast<double>(y)),0);
|
||||||
const std::int64_t horizontalQ=static_cast<std::int64_t>(std::llround(
|
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;
|
previousGround=column.groundHeightSubvoxels;previousX=x;previousY=y;
|
||||||
}
|
}
|
||||||
++tested;
|
++tested;
|
||||||
if(gradePass)candidates.push_back(std::move(candidate));
|
if(placementPass&&gradePass)candidates.push_back(std::move(candidate));
|
||||||
|
else if(!placementPass)++placementRejected;
|
||||||
else ++gradeRejected;
|
else ++gradeRejected;
|
||||||
}
|
}
|
||||||
std::stable_sort(candidates.begin(),candidates.end(),[](const Candidate& a,const Candidate& b)
|
std::stable_sort(candidates.begin(),candidates.end(),[](const Candidate& a,const Candidate& b)
|
||||||
@@ -382,14 +394,33 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
stratum.features.push_back(shelf);
|
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,
|
if(!BuildFiniteStrateRoute(recipe,stratum,routeCandidates,routeGradeRejected,
|
||||||
routeSweepRejected,error))return false;
|
routePlacementRejected,routeSweepRejected,arrivalRadiusQ,departureRadiusQ,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",
|
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>(routeCandidates),
|
||||||
static_cast<unsigned long long>(routeGradeRejected),
|
static_cast<unsigned long long>(routeGradeRejected),
|
||||||
|
static_cast<unsigned long long>(routePlacementRejected),
|
||||||
static_cast<unsigned long long>(routeSweepRejected),stratum.route.size(),
|
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));
|
manifest.plan.strates.push_back(std::move(stratum));
|
||||||
if(!manifest.finalize(error))return false;
|
if(!manifest.finalize(error))return false;
|
||||||
if(outManifest)*outManifest=manifest;
|
if(outManifest)*outManifest=manifest;
|
||||||
@@ -537,6 +568,18 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
|
|
||||||
auto planned=VoxelForgeCore::PlanFloatingIslandCluster(recipe,error);
|
auto planned=VoxelForgeCore::PlanFloatingIslandCluster(recipe,error);
|
||||||
if(!planned)return false;
|
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)
|
const double metresPerSubvoxel=static_cast<double>(owner.CoreVoxelSizeMicrons)
|
||||||
/(1000000.0*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
/(1000000.0*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||||
const double bedrockTopMetres=(minimumZ+recipe.boundarySealSubvoxels)*metresPerSubvoxel;
|
const double bedrockTopMetres=(minimumZ+recipe.boundarySealSubvoxels)*metresPerSubvoxel;
|
||||||
@@ -598,7 +641,7 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
|
|
||||||
inline bool BuildResolvedLayoutSnapshot(const FStandaloneOwnerWorld& owner,
|
inline bool BuildResolvedLayoutSnapshot(const FStandaloneOwnerWorld& owner,
|
||||||
const VoxelForgeCore::WorldAssemblyManifest& assembly,std::string_view recipeVariant,
|
const VoxelForgeCore::WorldAssemblyManifest& assembly,std::string_view recipeVariant,
|
||||||
VoxelForgeCore::ResolvedLayoutSnapshotV2& out,std::string& error)
|
VoxelForgeCore::ResolvedLayoutSnapshotV3& out,std::string& error)
|
||||||
{
|
{
|
||||||
using namespace VoxelForgeCore;
|
using namespace VoxelForgeCore;
|
||||||
if(!owner.Manager){error="resolved layout builder requires the initialized director";return false;}
|
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};
|
anchor.values={a.centerSubvoxels[0],a.centerSubvoxels[1],a.centerSubvoxels[2],a.radiusSubvoxels,a.topHalfSubvoxels};
|
||||||
s.anchors.push_back(std::move(anchor));
|
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;
|
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.managerLookup="UVoxelStrateManager selects the slot from the director-resolved layout chunk width";
|
||||||
s.managerChunkWidthMicrometres=owner.StrateLayoutChunkSizeMicrometres;
|
s.managerChunkWidthMicrometres=owner.StrateLayoutChunkSizeMicrometres;
|
||||||
s.dispatchChunkWidthMicrometres=owner.StrateLayoutChunkSizeMicrometres;
|
s.dispatchChunkWidthMicrometres=owner.StrateLayoutChunkSizeMicrometres;
|
||||||
s.boundDispatchPath="all generator plans use ResolvedLayoutSnapshotV2 layoutChunkWidthSubvoxels";
|
s.boundDispatchPath="all generator plans use ResolvedLayoutSnapshotV3 layoutChunkWidthSubvoxels";
|
||||||
s.queryDispatchPath="all generator dispatch uses floor(CoreZ / ResolvedLayoutSnapshotV2 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])));
|
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",
|
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};
|
"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};
|
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));}
|
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));
|
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);
|
return out.finalize(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user