wip(assembler): round L21 -- resolved strate layout snapshot + validator (no world change)

Core ResolvedLayoutSnapshotV1 (canonical JSON, stable identity): per strate stable ID, generator,
recipe, interior bounds in metres, ceiling/bedrock bands as Core really applies them, transition
mode/width, dispatch provenance. Validator: compat mode reports today's deviations; strict mode
enforces ceiling -> content -> bedrock -> next ceiling (owner rule), two separate bands, nominal
contiguity, interpenetration only within the declared EVoxelStrateTransition width; 7 negative
controls rejected. Findings: SW built at -120..-632 m although the params say -60 m; a 60 m unowned
gap filled by analytic FI fallback; no active bedrock bands; transitions ignored by Core.
All L19 hashes unchanged (28 + 14 crossing runs). The Unreal adapter exports resolved_layout
(uncompiled here).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-20 10:22:00 +02:00
1 parent 5d5447dc32
commit 8753f6dbe4
12 files changed
+1347 -3

No files matched your search

+30
View File
@@ -1869,6 +1869,36 @@ query, stratum position, scale, or asynchronous route changes. The strict LOD0
audit and the matched before/after captures are in audit and the matched before/after captures are in
[`Standalone/Research/WorldAssemblerM3/Linux19/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux19/REPORT.md). [`Standalone/Research/WorldAssemblerM3/Linux19/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux19/REPORT.md).
### 8.39 Resolved layout snapshots and nominal strate bands — World Assembler L21
`ResolvedLayoutSnapshotV1` is a separate Core-owned record built from the resolved manager slots and
the native SurfaceWorld/FloatingIslands plans. It has a canonical JSON representation, schema version,
and stable FNV-1a identity. Its physical intervals use half-open micrometre coordinates; recipe anchors
retain their serialized fixed-point units. The Unreal adapter exports it beside `world_assembly`, so the
layout digest never salts a recipe, manifest, density, or mesh hash. The standalone exporter follows
the same builder after plan resolution. It runs once per plan request and is not read from density or
tile-worker loops.
Each record separates the nominal ceiling, content, and bedrock bands. A strict layout requires that
sequence for every strate and requires bedrock(N) to touch the next strate's ceiling(N+1). The two
bands remain separately owned. Gradient and Interleaved interfaces may declare interpenetration only
inside their explicit transition zone; Hard has zero effective width. The manager's `TransitionBlendChunks`
is expressed in owner-chunk metres. Interleaved's existing two-chunk domain warp extends its possible
fingers beyond the blend width and is recorded separately. Compatibility validation reports current
gaps, overlaps, ambiguities, inactive/missing bands, and unhonoured transitions while accepting them;
strict validation rejects them. Core dispatches per-strate recipe fields without applying that
parameter blend, even though the legacy manager helper implements it. Both band types default diggable,
with protection represented explicitly.
Compatibility facts from L21: native SurfaceWorld plan/query bounds use 32 Core voxels (8 m) per
dispatch chunk, while FloatingIslands converts the 32-voxel owner chunk through its 12.5/25 cm scale
(16 Core voxels, 4 m). In the prototype where the top slot is FloatingIslands, this leaves a nominal
60 m interval between the FI bottom (−60 m) and SurfaceWorld top (−120 m); the Core selectors fall
through to the compiled analytic FloatingIslands fallback there. Core does not apply the manager's
parameter blend at either recipe boundary. The FI v3 upper seal remains a ceiling inside its current
content range, and its missing lower bedrock is recorded without changing evaluation. See
[`Standalone/Research/WorldAssemblerM3/Linux21/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux21/REPORT.md).
## 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
+2 -1
View File
@@ -163,13 +163,14 @@ 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` | `ResolvedLayoutSnapshotV1`: 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 from resolved plans; no evaluator call sites consume it. §8.39. |
| `../../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`. |
| `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). | | `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). |
| `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; schema-v1 owner-asset export check and BuildHost Core actor tests. `-coreworldassembly=PATH` supplies a reviewed manifest transiently to `-coreworldtest=1`; it does not save a data asset. `FStandaloneCoreAdapterScheduler` runs Core work at 1/8/16 workers; `VerifyStandaloneCoreAdapter` checks hashes and walkability. Existing bounded render/`VF_` walk/OBJ orchestration and research options remain editor/commandlet-only and are not linked by Game/Shipping. | | `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; schema-v1 owner-asset export check and BuildHost Core actor tests. `-coreworldassembly=PATH` supplies a reviewed manifest transiently to `-coreworldtest=1`; it does not save a data asset. `FStandaloneCoreAdapterScheduler` runs Core work at 1/8/16 workers; `VerifyStandaloneCoreAdapter` checks hashes and walkability. Existing bounded render/`VF_` walk/OBJ orchestration and research options remain editor/commandlet-only and are not linked by Game/Shipping. |
| `Source/VoxelForge/Public/VoxelSettings.h` / `Private/VoxelSettings.cpp` | `UVoxelSettings` owns optional canonical world-assembly JSON and its Core digest. The editor file picker plus `ImportWorldAssemblyManifestJson` imports the reviewed manifest into the cooked settings asset and validates the recipe seed against the effective settings seed. | | `Source/VoxelForge/Public/VoxelSettings.h` / `Private/VoxelSettings.cpp` | `UVoxelSettings` owns optional canonical world-assembly JSON and its Core digest. The editor file picker plus `ImportWorldAssemblyManifestJson` imports the reviewed manifest into the cooked settings asset and validates the recipe seed against the effective settings seed. |
| `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` | Validates the optional manifest against each resolved `SurfaceWorld` slot (unique strate index and exact top/bottom chunks), then places canonical `world_assembly` JSON and its hash in the Core snapshot. Invalid plans fail Core initialization. | | `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` | Validates the optional manifest against each resolved `SurfaceWorld` slot (unique strate index and exact top/bottom chunks), then exports canonical `resolved_layout` separately from `world_assembly` and its hash. The layout digest does not alter recipe hashes. Invalid plans fail Core initialization. |
| `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Six-worker lattice drivers for step 2c through step 2f. The original path still compares live `GetDensityAt` with the editor chunk program. Research modes assign contiguous batches dynamically so surface-heavy work balances across workers; steps 2e and 2f use 4³ proof bricks and stage each evaluated brick before copying contiguous X rows, while step 2d retains 8³. Each skip requires a macro Lipschitz bound plus the complete final-detail supremum; uncertain bricks retain every 12.5 cm detail sample. Timings split classification, point evaluation, and uniform fill; step 2f compares all profiles and hashes workers 1 and 6. | | `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Six-worker lattice drivers for step 2c through step 2f. The original path still compares live `GetDensityAt` with the editor chunk program. Research modes assign contiguous batches dynamically so surface-heavy work balances across workers; steps 2e and 2f use 4³ proof bricks and stage each evaluated brick before copying contiguous X rows, while step 2d retains 8³. Each skip requires a macro Lipschitz bound plus the complete final-detail supremum; uncertain bricks retain every 12.5 cm detail sample. Timings split classification, point evaluation, and uniform fill; step 2f compares all profiles and hashes workers 1 and 6. |
| `Public/VoxelRedefinedDensityField.h` / `Private/VoxelRedefinedDensityField.cpp` | Two default-off `WITH_EDITOR` research fields, selected by `voxel.ResearchRedefinedDensityField` (step 2d) and `voxel.ResearchParameterizedCave` (steps 2e/2f). Step 2f retains a metre-scaled 2e baseline for its rejection audit. New profiles use strongly jittered, individually rotated elliptical rooms capped against nearest neighbors and curved cubic-Bezier passages with deterministic centerline and width variation along each edge. Room heights and feature rolls vary by cell; ledges, occasional pillars, stalactites/stalagmites, basin floors, and bounded hanging masses remain features. Point and brick detail sampling share independently rotated 96/48/24-sample bands and a smooth ridge response, preserving 12/6/3 m wavelengths at 12.5 cm per sample. The parameterized mesher evaluates the exact field through prepared 4³ bricks and the same sound skip proof in commandlet and editor-game meshing. In-editor and in-game profile changes invalidate and rebuild all loaded tiles. In an editor game with this field enabled, sealed-solid, out-of-layout, and outer-classifier verdicts are declined without changing their console switches. | | `Public/VoxelRedefinedDensityField.h` / `Private/VoxelRedefinedDensityField.cpp` | Two default-off `WITH_EDITOR` research fields, selected by `voxel.ResearchRedefinedDensityField` (step 2d) and `voxel.ResearchParameterizedCave` (steps 2e/2f). Step 2f retains a metre-scaled 2e baseline for its rejection audit. New profiles use strongly jittered, individually rotated elliptical rooms capped against nearest neighbors and curved cubic-Bezier passages with deterministic centerline and width variation along each edge. Room heights and feature rolls vary by cell; ledges, occasional pillars, stalactites/stalagmites, basin floors, and bounded hanging masses remain features. Point and brick detail sampling share independently rotated 96/48/24-sample bands and a smooth ridge response, preserving 12/6/3 m wavelengths at 12.5 cm per sample. The parameterized mesher evaluates the exact field through prepared 4³ bricks and the same sound skip proof in commandlet and editor-game meshing. In-editor and in-game profile changes invalidate and rebuild all loaded tiles. In an editor game with this field enabled, sealed-solid, out-of-layout, and outer-classifier verdicts are declined without changing their console switches. |
| `Private/Tests/VoxelForgeParameterizedGenerationTest.cpp` | Separate three-seed functional sweep for the step 2f parameterized field; passage landing air/floor brackets and arrival-to-exit walking routes are checked without changing the live-world tests. | | `Private/Tests/VoxelForgeParameterizedGenerationTest.cpp` | Separate three-seed functional sweep for the step 2f parameterized field; passage landing air/floor brackets and arrival-to-exit walking routes are checked without changing the live-world tests. |
@@ -944,6 +944,199 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
MatchedStrateIndices.Add(Plan.strateIndex); MatchedStrateIndices.Add(Plan.strateIndex);
} }
// Keep the inspectable physical-layout export separate from the manifest identity: it is
// assembled from the same resolved slots/plans, but adding it must not salt evaluation hashes.
VoxelForgeCore::ResolvedLayoutSnapshotV1 Layout;
Layout.worldIdentity = std::string("owner-world/seed/")
+ std::to_string(StrateManager->GetWorldSeed());
Layout.ownerVoxelSizeMicrometres = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
Layout.coreVoxelSizeMicrometres = ExpectedCoreVoxelMicrons;
const auto generatorName = [](ECaveGeneratorType Type) -> const char*
{
switch (Type)
{
case ECaveGeneratorType::TunnelNetwork: return "TunnelNetwork";
case ECaveGeneratorType::FlatPlain: return "FlatPlain";
case ECaveGeneratorType::CrystalChamber: return "CrystalChamber";
case ECaveGeneratorType::Maze: return "Maze";
case ECaveGeneratorType::SurfaceWorld: return "SurfaceWorld";
case ECaveGeneratorType::VerticalShafts: return "VerticalShafts";
case ECaveGeneratorType::FloatingIslands: return "FloatingIslands";
case ECaveGeneratorType::Underwater: return "Underwater";
}
return "Unknown";
};
const auto transitionName = [](EVoxelStrateTransition Type) -> const char*
{
switch (Type)
{
case EVoxelStrateTransition::Gradient: return "Gradient";
case EVoxelStrateTransition::Hard: return "Hard";
case EVoxelStrateTransition::Interleaved: return "Interleaved";
}
return "Unknown";
};
const std::int64_t OwnerVoxelMicrons = Layout.ownerVoxelSizeMicrometres;
const auto FixedToMicrons = [ExpectedCoreVoxelMicrons](std::int64_t Fixed)
{
return static_cast<std::int64_t>(std::llround(static_cast<double>(Fixed)
* ExpectedCoreVoxelMicrons / VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
};
for (const FStrateSlot& Slot : StrateManager->GetLayout())
{
if (Slot.Definition == nullptr) continue;
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ));
VoxelForgeCore::ResolvedLayoutStratumV1 Item;
Item.strateIndex = Slot.StrateIndex;
Item.generatorKind = generatorName(Type);
Item.stableId = Layout.worldIdentity + "/strate/" + std::to_string(Slot.StrateIndex)
+ "/generator/" + generatorName(Type);
Item.recipeId = Type == ECaveGeneratorType::SurfaceWorld ? "SurfaceWorld.Heightfield2D"
: Type == ECaveGeneratorType::FloatingIslands ? "FloatingIslands.ClusterField"
: std::string("legacy.") + generatorName(Type);
Item.recipeVersion = Type == ECaveGeneratorType::SurfaceWorld ? Manifest->recipe.schemaVersion : 0;
Item.voxelSizeMicrometres = ExpectedCoreVoxelMicrons;
Item.interiorBottomMicrometres = static_cast<std::int64_t>(Slot.BottomChunkZ)
* CHUNK_SIZE * ExpectedCoreVoxelMicrons;
Item.interiorTopMicrometres = static_cast<std::int64_t>(Slot.TopChunkZ + 1)
* CHUNK_SIZE * ExpectedCoreVoxelMicrons;
Item.managerChunkWidthMicrometres = CHUNK_SIZE * OwnerVoxelMicrons;
Item.dispatchChunkWidthMicrometres = Type == ECaveGeneratorType::FloatingIslands
? CHUNK_SIZE * OwnerVoxelMicrons : CHUNK_SIZE * ExpectedCoreVoxelMicrons;
Item.transitionMode = transitionName(Slot.Definition->TransitionType);
Item.transitionBlendChunks = Slot.Definition->TransitionBlendChunks;
Item.transitionWidthMicrometres = static_cast<std::int64_t>(Item.transitionBlendChunks)
* Item.managerChunkWidthMicrometres;
Item.transitionWarpAmplitudeMicrometres = Item.transitionMode == "Interleaved"
? 2 * Item.managerChunkWidthMicrometres : 0;
Item.transitionInterpenetrationMicrometres = 0;
Item.transitionHonouredInCore = false;
Item.transitionDispatchPath = "upper strate owns lower interface; manager GetGenerationParams can blend legacy vectors, but Core's compiled field selects per-strate/native recipe fields without transition blending";
Item.managerLookup = "UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate";
if (Type == ECaveGeneratorType::SurfaceWorld)
{
Item.boundDispatchPath = "BuildSurfaceStratumPlan uses slot chunkZ * 32 Core voxels";
Item.queryDispatchPath = "NativeSurfaceDensityAt uses floor(CoreZ / 32), then GetGeneratorTypeForChunk/GetStrateIndex";
const auto Plan = std::find_if(Manifest->plan.strates.begin(), Manifest->plan.strates.end(),
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
if (Plan != Manifest->plan.strates.end())
{
for (const auto& Profile : Manifest->recipe.biomes)
if (Profile.hasCapProfile)
{
VoxelForgeCore::ResolvedLayoutAnchorV1 Anchor;
Anchor.kind = "cap_profile";
Anchor.stableId = "biome/" + std::to_string(Profile.stableId);
Anchor.units = "1/256 Core voxel";
Anchor.values = { Profile.capHeightSubvoxels, Profile.capUndulationSubvoxels,
Profile.capWavelengthSubvoxels, Profile.capDetailAmplitudeSubvoxels,
Profile.capRidgeAmplitudeSubvoxels };
Item.anchors.push_back(std::move(Anchor));
}
for (std::size_t RouteIndex = 0; RouteIndex < Plan->route.size(); ++RouteIndex)
{
const auto& Point = Plan->route[RouteIndex];
VoxelForgeCore::ResolvedLayoutAnchorV1 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));
}
}
Item.ceiling = { "ceiling", Item.interiorTopMicrometres, Item.interiorTopMicrometres,
false, true, false, "no separate volumetric boundary band; cap profile remains content",
"none", "Heightfield2D cap + AppendStratumBoundaryCeilings fallback mesh" };
Item.bedrock = { "bedrock", Item.interiorBottomMicrometres,
Item.interiorBottomMicrometres, false, true, false,
"no lower bedrock band", "none", "none" };
}
else if (Type == ECaveGeneratorType::FloatingIslands)
{
Item.boundDispatchPath = "BuildFloatingIslandStratumPlan uses slot chunkZ * 32 * owner/Core voxel ratio";
Item.queryDispatchPath = "NativeFloatingIslandDensityAt uses floor(CoreZ / (32 * owner/Core ratio)), then manager lookup";
const auto Plan = std::find_if(Manifest->floatingIslands.begin(), Manifest->floatingIslands.end(),
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
std::int64_t SealMicrometres = 0;
if (Plan != Manifest->floatingIslands.end())
{
Item.interiorBottomMicrometres = FixedToMicrons(Plan->manifest.recipe.layoutMinimumZSubvoxels);
Item.interiorTopMicrometres = FixedToMicrons(Plan->manifest.recipe.layoutMaximumZSubvoxels);
SealMicrometres = FixedToMicrons(Plan->manifest.recipe.boundarySealSubvoxels);
Item.recipeVersion = Plan->manifest.recipe.geometryVersion;
for (std::size_t IslandIndex = 0; IslandIndex < Plan->manifest.plan.islands.size(); ++IslandIndex)
{
const auto& Island = Plan->manifest.plan.islands[IslandIndex];
VoxelForgeCore::ResolvedLayoutAnchorV1 Anchor;
Anchor.kind = "island_anchor"; Anchor.stableId = "island/" + std::to_string(IslandIndex);
Anchor.units = "1/256 Core voxel";
Anchor.values = { Island.centerSubvoxels[0], Island.centerSubvoxels[1],
Island.centerSubvoxels[2], Island.radiusSubvoxels, Island.topHalfSubvoxels };
Item.anchors.push_back(std::move(Anchor));
}
}
Item.ceiling = { "ceiling", Item.interiorTopMicrometres - SealMicrometres,
Item.interiorTopMicrometres, SealMicrometres > 0, true, false,
"v3 shared upper boundary seal; one-sided and inset from upper edge",
"upper/interior side", "VF_ApplyUpperBoundarySeal" };
Item.bedrock = { "bedrock", Item.interiorBottomMicrometres,
Item.interiorBottomMicrometres, false, true, false,
"no lower seal in FloatingIsland geometryVersion 3", "none", "none" };
}
else
{
Item.boundDispatchPath = "resolved manager slot scaled by Core chunk width";
Item.queryDispatchPath = "legacy Core route; manager chunk selected by floor(CoreZ / 32)";
const std::int64_t SealMicrometres = static_cast<std::int64_t>(std::llround(
std::max(0.0f, Slot.Definition->GenerationParams.BoundarySealThickness)
* OwnerVoxelMicrons));
Item.ceiling = { "ceiling", Item.interiorTopMicrometres,
Item.interiorTopMicrometres + SealMicrometres, false, true, false,
"manager configures a two-sided legacy seal, but Core's compiled analytic field does not apply this density band",
"configured upper side", "legacy VF_ApplyBoundarySeal; absent from current Core density path" };
Item.bedrock = { "bedrock", Item.interiorBottomMicrometres - SealMicrometres,
Item.interiorBottomMicrometres, false, true, false,
"manager configures a two-sided legacy seal, but Core's compiled analytic field does not apply this density band; breakable by default",
"configured lower side", "legacy VF_ApplyBoundarySeal; absent from current Core density path" };
}
Item.content = { "content", Item.interiorBottomMicrometres,
Item.interiorTopMicrometres, true, true, false, "resolved generator interior",
"none", "none" };
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::ResolvedLayoutAnchorV1 Anchor;
Anchor.kind = "passage"; Anchor.stableId = "passage/" + std::to_string(PassageIndex);
Anchor.units = "milli owner voxels";
Anchor.values = { static_cast<std::int64_t>(std::llround(Passage.UpperPoint.X * 1000.0)),
static_cast<std::int64_t>(std::llround(Passage.UpperPoint.Y * 1000.0)),
static_cast<std::int64_t>(std::llround(Passage.UpperPoint.Z * 1000.0)),
static_cast<std::int64_t>(std::llround(Passage.LowerPoint.X * 1000.0)),
static_cast<std::int64_t>(std::llround(Passage.LowerPoint.Y * 1000.0)),
static_cast<std::int64_t>(std::llround(Passage.LowerPoint.Z * 1000.0)) };
Item.anchors.push_back(std::move(Anchor));
}
Layout.strates.push_back(std::move(Item));
}
std::string LayoutError;
if (!Layout.finalize(LayoutError))
{
OutError = FString::Printf(TEXT("Core resolved-layout export failed: %s"), UTF8_TO_TCHAR(LayoutError.c_str()));
return false;
}
const std::string LayoutJson = Layout.ToJson();
const FString LayoutText = UTF8_TO_TCHAR(LayoutJson.c_str());
TSharedPtr<FJsonObject> LayoutObject;
const TSharedRef<TJsonReader<>> LayoutReader = TJsonReaderFactory<>::Create(LayoutText);
if (!FJsonSerializer::Deserialize(LayoutReader, LayoutObject) || !LayoutObject.IsValid())
{
OutError = TEXT("Core resolved-layout canonical JSON could not be embedded in the resolved snapshot.");
return false;
}
Root->SetObjectField(TEXT("resolved_layout"), LayoutObject.ToSharedRef());
const std::string CanonicalJson = Manifest->ToJson(); const std::string CanonicalJson = Manifest->ToJson();
if (CanonicalJson.empty()) if (CanonicalJson.empty())
{ {
@@ -563,6 +563,96 @@ namespace VoxelForgeCore
std::string ToJson() const; std::string ToJson() const;
}; };
// Read-only, versioned account of the physical layout resolved by the director and the
// Core adapter. Coordinates are half-open micrometre intervals; recipe anchors retain their
// source fixed-point units so this record never rounds the recipe itself.
inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV1 = 1;
enum class ResolvedLayoutValidationModeV1 : std::uint8_t { compatibility, strict };
struct ResolvedLayoutAnchorV1
{
std::string kind;
std::string stableId;
std::string units;
std::vector<std::int64_t> values;
};
struct ResolvedLayoutBandV1
{
std::string kind; // ceiling, content, bedrock
std::int64_t bottomMicrometres = 0;
std::int64_t topMicrometres = 0;
bool presentInCurrentField = false;
bool diggable = true;
bool protectedBand = false;
std::string behaviour;
std::string densitySide;
std::string capPath;
};
struct ResolvedLayoutStratumV1
{
std::string stableId;
std::int32_t order = 0;
std::int32_t strateIndex = -1;
std::string generatorKind;
std::string recipeId;
std::uint32_t recipeVersion = 0;
std::int64_t interiorBottomMicrometres = 0;
std::int64_t interiorTopMicrometres = 0;
std::uint32_t voxelSizeMicrometres = 0;
std::array<std::int64_t, 3> latticeOriginMicrometres{};
ResolvedLayoutBandV1 ceiling;
ResolvedLayoutBandV1 content;
ResolvedLayoutBandV1 bedrock;
std::string transitionMode; // mode at this strate's lower interface
std::int32_t transitionBlendChunks = 0;
std::int64_t transitionWidthMicrometres = 0;
std::int64_t transitionWarpAmplitudeMicrometres = 0;
std::int64_t transitionInterpenetrationMicrometres = 0;
bool transitionHonouredInCore = false;
std::string transitionDispatchPath;
std::string boundDispatchPath;
std::string queryDispatchPath;
std::string managerLookup;
std::int64_t managerChunkWidthMicrometres = 0;
std::int64_t dispatchChunkWidthMicrometres = 0;
std::vector<ResolvedLayoutAnchorV1> anchors;
};
struct ResolvedLayoutIssueV1
{
std::string code;
std::string detail;
};
struct VFCORE_API ResolvedLayoutSnapshotV1
{
std::uint32_t schemaVersion = ResolvedLayoutSchemaVersionV1;
std::string worldIdentity;
std::uint32_t ownerVoxelSizeMicrometres = 0;
std::uint32_t coreVoxelSizeMicrometres = 0;
std::vector<ResolvedLayoutStratumV1> strates;
std::string stableIdentity;
bool finalize(std::string& error);
bool validate(ResolvedLayoutValidationModeV1 mode,
std::vector<ResolvedLayoutIssueV1>& issues,
std::string& error) const;
std::string ToJson() const;
static std::optional<ResolvedLayoutSnapshotV1> FromJson(
std::string_view json, std::string& error);
};
struct ResolvedLayoutValidatorAuditV1
{
std::uint32_t controlsRun = 0;
std::uint32_t controlsRejectedAsExpected = 0;
bool passed = false;
std::vector<ResolvedLayoutIssueV1> failures;
};
VFCORE_API ResolvedLayoutValidatorAuditV1 AuditResolvedLayoutValidatorV1();
struct FloatingIslandBoundsAuditResult struct FloatingIslandBoundsAuditResult
{ {
std::uint64_t boxesTested = 0; std::uint64_t boxesTested = 0;
@@ -1154,6 +1244,8 @@ namespace VoxelForgeCore
ShapeBuildStats buildStats() const; ShapeBuildStats buildStats() const;
float densityAt(Vec3d voxelPosition) const; float densityAt(Vec3d voxelPosition) const;
// Audit-only selector provenance for the same point evaluator; never used by meshing.
std::string densityDispatchPathAt(Vec3d voxelPosition) const;
// Analytic source distance without the occupancy-preserving near-zero clamp or packed proxy. // Analytic source distance without the occupancy-preserving near-zero clamp or packed proxy.
float unclampedDensityAt(Vec3d voxelPosition) const; float unclampedDensityAt(Vec3d voxelPosition) const;
DetailFieldSample detailSampleAt(Vec3d voxelPosition) const; DetailFieldSample detailSampleAt(Vec3d voxelPosition) const;
@@ -0,0 +1,330 @@
#pragma once
#include "Json.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <algorithm>
#include <iomanip>
#include <sstream>
namespace VoxelForgeCore
{
namespace
{
std::string LayoutEscape(std::string_view value)
{
std::string out="\"";
for(unsigned char c:value)
{
switch(c)
{
case '"':out+="\\\"";break;
case '\\':out+="\\\\";break;
case '\b':out+="\\b";break;
case '\f':out+="\\f";break;
case '\n':out+="\\n";break;
case '\r':out+="\\r";break;
case '\t':out+="\\t";break;
default:if(c<0x20){char b[7];std::snprintf(b,sizeof(b),"\\u%04x",c);out+=b;}
else out+=static_cast<char>(c);
}
}
out+='"';return out;
}
std::uint64_t LayoutHash(std::string_view value)
{
std::uint64_t hash=14695981039346656037ull;
for(unsigned char c:value){hash^=c;hash*=1099511628211ull;}
return hash;
}
std::string LayoutHex(std::uint64_t value)
{std::ostringstream out;out<<std::hex<<std::setfill('0')<<std::setw(16)<<value;return out.str();}
std::int64_t LayoutTransitionLimit(const ResolvedLayoutStratumV1& stratum)
{
if(stratum.transitionMode=="Hard")return 0;
return stratum.transitionWidthMicrometres
+(stratum.transitionMode=="Interleaved"?stratum.transitionWarpAmplitudeMicrometres:0);
}
std::string LayoutBandJson(const ResolvedLayoutBandV1& band)
{
std::ostringstream out;out<<"{\"kind\":"<<LayoutEscape(band.kind)
<<",\"bottom_um\":"<<band.bottomMicrometres<<",\"top_um\":"<<band.topMicrometres
<<",\"present\":"<<(band.presentInCurrentField?"true":"false")
<<",\"diggable\":"<<(band.diggable?"true":"false")
<<",\"protected\":"<<(band.protectedBand?"true":"false")
<<",\"behaviour\":"<<LayoutEscape(band.behaviour)
<<",\"density_side\":"<<LayoutEscape(band.densitySide)
<<",\"cap_path\":"<<LayoutEscape(band.capPath)<<'}';return out.str();
}
ResolvedLayoutBandV1 ParseLayoutBand(const VFJson::Value& value)
{
ResolvedLayoutBandV1 b;b.kind=value.Get("kind").String();
b.bottomMicrometres=value.Get("bottom_um").Integer();b.topMicrometres=value.Get("top_um").Integer();
b.presentInCurrentField=value.Get("present").Boolean();b.diggable=value.Get("diggable").Boolean(true);
b.protectedBand=value.Get("protected").Boolean();b.behaviour=value.Get("behaviour").String();
b.densitySide=value.Get("density_side").String();b.capPath=value.Get("cap_path").String();return b;
}
std::string LayoutBodyJson(const ResolvedLayoutSnapshotV1& s)
{
std::ostringstream out;out<<"{\"schema_version\":"<<s.schemaVersion
<<",\"world_identity\":"<<LayoutEscape(s.worldIdentity)
<<",\"owner_voxel_size_um\":"<<s.ownerVoxelSizeMicrometres
<<",\"core_voxel_size_um\":"<<s.coreVoxelSizeMicrometres<<",\"strates\":[";
for(std::size_t i=0;i<s.strates.size();++i)
{
if(i)out<<',';const auto& x=s.strates[i];
out<<"{\"stable_id\":"<<LayoutEscape(x.stableId)<<",\"order\":"<<x.order
<<",\"strate_index\":"<<x.strateIndex<<",\"generator_kind\":"<<LayoutEscape(x.generatorKind)
<<",\"recipe_id\":"<<LayoutEscape(x.recipeId)<<",\"recipe_version\":"<<x.recipeVersion
<<",\"interior_bottom_um\":"<<x.interiorBottomMicrometres
<<",\"interior_top_um\":"<<x.interiorTopMicrometres
<<",\"voxel_size_um\":"<<x.voxelSizeMicrometres
<<",\"lattice_origin_um\":["<<x.latticeOriginMicrometres[0]<<','
<<x.latticeOriginMicrometres[1]<<','<<x.latticeOriginMicrometres[2]<<']'
<<",\"ceiling\":"<<LayoutBandJson(x.ceiling)
<<",\"content\":"<<LayoutBandJson(x.content)
<<",\"bedrock\":"<<LayoutBandJson(x.bedrock)
<<",\"transition_mode\":"<<LayoutEscape(x.transitionMode)
<<",\"transition_blend_chunks\":"<<x.transitionBlendChunks
<<",\"transition_width_um\":"<<x.transitionWidthMicrometres
<<",\"transition_warp_amplitude_um\":"<<x.transitionWarpAmplitudeMicrometres
<<",\"transition_interpenetration_um\":"<<x.transitionInterpenetrationMicrometres
<<",\"transition_honoured_in_core\":"<<(x.transitionHonouredInCore?"true":"false")
<<",\"transition_dispatch_path\":"<<LayoutEscape(x.transitionDispatchPath)
<<",\"bound_dispatch_path\":"<<LayoutEscape(x.boundDispatchPath)
<<",\"query_dispatch_path\":"<<LayoutEscape(x.queryDispatchPath)
<<",\"manager_lookup\":"<<LayoutEscape(x.managerLookup)
<<",\"manager_chunk_width_um\":"<<x.managerChunkWidthMicrometres
<<",\"dispatch_chunk_width_um\":"<<x.dispatchChunkWidthMicrometres
<<",\"anchors\":[";
for(std::size_t a=0;a<x.anchors.size();++a)
{
if(a)out<<',';const auto& anchor=x.anchors[a];
out<<"{\"kind\":"<<LayoutEscape(anchor.kind)<<",\"stable_id\":"
<<LayoutEscape(anchor.stableId)<<",\"units\":"<<LayoutEscape(anchor.units)<<",\"values\":[";
for(std::size_t v=0;v<anchor.values.size();++v){if(v)out<<',';out<<anchor.values[v];}
out<<"]}";
}
out<<"]}";
}
out<<"]}";return out.str();
}
std::int64_t LayoutInt(const VFJson::Value& obj,std::string_view key)
{return obj.Get(key).Integer();}
std::string LayoutString(const VFJson::Value& obj,std::string_view key)
{return obj.Get(key).String();}
bool LayoutBool(const VFJson::Value& obj,std::string_view key,bool fallback=false)
{return obj.Get(key).Boolean(fallback);}
}
std::string ResolvedLayoutSnapshotV1::ToJson() const
{
const std::string body=LayoutBodyJson(*this);
return body.substr(0,body.size()-1)+",\"stable_identity\":"
+LayoutEscape(stableIdentity.empty()?"resolved-layout-v1-fnv64:"+LayoutHex(LayoutHash(body)):stableIdentity)+"}";
}
bool ResolvedLayoutSnapshotV1::finalize(std::string& error)
{
error.clear();
if(schemaVersion!=ResolvedLayoutSchemaVersionV1||worldIdentity.empty()||strates.empty())
{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)
{return a.interiorTopMicrometres!=b.interiorTopMicrometres
?a.interiorTopMicrometres>b.interiorTopMicrometres:a.order<b.order;});
for(std::size_t i=0;i<strates.size();++i)
{
auto& x=strates[i];x.order=static_cast<std::int32_t>(i);
if(x.stableId.empty())x.stableId=worldIdentity+"/strate/"+std::to_string(x.strateIndex);
if(x.content.kind.empty())x.content.kind="content";
if(x.ceiling.kind.empty())x.ceiling.kind="ceiling";
if(x.bedrock.kind.empty())x.bedrock.kind="bedrock";
if(x.interiorTopMicrometres<=x.interiorBottomMicrometres||x.voxelSizeMicrometres==0)
{error="resolved layout contains an invalid interior interval or voxel size";return false;}
}
stableIdentity.clear();const std::string body=LayoutBodyJson(*this);
stableIdentity="resolved-layout-v1-fnv64:"+LayoutHex(LayoutHash(body));
std::vector<ResolvedLayoutIssueV1> issues;
return validate(ResolvedLayoutValidationModeV1::compatibility,issues,error);
}
bool ResolvedLayoutSnapshotV1::validate(ResolvedLayoutValidationModeV1 mode,
std::vector<ResolvedLayoutIssueV1>& issues,std::string& error) const
{
issues.clear();error.clear();
if(schemaVersion!=ResolvedLayoutSchemaVersionV1||worldIdentity.empty()||strates.empty())
{error="resolved layout schema/world/strata header is invalid";return false;}
const auto add=[&](std::string code,std::string detail)
{issues.push_back({std::move(code),std::move(detail)});};
const bool strict=mode==ResolvedLayoutValidationModeV1::strict;
for(std::size_t i=0;i<strates.size();++i)
{
const auto& s=strates[i];
const std::int64_t effectiveTransitionWidth=LayoutTransitionLimit(s);
if(s.interiorTopMicrometres<=s.interiorBottomMicrometres)
add("invalid_interior",s.stableId+" has non-positive content height");
if(s.transitionInterpenetrationMicrometres>effectiveTransitionWidth
||s.transitionInterpenetrationMicrometres<0)
add("transition_overlap_exceeds_width",s.stableId+" transition interpenetration exceeds its declared width");
if(s.managerChunkWidthMicrometres>0&&s.dispatchChunkWidthMicrometres>0
&&s.managerChunkWidthMicrometres!=s.dispatchChunkWidthMicrometres)
add("ambiguous_dispatch_units",s.stableId+" manager chunk width differs from Core dispatch chunk width");
if(i+1<strates.size()&&s.transitionMode!="Hard"&&!s.transitionHonouredInCore)
add("transition_not_honoured",s.stableId+" declares a blend/interleave that Core does not apply on this interface");
if(!s.ceiling.presentInCurrentField)add("missing_ceiling",s.stableId+" has no explicit ceiling band in today's field");
if(!s.bedrock.presentInCurrentField)add("missing_bedrock",s.stableId+" has no explicit bedrock floor in today's field");
if(strict)
{
if(s.order!=static_cast<std::int32_t>(i))add("wrong_order",s.stableId+" order is not top-to-bottom");
if(!s.ceiling.presentInCurrentField||!s.bedrock.presentInCurrentField)
add(!s.ceiling.presentInCurrentField?"missing_ceiling":"missing_bedrock",s.stableId+" lacks a required boundary band");
if(!s.content.presentInCurrentField)
add("missing_content",s.stableId+" has no content generator band");
const auto bandShape=[&](const ResolvedLayoutBandV1& b,std::string_view expected)
{
if(b.kind!=expected||b.topMicrometres<=b.bottomMicrometres)
add("wrong_order",s.stableId+" has an invalid "+std::string(expected)+" band");
if(b.protectedBand&&b.diggable)
add("invalid_protection",s.stableId+" band is both protected and diggable");
};
bandShape(s.ceiling,"ceiling");bandShape(s.content,"content");bandShape(s.bedrock,"bedrock");
if(s.ceiling.bottomMicrometres!=s.content.topMicrometres
||s.content.bottomMicrometres!=s.bedrock.topMicrometres)
add("wrong_order",s.stableId+" bands do not follow ceiling -> content -> bedrock");
if(i+1<strates.size()
&&s.bedrock.bottomMicrometres!=strates[i+1].ceiling.topMicrometres)
add(s.bedrock.bottomMicrometres>strates[i+1].ceiling.topMicrometres?"gap":"overlap",
s.stableId+" bedrock and next strate ceiling are not nominally contiguous");
if(s.transitionInterpenetrationMicrometres>effectiveTransitionWidth)
add("transition_overlap_exceeds_width",s.stableId+" interpenetration is wider than the declared transition zone");
}
}
if(!strict)
{
for(const auto& s:strates)
{
if(s.content.presentInCurrentField&&s.ceiling.presentInCurrentField)
{
if(s.ceiling.bottomMicrometres<s.content.topMicrometres)
add("band_overlap",s.stableId+" ceiling overlaps content by "+std::to_string(
s.content.topMicrometres-s.ceiling.bottomMicrometres)+" um");
else if(s.ceiling.bottomMicrometres>s.content.topMicrometres)
add("uncovered_interval",s.stableId+" has a "+std::to_string(
s.ceiling.bottomMicrometres-s.content.topMicrometres)+" um gap below its ceiling");
}
if(s.content.presentInCurrentField&&s.bedrock.presentInCurrentField)
{
if(s.bedrock.topMicrometres>s.content.bottomMicrometres)
add("band_overlap",s.stableId+" bedrock overlaps content by "+std::to_string(
s.bedrock.topMicrometres-s.content.bottomMicrometres)+" um");
else if(s.bedrock.topMicrometres<s.content.bottomMicrometres)
add("uncovered_interval",s.stableId+" has a "+std::to_string(
s.content.bottomMicrometres-s.bedrock.topMicrometres)+" um gap above its bedrock");
}
}
for(std::size_t i=0;i+1<strates.size();++i)
{
const auto& a=strates[i];const auto& b=strates[i+1];
const std::int64_t effectiveTransitionWidth=LayoutTransitionLimit(a);
if(a.interiorBottomMicrometres>b.interiorTopMicrometres)
add("uncovered_interval",a.stableId+" to "+b.stableId+" leaves "+std::to_string(
a.interiorBottomMicrometres-b.interiorTopMicrometres)+" um uncovered");
else if(a.interiorBottomMicrometres<b.interiorTopMicrometres)
{
const std::int64_t overlap=b.interiorTopMicrometres-a.interiorBottomMicrometres;
if(overlap>effectiveTransitionWidth)
add("overlap",a.stableId+" and "+b.stableId+" interiors overlap by "+std::to_string(overlap)+" um");
else add("transition_interpenetration",a.stableId+" and "+b.stableId+" overlap inside the declared transition zone");
}
if(a.bedrock.presentInCurrentField&&b.ceiling.presentInCurrentField)
{
if(a.bedrock.bottomMicrometres>b.ceiling.topMicrometres)
add("uncovered_interval",a.stableId+" bedrock and "+b.stableId+" ceiling leave "+std::to_string(
a.bedrock.bottomMicrometres-b.ceiling.topMicrometres)+" um uncovered");
else if(a.bedrock.bottomMicrometres<b.ceiling.topMicrometres)
{
const std::int64_t overlap=b.ceiling.topMicrometres-a.bedrock.bottomMicrometres;
if(overlap>effectiveTransitionWidth)
add("overlap",a.stableId+" bedrock and "+b.stableId+" ceiling overlap by "+std::to_string(overlap)+" um");
else add("transition_interpenetration",a.stableId+" bedrock and "+b.stableId+" ceiling interpenetrate by "+std::to_string(overlap)+" um inside the declared transition zone");
}
}
}
}
if(strict&&!issues.empty()){error="strict resolved-layout validation rejected "+issues.front().code;return false;}
return true;
}
std::optional<ResolvedLayoutSnapshotV1> ResolvedLayoutSnapshotV1::FromJson(
std::string_view json,std::string& error)
{
VFJson::Value root;if(!VFJson::Parser(std::string(json)).Parse(root,error))return std::nullopt;
if(!root.IsObject()){error="resolved layout JSON root must be an object";return std::nullopt;}
ResolvedLayoutSnapshotV1 s;s.schemaVersion=static_cast<std::uint32_t>(LayoutInt(root,"schema_version"));
s.worldIdentity=LayoutString(root,"world_identity");
s.ownerVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"owner_voxel_size_um"));
s.coreVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"core_voxel_size_um"));
for(const auto& v:root.Get("strates").AsArray())
{
ResolvedLayoutStratumV1 x;x.stableId=LayoutString(v,"stable_id");
x.order=static_cast<std::int32_t>(LayoutInt(v,"order"));x.strateIndex=static_cast<std::int32_t>(LayoutInt(v,"strate_index"));
x.generatorKind=LayoutString(v,"generator_kind");x.recipeId=LayoutString(v,"recipe_id");
x.recipeVersion=static_cast<std::uint32_t>(LayoutInt(v,"recipe_version"));
x.interiorBottomMicrometres=LayoutInt(v,"interior_bottom_um");x.interiorTopMicrometres=LayoutInt(v,"interior_top_um");
x.voxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(v,"voxel_size_um"));
const auto& origin=v.Get("lattice_origin_um").AsArray();for(std::size_t i=0;i<3&&i<origin.size();++i)x.latticeOriginMicrometres[i]=origin[i].Integer();
x.ceiling=ParseLayoutBand(v.Get("ceiling"));x.content=ParseLayoutBand(v.Get("content"));x.bedrock=ParseLayoutBand(v.Get("bedrock"));
x.transitionMode=LayoutString(v,"transition_mode");x.transitionBlendChunks=static_cast<std::int32_t>(LayoutInt(v,"transition_blend_chunks"));
x.transitionWidthMicrometres=LayoutInt(v,"transition_width_um");
x.transitionWarpAmplitudeMicrometres=LayoutInt(v,"transition_warp_amplitude_um");
x.transitionInterpenetrationMicrometres=LayoutInt(v,"transition_interpenetration_um");
x.transitionHonouredInCore=LayoutBool(v,"transition_honoured_in_core");x.transitionDispatchPath=LayoutString(v,"transition_dispatch_path");
x.boundDispatchPath=LayoutString(v,"bound_dispatch_path");x.queryDispatchPath=LayoutString(v,"query_dispatch_path");
x.managerLookup=LayoutString(v,"manager_lookup");x.managerChunkWidthMicrometres=LayoutInt(v,"manager_chunk_width_um");
x.dispatchChunkWidthMicrometres=LayoutInt(v,"dispatch_chunk_width_um");
for(const auto& a:v.Get("anchors").AsArray())
{ResolvedLayoutAnchorV1 p;p.kind=LayoutString(a,"kind");p.stableId=LayoutString(a,"stable_id");p.units=LayoutString(a,"units");
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));
}
const std::string serializedIdentity=LayoutString(root,"stable_identity");
if(!s.finalize(error))return std::nullopt;
if(s.stableIdentity!=serializedIdentity){error="resolved layout stable identity does not match its canonical payload";return std::nullopt;}
return s;
}
ResolvedLayoutValidatorAuditV1 AuditResolvedLayoutValidatorV1()
{
ResolvedLayoutValidatorAuditV1 result;
auto make=[](std::string id,std::int32_t order,std::int64_t top,std::int64_t bottom)
{
ResolvedLayoutStratumV1 s;s.stableId=std::move(id);s.order=order;s.strateIndex=order;s.generatorKind="synthetic";
s.interiorTopMicrometres=top;s.interiorBottomMicrometres=bottom;s.voxelSizeMicrometres=250000;
s.transitionMode="Gradient";s.transitionWidthMicrometres=4000000;
s.transitionHonouredInCore=true;
s.ceiling={"ceiling",top,top+1000000,true,true,false,"closed ceiling band","upper face","synthetic"};
s.content={"content",bottom,top,true,true,false,"generator content","none","none"};
s.bedrock={"bedrock",bottom-1000000,bottom,true,true,false,"breakable bedrock","lower face","none"};
return s;
};
const auto expectReject=[&](std::string name,ResolvedLayoutSnapshotV1 candidate)
{
++result.controlsRun;std::vector<ResolvedLayoutIssueV1> issues;std::string error;
if(!candidate.validate(ResolvedLayoutValidationModeV1::strict,issues,error))++result.controlsRejectedAsExpected;
else result.failures.push_back({"negative_control_accepted",std::move(name)+" was accepted by strict validator"});
};
auto base=ResolvedLayoutSnapshotV1{};base.worldIdentity="synthetic";base.strates={make("a",0,10000000,0),make("b",1,-2000000,-12000000)};
{std::vector<ResolvedLayoutIssueV1> issues;std::string error;
if(!base.validate(ResolvedLayoutValidationModeV1::strict,issues,error))
result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"});}
auto boundaryGap=base;boundaryGap.strates[1].ceiling.topMicrometres-=1;
expectReject("bedrock/next-ceiling gap",boundaryGap);
auto missingBedrock=base;missingBedrock.strates[0].bedrock.presentInCurrentField=false;expectReject("missing bedrock",missingBedrock);
auto missingCeiling=base;missingCeiling.strates[0].ceiling.presentInCurrentField=false;expectReject("missing ceiling",missingCeiling);
auto wrongOrder=base;wrongOrder.strates[0].content.kind="bedrock";expectReject("wrong band order",wrongOrder);
auto largeOverlap=base;largeOverlap.strates[0].transitionInterpenetrationMicrometres=4000001;expectReject("interpenetration exceeds transition width",largeOverlap);
auto genericOverlap=base;genericOverlap.strates[0].bedrock.bottomMicrometres-=1;expectReject("nominal band overlap",genericOverlap);
auto genericGap=base;genericGap.strates[0].bedrock.bottomMicrometres+=1;expectReject("nominal band gap",genericGap);
result.passed=result.controlsRun==result.controlsRejectedAsExpected&&result.failures.empty();return result;
}
}
+26
View File
@@ -1095,6 +1095,32 @@ public:
return AnalyticDensityAt(X,Y,Z); return AnalyticDensityAt(X,Y,Z);
} }
std::string DensityDispatchPathAt(float X,float Y,float Z) const
{
if(SharedDensitySource)return SharedDensitySource->DensityDispatchPathAt(X,Y,Z);
float Density=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,Density))return "FloatingIslands-native";
if(NativeSurfaceDensityAt(X,Y,Z,Density))return "SurfaceWorld-native";
if(UsesLegacySurfaceField(X,Y,Z))return "SurfaceWorld-legacy";
if(Owner&&Owner->Manager)
{
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CHUNK_SIZE));
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ));
switch(Type)
{
case ECaveGeneratorType::TunnelNetwork:return "compiled-analytic/TunnelNetwork";
case ECaveGeneratorType::FlatPlain:return "compiled-analytic/FlatPlain";
case ECaveGeneratorType::CrystalChamber:return "compiled-analytic/CrystalChamber";
case ECaveGeneratorType::Maze:return "compiled-analytic/Maze";
case ECaveGeneratorType::SurfaceWorld:return "compiled-analytic/SurfaceWorld";
case ECaveGeneratorType::VerticalShafts:return "compiled-analytic/VerticalShafts";
case ECaveGeneratorType::FloatingIslands:return "compiled-analytic/FloatingIslands";
case ECaveGeneratorType::Underwater:return "compiled-analytic/Underwater";
}
}
return "compiled-analytic/fallback";
}
float DensityAtLattice(int32 X,int32 Y,int32 Z) const float DensityAtLattice(int32 X,int32 Y,int32 Z) const
{ {
if(SharedDensitySource)return SharedDensitySource->DensityAtLattice(X,Y,Z); if(SharedDensitySource)return SharedDensitySource->DensityAtLattice(X,Y,Z);
+5
View File
@@ -2,6 +2,7 @@
#include "StandaloneSnapshotAdapter.h" #include "StandaloneSnapshotAdapter.h"
#include "VoxelForgeCore/VoxelForgeCore.h" #include "VoxelForgeCore/VoxelForgeCore.h"
#include "WorldAssemblerInternal.h" #include "WorldAssemblerInternal.h"
#include "ResolvedLayoutInternal.h"
#include "FloatingIslandsInternal.h" #include "FloatingIslandsInternal.h"
#include <shared_mutex> #include <shared_mutex>
@@ -1457,6 +1458,10 @@ namespace VoxelForgeCore
float World::densityAt(Vec3d position) const float World::densityAt(Vec3d position) const
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; } { return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
std::string World::densityDispatchPathAt(Vec3d position) const
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape
?impl_->shape->DensityDispatchPathAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z))
:"uncompiled"; }
float World::unclampedDensityAt(Vec3d position) const float World::unclampedDensityAt(Vec3d position) const
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAtUnclamped(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; } { return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAtUnclamped(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
DetailFieldSample World::detailSampleAt(Vec3d position) const DetailFieldSample World::detailSampleAt(Vec3d position) const
@@ -0,0 +1,203 @@
# World Assembler L21 — resolved strate layout
This round adds a read-only layout snapshot and validator. It does not change density or mesh
evaluation.
## Snapshot and current stack
World identity is `owner-world/seed/0`; Core voxel size is 0.25 m, owner voxel size is 0.125 m, and
all lattice origins are `(0,0,0)`. Intervals below are half-open `[bottom, top)` metres. Stable strate
IDs are `owner-world/seed/0/strate/<index>/generator/<kind>`. The shared Core record uses canonical
JSON schema v1 and an FNV-1a payload identity.
The required structure, top to bottom, is:
```text
strate N ceiling band
content
bedrock band
strate N+1 ceiling band
content
bedrock band
```
Ceiling and bedrock remain distinct bands owned by their own strate. Their nominal edges touch; a
separate `EVoxelStrateTransition` declaration describes possible interpenetration.
| Prototype | Order / stable suffix | Generator; recipe version | Interior/content (m) | Current ceiling | Current bedrock | Transition at lower interface |
|---|---|---|---|---|---|---|
| SurfaceWorld | 0 / `TunnelNetwork` | TunnelNetwork; `legacy.TunnelNetwork` v0 | `[-120, 8)` | No Core density band. Manager configures a 1 m upper `VF_ApplyBoundarySeal` at `[8,9)`, but the compiled analytic Core path does not apply it. | No Core density band. Manager configures a 1 m lower `VF_ApplyBoundarySeal` at `[-121,-120)`, but the compiled analytic Core path does not apply it. | Gradient, 2 chunks = 8 m by the 4 m manager chunk. Core does not apply it; manager/dispatch chunk widths are 4/8 m. |
| SurfaceWorld | 1 / `SurfaceWorld` | SurfaceWorld; `SurfaceWorld.Heightfield2D` v1 | `[-632,-120)` | No separate volumetric band; cap profile is in content, with `AppendStratumBoundaryCeilings` mesh fallback. | None. | Gradient, 1 chunk = 4 m by manager units; no lower strate in this plan. Manager/dispatch widths are 4/8 m. |
| FloatingIslands | 0 / `FloatingIslands` | FloatingIslands; `FloatingIslands.ClusterField` geometry v3 | `[-60,4)` | Active one-sided upper seal `[3.5,4)` = 0.5 m. It overlaps the content range by 0.5 m. | None in geometry v3. | Gradient, 2 chunks = 8 m. Core does not apply it; manager/dispatch widths are both 4 m. |
| FloatingIslands | 1 / `SurfaceWorld` | SurfaceWorld; `SurfaceWorld.Heightfield2D` v1 | `[-632,-120)` | No separate volumetric band; cap profile is in content, with mesh fallback. | None. | Gradient, 1 chunk = 4 m; no lower strate in this plan. Manager/dispatch widths are 4/8 m. |
Dispatch provenance is recorded per stratum in the JSON snapshot:
- SurfaceWorld bounds come from `BuildSurfaceStratumPlan`, which converts each chunk with 32 Core
voxels = 8 m. `NativeSurfaceDensityAt` selects with `floor(CoreZ/32)`, then calls
`GetGeneratorTypeForChunk` / `GetStrateIndex`. The manager's owner chunk is 4 m.
- FI bounds come from `BuildFloatingIslandStratumPlan`, which converts each 32-voxel owner chunk
through the 12.5/25 cm owner/Core ratio = 16 Core voxels = 4 m. `NativeFloatingIslandDensityAt`
uses the same conversion before the manager lookup.
- Legacy TunnelNetwork selection uses the Core 32-voxel chunk lookup. The manager helper assigns
the upper strate's transition to its lower interface.
SurfaceWorld strata carry four cap profiles, 33 route points and their attached passages. The FI
strate carries four island anchors and its attached passages. The snapshot preserves these recipe
anchors in their source fixed-point units.
The owner configuration’s non-Hard transitions are currently not honored by Core at these interfaces.
`UVoxelStrateManager::GetGenerationParams` contains legacy vector blending, but Core’s compiled
dispatcher selects the resolved/native per-strate fields without applying that blend. FI v3 calls
`VF_ApplyUpperBoundarySeal` for its ceiling; player edit brushes are applied after the native field,
so that seal remains diggable. Bands default to `diggable=true`, `protected=false`; protection is an
explicit option.
```text
Current SurfaceWorld plan, top → bottom
TunnelNetwork ceiling [8,9) (configured; inactive in Core density)
TunnelNetwork content [-120,8)
TunnelNetwork bedrock [-121,-120) (configured; inactive in Core)
SurfaceWorld ceiling: none
SurfaceWorld content [-632,-120)
SurfaceWorld bedrock: none
Current FI prototype, top → bottom
FI ceiling [3.5,4) active, inset into FI content by 0.5 m
FI content [-60,4)
FI bedrock: missing
Unassigned interval [-120,-60) = 60 m
SurfaceWorld ceiling: none
SurfaceWorld content [-632,-120)
SurfaceWorld bedrock: none
```
The SurfaceWorld and FI content interiors therefore do not overlap: the FI prototype has a 60 m gap.
The owner manager slots meet at −60 m when both use 4 m owner chunks, but SurfaceWorld plan/query
dispatch converts chunks at 8 m while FI converts at 4 m. In the gap, Core answers through the
compiled analytic FloatingIslands fallback, not either planned recipe; probes at −68 m returned
solid density −4.125. SurfaceWorld and TunnelNetwork content interiors touch at −120 m in the
SurfaceWorld prototype, but Core does not apply the declared Gradient there.
## Compatibility validation and boundary probes
Compatibility mode accepted both layouts with seven reported issues apiece. SurfaceWorld reports
unhonored transition, 4/8 m dispatch-unit ambiguity on both strates, and missing current-field
ceiling and bedrock bands on both. FI reports its unhonored transition, missing FI bedrock, the
0.5 m ceiling/content overlap, the 60 m uncovered interval, and missing SurfaceWorld ceiling and
bedrock. These are recorded as compatibility deviations, not world changes.
Strict mode accepted a valid synthetic two-strate fixture. Its seven negative controls rejected:
| Control | Strict check that rejects it |
|---|---|
| Nominal band overlap | Bedrock(N) bottom must equal ceiling(N+1) top. |
| Nominal band gap | The same exact nominal contiguity check. |
| Missing bedrock | `bedrock.present` is required. |
| Missing ceiling | `ceiling.present` is required. |
| Wrong band order | Each band kind and the ceiling → content → bedrock edges must match. |
| Gap between bedrock(N) and ceiling(N+1) | Their nominal endpoints must be equal. |
| Interpenetration wider than transition | Explicit overlap must be at most the mode’s limit; this control uses 4,000,001 µm against a 4,000,000 µm Gradient zone. |
Hard has a 0 m limit. Gradient uses `TransitionBlendChunks × 4 m`; Interleaved adds its recorded
two-manager-chunk warp amplitude to the blend width. The nominal bands still touch exactly; the
separate interpenetration field records any permitted physical crossing. Current Core
interpenetration is 0 m because it does not apply the declared blends.
Each plan audited 21 points: one Core voxel (0.25 m) below, at, and above every distinct content,
active-band, configured-band, and transition-zone endpoint. The remaining points agreed with the
snapshot. Every disagreement was an uncovered snapshot point answered by a named compiled analytic
fallback:
| Prototype | Probe Z (m) | Actual Core dispatch | Density / occupancy |
|---|---|---|---|
| SurfaceWorld | −632.250 | `compiled-analytic/TunnelNetwork` | `+1`, air |
| SurfaceWorld | 8.000, 8.250, 8.750, 9.000, 9.250 | `compiled-analytic/TunnelNetwork` | `+9.648438`, `+10.64844`, `+12.64844`, `+13.64844`, `+14.64844`; all air |
| FI | −632.250 | `compiled-analytic/TunnelNetwork` | `+1`, air |
| FI | −120.000, −119.750 | `compiled-analytic/FloatingIslands` | `−108.4793`, `−107.4969`; solid |
| FI | −68.250, −68.000, −67.750, −60.250 | `compiled-analytic/FloatingIslands` | `−4.125`; solid at each point |
| FI | 4.000, 4.250 | `compiled-analytic/FloatingIslands` | `+0.671875`, `+2.253906`; air |
Probe summaries: SurfaceWorld 6/21 disagreements; FI 9/21. Full per-point records are in
`Saved/Linux/linux21/boundary-probes-{surface-world,floating-islands}.log`.
## Identity, output parity and cost
Snapshots round-tripped through canonical JSON. At W1/W8/W12 their bytes were identical per recipe:
| Recipe | Stable identity | Canonical JSON SHA-256 |
|---|---|---|
| SurfaceWorld | `resolved-layout-v1-fnv64:63cf4d2661149fcd` | `267586b3da9bc5c08c654c097559b205222f53c594c4c4332cb4929fd3d75c05` |
| FloatingIslands | `resolved-layout-v1-fnv64:1d90167f37ef9323` | `7bb9e9d745de3a2edf4276f507a5e4c082ff86aa73d9c1ad7e77c1b4d8b8bea8` |
The 28 hash runs (both recipes, Clang `fp-contract=off` and GCC default, W1/W8/W12 and W12×5)
matched all 12 L19 rows per run. The 14 dedicated 24 m crossing runs also matched all 12 L19
crossing rows. The table gives the final Clang W12×5 rows, in density/mesh/payload/topology order;
each equals its L19 reference.
| Scope | SurfaceWorld | FloatingIslands |
|---|---|---|
| Full detail | `0ee0579bd15cbbe4 / 32e5d4964d610fee / 1932b96ee4b1d910 / 69743c6bad3447dc` | `43034299d33f2536 / a41e8353ec4bbe9c / cd0e3e15eef827b0 / 0b3c3f13cc1d6f8a` |
| Whole view | `c10697a8f48380c7 / 4197fc1072f0ff58 / 7b3e616ec9459851 / c879e35592bf33e5` | `00bc3607a7839854 / 4baa66127c2b0892 / e11ef05e851a1517 / 1278bc4b1fffb869` |
| LOD 0 | `769f2a706d585a72 / 33eeeb39fa443816 / 645621a1d968983f / c2457ed905036c9d` | `ccf97d8e95163a35 / 3333fa0ddebdece2 / d1906bffc2b9d6f2 / a261f3d965e15c12` |
| LOD 1 | `69cc5a5c0a2c60af / b48335f5410c57d5 / d15c5480162ab8ba / feca1bd025823604` | `25fa2f0fa7fbb6e6 / c549c519707ac1d7 / 66f797cde4ebdecf / 2383982a12a27ee5` |
| LOD 2 | `85deed5683a77202 / 53469895987aeea2 / e55f8ae7e1532033 / d5feb12a0e85c09b` | `c0577decd8a02cab / 3ccf08b7be6edc5b / 0abcbbda5a06c110 / 11c77b71b606fe75` |
| LOD 3 | `6518caefb3be7e7f / ec513c90b853a614 / f62fa1137ff17f82 / ccf1dcf681965816` | `386e0c0ef2d7c920 / 1ee2c82d34ad9918 / 000a6514ba018406 / 1b4523b19e90f305` |
| LOD 4 | `62d4be96021992f4 / 42706847cb9c744a / eab76f9634e4b9bd / 207ffbb6ef184397` | `e8117a3a803be9f8 / 8a6c36cae1fe0e30 / c839f1b1629eff8c / 0ce9d7ea77b4f62b` |
| LOD 5 | `5e4a8c38b8cea6fc / 76adc9ae86988e27 / 938975bf7cc6d94d / 42e135856abdbcac` | `ca6d1df453c6b600 / 02c90d834d9a3fb6 / 4364c37d526f4104 / 1b1cf40d25e50ddf` |
| LOD 6 | `e036e95b9006945b / a880cc101a4ed957 / bd82f1aefad6dd89 / 5e9f3408345d95b3` | `6764e347b08603a0 / 46c6ed9a1f029b94 / 5b8dc9d5b7cb0d3d / a10a4a69a93eac4d` |
| LOD 7 | `8ee358af56a30d8d / 5cbd8e5965a2cc3f / d4a7fe5d9e4bde30 / 4dd52cd090a9af78` | `c7cd1b483c598595 / a545eb567dc4a0df / b0a1fb225437e05a / 301d1b884c509550` |
| LOD 8 | `c3458e1cbdb9c471 / fba3698d21dd1be0 / 77dfb814eac3a24c / c9514f1580c42449` | `7b6e9279b4993109 / cc63440d9867a18d / 12f5cec78b38fa1b / eed919db6db6b2a7` |
Exact-query rows (density/unclamped, detail, collision): SurfaceWorld `e4d4a8eb95b35933 / e4d4a8eb95b35933`,
`efcba20c0c70c415`, `e604823a249029bf`; FloatingIslands `87e5c0dbf9ee9ee2 / 87e5c0dbf9ee9ee2`,
`efcba20c0c70c415`, `e604823a249029bf`. The representative 24 m crossing W12×5 whole-view row is the
SurfaceWorld row above; all crossing full-detail, LOD and exact-query rows matched the L19 crossing
reference.
Five interleaved hashing-off W12 pairs against L19 measured SurfaceWorld medians 1.510 s old / 1.512 s
new (+0.14%) and FI 13.487 s old / 13.381 s new (−0.79%). The opt-in snapshot is constructed once
at plan time, outside tile workers; ordinary standalone evaluation does not request it. No hash rows
were produced during these pairs.
Clang and GCC builds passed at 12 jobs. Final build peak process-tree/single-process RSS was
2,697,676/450,248 KiB (Clang) and 4,570,704/897,024 KiB (GCC). The final evidence runner's maximum
single-process RSS was 583,736 KiB. All runs used CPUs 0–11 and stayed below the per-run limit; no
governor or system changes were made.
## Failed and retried runs
- An initial snapshot invocation omitted `--stream-benchmark --stream-pipeline` and exited 2; adding
the required flags passed.
- Early Clang builds caught a `strates` member typo, then an audit initializer/type error. Both were
fixed; the final Clang builds passed.
- The first complete evidence pass exited 1 at boundary probing because the audit read the host
manager after the harness had released it. Dispatch provenance was moved to Core's live selector;
the final audits passed.
- One GCC build wrapper was interrupted (exit 130) while diagnosing an empty progress log, then
rerun successfully. No runtime/hash run timed out or exceeded 15 minutes, and no L19 hash mismatch
remained.
## Owner note
This records the present strata and confirms the selected ceiling → content → bedrock model. FI still
has no floor in v3; SurfaceWorld has no volumetric floor or separate ceiling; the 8 m / 4 m dispatch
conversions leave a 60 m FI-prototype gap answered by analytic fallback. Core currently ignores the
configured transition blend at these interfaces. Bedrock and ceiling protection remain explicit
options with diggable defaults. This round changes no world output.
The Unreal adapter adds only a separate `resolved_layout` JSON field beside `world_assembly`; that
small edit could not be compiled on this Linux machine. No async path, density evaluator, hash input,
or recipe version changed. No commit, push, or stash was made.
## Orchestrator review (Claude)
- Merged: read-only snapshot + validator, no world change (28 hash runs and 14 crossing runs equal L19;
cost within noise). The Unreal adapter gains a 193-line `resolved_layout` export beside
`world_assembly`. It is export-only, but UNCOMPILED here; it must be built on the owner PC.
- Key facts for the owner: the authored params put the SW top at −60 m (4 m owner chunks), Core
builds SW at −120…−632 m (8 m conversion); the 60 m between FI and SW is unowned and answered by
the analytic FloatingIslands fallback as solid. No strate has an active bedrock band; SW has no
ceiling band (mesh fallback only); configured Gradient transitions are ignored by Core.
- To check next: whether the "ledges" ground seen under the islands is this fallback solid in the
unowned gap rather than SW terrain (L19 attributed them to SW at about −60…−73 m).
@@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""Constrained L21 snapshot, output-parity, crossing, and off-cost evidence."""
import os
import re
import signal
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/linux21"
OUT.mkdir(parents=True, exist_ok=True)
if not os.sched_getaffinity(0).issubset(set(range(12))):
raise SystemExit("run under taskset -c 0-11")
BIN = {
"clang-off": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux21-clang",
"gcc-default": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux21-gcc",
}
OLD_CLANG = ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux19-clang"
OLD_GCC = ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux19-gcc"
DATA = ROOT / "Standalone/data/owner_world.json"
HASH_ROW = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
LODS = [(1, 1), (8, 1), (12, 1), (12, 2), (12, 3), (12, 4), (12, 5)]
RECIPES = {
"surface-world": "0,-35,-487.94,0,-22",
"floating-islands": "-22,-19,-22.8,0,-4",
}
results = []
def launch(name, argv, timeout=900):
logfile = OUT / f"{name}.log"
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", *map(str, argv)]
started = time.monotonic()
peak_tree = 0
peak_process = 0
timed_out = False
with logfile.open("w", encoding="utf-8") as stream:
process = subprocess.Popen(command, stdout=stream, stderr=subprocess.STDOUT,
start_new_session=True)
while process.poll() is None:
pending, seen, total = [process.pid], set(), 0
while pending:
pid = pending.pop()
if pid in seen:
continue
seen.add(pid)
try:
pending.extend(int(x) for x in Path(f"/proc/{pid}/task/{pid}/children").read_text().split())
values = {}
for line in Path(f"/proc/{pid}/status").read_text().splitlines():
if line.startswith(("VmRSS:", "VmHWM:")):
key, amount, *_ = line.split()
values[key] = int(amount)
total += values.get("VmRSS:", 0)
peak_process = max(peak_process, values.get("VmRSS:", 0), values.get("VmHWM:", 0))
except (FileNotFoundError, PermissionError, ProcessLookupError, ValueError):
pass
peak_tree = max(peak_tree, total)
if time.monotonic() - started > timeout:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
timed_out = True
break
time.sleep(0.1)
code = process.wait()
elapsed = time.monotonic() - started
rows = HASH_ROW.findall(logfile.read_text(encoding="utf-8", errors="replace"))
results.append((name, code, elapsed, peak_process, peak_tree, timed_out, len(rows)))
print(f"[L21Run] {name} exit={code} seconds={elapsed:.3f} peak_process_rss_kib={peak_process} "
f"peak_tree_rss_kib={peak_tree} hash_rows={len(rows)} timed_out={str(timed_out).lower()}", flush=True)
return logfile
def base_args(binary, recipe, workers, hash_output=False, crossing=False):
args = [binary, DATA, "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", f"--workers={workers}",
f"--camera={RECIPES[recipe]}", f"--recipe={recipe}"]
if hash_output:
args.append("--stream-hash")
if crossing:
args += ["--camera-step=24,0,0", "--region-crossing"]
return args
# Plan-time identities and canonical serialization at W1/W8/W12, both field recipes.
snapshots = {}
for recipe in RECIPES:
for workers, repeat in [(1, 1), (8, 1), (12, 1)]:
path = OUT / f"snapshot-{recipe}-w{workers}-r{repeat}.json"
args = [BIN["clang-off"], DATA, "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", f"--recipe={recipe}", f"--workers={workers}",
f"--resolved-layout-output={path}", "--resolved-layout-only"]
log = launch(f"snapshot-{recipe}-w{workers}-r{repeat}", args)
if results[-1][1] != 0:
raise SystemExit(f"snapshot plan failed: {log}")
snapshots[(recipe, workers)] = path.read_bytes()
if len({snapshots[(recipe, workers)] for workers in (1, 8, 12)}) != 1:
raise SystemExit(f"snapshot serialization changed with worker count: {recipe}")
print(f"[L21SnapshotIdentity] recipe={recipe} worker_counts=1,8,12 canonical_bytes=identical status=PASS", flush=True)
# Full matrix. Every row is compared against the L19 W12x5 reference.
for recipe in RECIPES:
for compiler, binary in BIN.items():
for workers, repeat in LODS:
name = f"matrix-{recipe}-{compiler}-w{workers}-r{repeat}"
log = launch(name, base_args(binary, recipe, workers, hash_output=True))
rows = HASH_ROW.findall(log.read_text(encoding="utf-8", errors="replace"))
reference_name = f"{recipe}-{compiler}-w12-r5.log"
reference = ROOT / "Saved/Linux/l19-matrix" / reference_name
expected = HASH_ROW.findall(reference.read_text(encoding="utf-8", errors="replace"))
if results[-1][1] != 0 or len(rows) != 12 or rows != expected:
print(f"[L21HashMismatch] {name} expected_rows={len(expected)} actual_rows={len(rows)}", flush=True)
raise SystemExit(1)
# The crossing matrix retains the 24 m two-view query proof on both compilers.
for compiler, binary in BIN.items():
for workers, repeat in LODS:
name = f"crossing-{compiler}-w{workers}-r{repeat}"
log = launch(name, base_args(binary, "surface-world", workers, hash_output=True, crossing=True))
rows = HASH_ROW.findall(log.read_text(encoding="utf-8", errors="replace"))
reference = ROOT / "Saved/Linux/l19-crossing" / f"{compiler}-w12-r5.log"
expected = HASH_ROW.findall(reference.read_text(encoding="utf-8", errors="replace"))
if results[-1][1] != 0 or len(rows) != 12 or rows != expected:
print(f"[L21CrossingHashMismatch] {name} expected_rows={len(expected)} actual_rows={len(rows)}", flush=True)
raise SystemExit(1)
# Five interleaved W12 pairs per recipe, hashing disabled. Alternate order by pair to reduce drift.
off_pairs = {recipe: {"old": [], "new": []} for recipe in RECIPES}
for recipe in RECIPES:
for pair in range(1, 6):
order = [("old", OLD_CLANG), ("new", BIN["clang-off"])] if pair % 2 else [("new", BIN["clang-off"]), ("old", OLD_CLANG)]
for label, binary in order:
name = f"offcost-{recipe}-pair{pair}-{label}"
log = launch(name, base_args(binary, recipe, 12, hash_output=False), timeout=900)
if results[-1][1] != 0:
raise SystemExit(f"hashing-off cost run failed: {log}")
off_pairs[recipe][label].append(results[-1][2])
print(f"[L21OffCostPair] recipe={recipe} pair={pair} old_seconds={off_pairs[recipe]['old'][-1]:.3f} "
f"new_seconds={off_pairs[recipe]['new'][-1]:.3f}", flush=True)
for recipe, pair in off_pairs.items():
old = statistics.median(pair["old"])
new = statistics.median(pair["new"])
print(f"[L21OffCostSummary] recipe={recipe} pairs=5 median_old_seconds={old:.3f} "
f"median_new_seconds={new:.3f} delta_percent={(new-old)/old*100.0:.2f}", flush=True)
# Query the real compiled Core field at every distinct recorded stratum/band endpoint.
for recipe in RECIPES:
name = f"boundary-probes-{recipe}"
args = base_args(BIN["clang-off"], recipe, 12, hash_output=False)
args += ["--audit-resolved-layout-boundaries"]
log = launch(name, args)
if results[-1][1] != 0:
raise SystemExit(f"Core boundary probes failed: {log}")
print(f"[L21EvidenceSummary] snapshots=6 matrix=28 crossing=14 off_cost_pairs=10 boundary_audits=2 status=PASS", flush=True)
for name, code, elapsed, rss, tree, timed_out, rows in results:
print(f"[L21RunDetail] {name} exit={code} seconds={elapsed:.3f} peak_process_rss_kib={rss} "
f"peak_tree_rss_kib={tree} hash_rows={rows} timed_out={str(timed_out).lower()}", flush=True)
+58 -1
View File
@@ -63,6 +63,9 @@ namespace
float CameraYawDegrees=0.0f; float CameraYawDegrees=0.0f;
float CameraPitchDegrees=0.0f; float CameraPitchDegrees=0.0f;
std::filesystem::path MeshExportPath; std::filesystem::path MeshExportPath;
std::filesystem::path ResolvedLayoutOutputPath;
bool ResolvedLayoutOnly=false;
bool AuditResolvedLayoutBoundaries=false;
}; };
bool ParseInteger(std::string_view text,int& output) bool ParseInteger(std::string_view text,int& output)
@@ -117,6 +120,9 @@ namespace
" --mesh-export=FILE (write the exact generated tile meshes for offline rendering)\n" " --mesh-export=FILE (write the exact generated tile meshes for offline rendering)\n"
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n" " --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
" --seed=N --recipe=surface-world|floating-islands\n" " --seed=N --recipe=surface-world|floating-islands\n"
" --resolved-layout-output=FILE (canonical plan-time layout snapshot)\n"
" --resolved-layout-only (exit after plan and snapshot validation)\n"
" --audit-resolved-layout-boundaries (query Core at each recorded endpoint)\n"
" --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"
" --screen-space-gate (measure display error and historical negative controls)\n" " --screen-space-gate (measure display error and historical negative controls)\n"
@@ -134,6 +140,8 @@ namespace
return false; return false;
} }
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=="--audit-resolved-layout-boundaries")options.AuditResolvedLayoutBoundaries=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;
else if(arg=="--stream-hash")options.StreamHash=true; else if(arg=="--stream-hash")options.StreamHash=true;
@@ -155,6 +163,8 @@ namespace
else if(recipe=="floating-islands")options.FloatingIslandsRecipe=true; else if(recipe=="floating-islands")options.FloatingIslandsRecipe=true;
else {std::fprintf(stderr,"Invalid --recipe value: %.*s\n",static_cast<int>(recipe.size()),recipe.data());return false;} else {std::fprintf(stderr,"Invalid --recipe value: %.*s\n",static_cast<int>(recipe.size()),recipe.data());return false;}
} }
else if(arg.starts_with("--resolved-layout-output="))
options.ResolvedLayoutOutputPath=std::string(arg.substr(std::string_view("--resolved-layout-output=").size()));
else if(arg.starts_with("--seed=")) else if(arg.starts_with("--seed="))
{if(!readInt("--seed=",options.SeedOverride))return false;options.HasSeedOverride=true;} {if(!readInt("--seed=",options.SeedOverride))return false;options.HasSeedOverride=true;}
else if(arg.starts_with("--render-distance-chunks=")) else if(arg.starts_with("--render-distance-chunks="))
@@ -314,6 +324,52 @@ 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::ResolvedLayoutSnapshotV1 resolvedLayout;
const bool buildResolvedLayout=!options.ResolvedLayoutOutputPath.empty()
||options.AuditResolvedLayoutBoundaries||options.ResolvedLayoutOnly;
if(buildResolvedLayout)
{
const std::string_view variant=options.FloatingIslandsRecipe?"floating-islands":"surface-world";
if(!StandaloneWorldAssemblyPrototype::BuildResolvedLayoutSnapshot(
*ownerWorld,worldAssembly,variant,resolvedLayout,error))
{std::fprintf(stderr,"Resolved-layout snapshot failed: %s\n",error.c_str());return 2;}
const std::string layoutJson=resolvedLayout.ToJson();
if(!options.ResolvedLayoutOutputPath.empty())
{
const auto roundTrip=VoxelForgeCore::ResolvedLayoutSnapshotV1::FromJson(layoutJson,error);
if(!roundTrip||roundTrip->ToJson()!=layoutJson||roundTrip->stableIdentity!=resolvedLayout.stableIdentity)
{if(error.empty())error="resolved-layout canonical serialization did not round-trip";
std::fprintf(stderr,"Resolved-layout round trip failed: %s\n",error.c_str());return 2;}
if(!options.ResolvedLayoutOutputPath.parent_path().empty())
std::filesystem::create_directories(options.ResolvedLayoutOutputPath.parent_path());
std::ofstream output(options.ResolvedLayoutOutputPath,std::ios::binary);
if(!output){std::fprintf(stderr,"Could not write resolved-layout snapshot: %s\n",options.ResolvedLayoutOutputPath.c_str());return 2;}
output.write(layoutJson.data(),static_cast<std::streamsize>(layoutJson.size()));
if(!output){std::fprintf(stderr,"Resolved-layout snapshot write failed: %s\n",options.ResolvedLayoutOutputPath.c_str());return 2;}
}
const auto controls=VoxelForgeCore::AuditResolvedLayoutValidatorV1();
std::vector<VoxelForgeCore::ResolvedLayoutIssueV1> compatibilityIssues;
std::string compatibilityError;
const bool compatibilityAccepted=resolvedLayout.validate(
VoxelForgeCore::ResolvedLayoutValidationModeV1::compatibility,
compatibilityIssues,compatibilityError);
std::printf("[ResolvedLayoutSnapshot] schema=%u stable_identity=%s strata=%zu bytes=%zu round_trip=PASS\n",
resolvedLayout.schemaVersion,resolvedLayout.stableIdentity.c_str(),resolvedLayout.strates.size(),layoutJson.size());
std::printf("[ResolvedLayoutCompatibility] accepted=%s issues=%zu error=%s\n",
compatibilityAccepted?"true":"false",compatibilityIssues.size(),compatibilityError.c_str());
for(const auto& issue:compatibilityIssues)
std::printf("[ResolvedLayoutCompatibilityIssue] code=%s detail=%s\n",
issue.code.c_str(),issue.detail.c_str());
if(!compatibilityAccepted)
{std::fprintf(stderr,"Resolved-layout compatibility validation failed: %s\n",compatibilityError.c_str());return 2;}
std::printf("[ResolvedLayoutValidatorControls] rejected=%u/%u status=%s\n",
controls.controlsRejectedAsExpected,controls.controlsRun,controls.passed?"PASS":"FAIL");
if(!controls.passed)
{for(const auto& failure:controls.failures)std::printf("[ResolvedLayoutValidatorControlFailure] %s %s\n",failure.code.c_str(),failure.detail.c_str());return 2;}
if(!options.ResolvedLayoutOutputPath.empty())
std::printf("[ResolvedLayoutSnapshotOutput] path=%s\n",options.ResolvedLayoutOutputPath.c_str());
if(options.ResolvedLayoutOnly)return 0;
}
if(options.AutoSurfaceCamera) if(options.AutoSurfaceCamera)
{ {
constexpr std::int64_t UnitsPerVoxel=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; constexpr std::int64_t UnitsPerVoxel=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
@@ -377,5 +433,6 @@ int main(int argc,char** argv)
cameraStepVoxels,options.AuditUniformBlockProofs,options.ProfileLodWork, cameraStepVoxels,options.AuditUniformBlockProofs,options.ProfileLodWork,
options.RegionCrossing,options.AuditTileShapeReadiness, options.RegionCrossing,options.AuditTileShapeReadiness,
options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence, options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence,
options.AuditRouteParity); options.AuditRouteParity,options.AuditResolvedLayoutBoundaries,
buildResolvedLayout?&resolvedLayout:nullptr);
} }
+86 -1
View File
@@ -253,7 +253,9 @@ namespace StandaloneStreamBenchmark
bool bAuditUniformBlockProofs=false,bool bReportLodWorkProfile=false, bool bAuditUniformBlockProofs=false,bool bReportLodWorkProfile=false,
bool bRegionCrossing=false, bool bRegionCrossing=false,
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,
const VoxelForgeCore::ResolvedLayoutSnapshotV1* ResolvedLayout=nullptr)
{ {
using Clock=std::chrono::steady_clock; using Clock=std::chrono::steady_clock;
FStandaloneOwnerWorld& World=*OwnerWorld; FStandaloneOwnerWorld& World=*OwnerWorld;
@@ -438,6 +440,89 @@ 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(bAuditResolvedLayoutBoundaries)
{
if(ResolvedLayout==nullptr)
{Streamer.Stop();std::fprintf(stderr,"[ResolvedLayoutProbe] missing plan-time snapshot\n");return 2;}
std::vector<std::int64_t> endpoints;
for(const auto& Stratum:ResolvedLayout->strates)
{
endpoints.push_back(Stratum.interiorBottomMicrometres);
endpoints.push_back(Stratum.interiorTopMicrometres);
endpoints.push_back(Stratum.ceiling.bottomMicrometres);
endpoints.push_back(Stratum.ceiling.topMicrometres);
endpoints.push_back(Stratum.bedrock.bottomMicrometres);
endpoints.push_back(Stratum.bedrock.topMicrometres);
}
for(std::size_t StratumIndex=0;StratumIndex+1<ResolvedLayout->strates.size();++StratumIndex)
{
const auto& Upper=ResolvedLayout->strates[StratumIndex];
if(Upper.transitionMode=="Hard"||Upper.transitionWidthMicrometres<=0)continue;
endpoints.push_back(Upper.interiorBottomMicrometres-Upper.transitionWidthMicrometres);
endpoints.push_back(Upper.interiorBottomMicrometres+Upper.transitionWidthMicrometres);
}
std::sort(endpoints.begin(),endpoints.end());
endpoints.erase(std::unique(endpoints.begin(),endpoints.end()),endpoints.end());
std::uint64_t probes=0,disagreements=0;
const double CoreVoxelMicrometres=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons);
const auto expectedAt=[&](std::int64_t zMicrometres)
{
std::vector<std::string> candidates;
for(const auto& Stratum:ResolvedLayout->strates)
{
const auto contains=[zMicrometres](const VoxelForgeCore::ResolvedLayoutBandV1& Band)
{return Band.presentInCurrentField&&zMicrometres>=Band.bottomMicrometres
&&zMicrometres<Band.topMicrometres;};
if(contains(Stratum.ceiling))candidates.push_back(Stratum.generatorKind+"/ceiling");
if(contains(Stratum.content))candidates.push_back(Stratum.generatorKind+"/content");
if(contains(Stratum.bedrock))candidates.push_back(Stratum.generatorKind+"/bedrock");
}
return candidates;
};
const std::array<std::int64_t,3> offsets{{
-static_cast<std::int64_t>(World.CoreVoxelSizeMicrons),0,
static_cast<std::int64_t>(World.CoreVoxelSizeMicrons)}};
for(const std::int64_t Endpoint:endpoints)
for(const std::int64_t Offset:offsets)
{
const std::int64_t zMicrometres=Endpoint+Offset;
const double zVoxel=static_cast<double>(zMicrometres)/CoreVoxelMicrometres;
const VoxelForgeCore::Vec3d position{0.0,0.0,zVoxel};
const std::string actual=Shape.densityDispatchPathAt(position);
const auto expected=expectedAt(zMicrometres);
std::string expectedLabel;
std::vector<std::string> expectedGenerators;
for(const auto& Candidate:expected)
{
if(!expectedLabel.empty())expectedLabel+=',';
expectedLabel+=Candidate;
const auto Separator=Candidate.find('/');
const std::string Kind=Candidate.substr(0,Separator);
if(std::find(expectedGenerators.begin(),expectedGenerators.end(),Kind)==expectedGenerators.end())
expectedGenerators.push_back(Kind);
}
if(expected.empty())expectedLabel="uncovered";
std::string actualGenerator;
if(actual=="SurfaceWorld-native"||actual=="SurfaceWorld-legacy")actualGenerator="SurfaceWorld";
else if(actual=="FloatingIslands-native")actualGenerator="FloatingIslands";
else if(actual.rfind("compiled-analytic/",0)==0)actualGenerator=actual.substr(18);
const bool mismatch=expected.empty()
?actual!="compiled-analytic/fallback"
:std::find(expectedGenerators.begin(),expectedGenerators.end(),actualGenerator)==expectedGenerators.end();
const float density=Shape.densityAt(position);
const bool solid=density<0.0f;
++probes;disagreements+=mismatch?1u:0u;
std::printf("[ResolvedLayoutProbe] endpoint_um=%lld offset_um=%lld z_m=%.3f expected=%s actual=%s density=%.7g occupancy=%s disagreement=%s\n",
static_cast<long long>(Endpoint),static_cast<long long>(Offset),
static_cast<double>(zMicrometres)/1000000.0,expectedLabel.c_str(),actual.c_str(),density,
solid?"solid":"air",mismatch?"YES":"no");
}
std::printf("[ResolvedLayoutProbeSummary] probes=%llu disagreements=%llu status=%s\n",
static_cast<unsigned long long>(probes),static_cast<unsigned long long>(disagreements),
disagreements==0?"PASS":"DEVIATIONS_RECORDED");
Streamer.Stop();
return 0;
}
const auto ShapeReady=Clock::now(); const auto ShapeReady=Clock::now();
const double ShapeSeconds=std::chrono::duration<double>(ShapeReady-ShapeBuildStart).count(); const double ShapeSeconds=std::chrono::duration<double>(ShapeReady-ShapeBuildStart).count();
if(bAuditTileShapeReadiness) if(bAuditTileShapeReadiness)
+157
View File
@@ -525,4 +525,161 @@ namespace StandaloneWorldAssemblyPrototype
return true; return true;
} }
inline const char* GeneratorKind(ECaveGeneratorType type)
{
switch(type)
{
case ECaveGeneratorType::TunnelNetwork:return "TunnelNetwork";
case ECaveGeneratorType::FlatPlain:return "FlatPlain";
case ECaveGeneratorType::CrystalChamber:return "CrystalChamber";
case ECaveGeneratorType::Maze:return "Maze";
case ECaveGeneratorType::SurfaceWorld:return "SurfaceWorld";
case ECaveGeneratorType::VerticalShafts:return "VerticalShafts";
case ECaveGeneratorType::FloatingIslands:return "FloatingIslands";
case ECaveGeneratorType::Underwater:return "Underwater";
}
return "Unknown";
}
inline const char* TransitionKind(EVoxelStrateTransition type)
{
switch(type)
{
case EVoxelStrateTransition::Gradient:return "Gradient";
case EVoxelStrateTransition::Hard:return "Hard";
case EVoxelStrateTransition::Interleaved:return "Interleaved";
}
return "Unknown";
}
inline bool BuildResolvedLayoutSnapshot(const FStandaloneOwnerWorld& owner,
const VoxelForgeCore::WorldAssemblyManifest& assembly,std::string_view recipeVariant,
VoxelForgeCore::ResolvedLayoutSnapshotV1& out,std::string& error)
{
using namespace VoxelForgeCore;
if(!owner.Manager){error="resolved layout builder requires the initialized director";return false;}
(void)recipeVariant;
out={};out.worldIdentity="owner-world/seed/"+std::to_string(owner.Seed);
out.ownerVoxelSizeMicrometres=owner.OwnerVoxelSizeMicrons;
out.coreVoxelSizeMicrometres=owner.CoreVoxelSizeMicrons;
const std::int64_t coreMicrons=std::max<std::uint32_t>(1,owner.CoreVoxelSizeMicrons);
const std::int64_t managerChunkMicrons=CHUNK_SIZE*static_cast<std::int64_t>(owner.OwnerVoxelSizeMicrons);
const auto fixedToMicrons=[&](std::int64_t fixed)
{return static_cast<std::int64_t>(std::llround(static_cast<double>(fixed)
*static_cast<double>(coreMicrons)/(WorldAssemblySubvoxelsPerVoxel)));};
for(const FStrateSlot& slot:owner.Manager->GetLayout())
{
if(!slot.Definition)continue;
const auto type=owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ));
ResolvedLayoutStratumV1 s;s.strateIndex=slot.StrateIndex;s.generatorKind=GeneratorKind(type);
s.stableId=out.worldIdentity+"/strate/"+std::to_string(slot.StrateIndex)
+"/generator/"+GeneratorKind(type);
s.recipeId=type==ECaveGeneratorType::SurfaceWorld?"SurfaceWorld.Heightfield2D"
:type==ECaveGeneratorType::FloatingIslands?"FloatingIslands.ClusterField":"legacy."+std::string(GeneratorKind(type));
s.recipeVersion=type==ECaveGeneratorType::SurfaceWorld?assembly.recipe.schemaVersion
:type==ECaveGeneratorType::FloatingIslands&&!assembly.floatingIslands.empty()
?assembly.floatingIslands.front().manifest.recipe.geometryVersion:0;
s.voxelSizeMicrometres=owner.CoreVoxelSizeMicrons;
s.latticeOriginMicrometres={0,0,0};
std::int64_t bottom=static_cast<std::int64_t>(slot.BottomChunkZ)*CHUNK_SIZE*coreMicrons;
std::int64_t top=static_cast<std::int64_t>(slot.TopChunkZ+1)*CHUNK_SIZE*coreMicrons;
if(type==ECaveGeneratorType::FloatingIslands)
{
const auto it=std::find_if(assembly.floatingIslands.begin(),assembly.floatingIslands.end(),
[&](const auto& p){return p.strateIndex==slot.StrateIndex;});
if(it!=assembly.floatingIslands.end())
{bottom=fixedToMicrons(it->manifest.recipe.layoutMinimumZSubvoxels);
top=fixedToMicrons(it->manifest.recipe.layoutMaximumZSubvoxels);}
}
s.interiorBottomMicrometres=bottom;s.interiorTopMicrometres=top;
s.content={"content",bottom,top,true,true,false,"current generator interior","none","none"};
s.transitionMode=TransitionKind(slot.Definition->TransitionType);
s.transitionBlendChunks=slot.Definition->TransitionBlendChunks;
s.transitionWidthMicrometres=static_cast<std::int64_t>(s.transitionBlendChunks)*managerChunkMicrons;
s.transitionWarpAmplitudeMicrometres=s.transitionMode=="Interleaved"
?2*managerChunkMicrons:0;
s.transitionInterpenetrationMicrometres=0;
s.transitionHonouredInCore=false;
s.transitionDispatchPath="upper strate owns lower interface; UVoxelStrateManager::GetGenerationParams can blend legacy vectors, but Core's compiled field selects per-strate/native recipe fields without transition blending";
s.managerLookup="UVoxelStrateManager::GetGeneratorTypeForChunk; upper strate TransitionType owns lower interface";
s.managerChunkWidthMicrometres=managerChunkMicrons;
s.dispatchChunkWidthMicrometres=type==ECaveGeneratorType::FloatingIslands
?static_cast<std::int64_t>(CHUNK_SIZE)*owner.OwnerVoxelSizeMicrons
:static_cast<std::int64_t>(CHUNK_SIZE)*coreMicrons;
if(type==ECaveGeneratorType::SurfaceWorld)
{s.boundDispatchPath="BuildSurfaceStratumPlan: (chunkZ*CHUNK_SIZE) Core voxels; 32 Core voxels per chunk";
s.queryDispatchPath="FStandaloneShapeWorld::NativeSurfaceDensityAt: floor(Z/CHUNK_SIZE), manager lookup; 32 Core voxels per chunk";}
else if(type==ECaveGeneratorType::FloatingIslands)
{s.boundDispatchPath="BuildFloatingIslandStratumPlan: owner chunk * 32 * owner/Core voxel ratio";
s.queryDispatchPath="FStandaloneShapeWorld::NativeFloatingIslandDensityAt: floor(Z/(32*owner/Core ratio)), manager lookup";}
else
{s.boundDispatchPath="legacy strata slot bounds scaled by Core chunk width";
s.queryDispatchPath="legacy Core field; manager lookup uses floor(Z/CHUNK_SIZE)";}
const std::int64_t legacySealMicrons=static_cast<std::int64_t>(std::llround(
std::max(0.0f,slot.Definition->GenerationParams.BoundarySealThickness)
*owner.OwnerVoxelSizeMicrons));
if(type==ECaveGeneratorType::FloatingIslands)
{
const auto it=std::find_if(assembly.floatingIslands.begin(),assembly.floatingIslands.end(),
[&](const auto& p){return p.strateIndex==slot.StrateIndex;});
const std::int64_t seal=it==assembly.floatingIslands.end()?0:fixedToMicrons(
it->manifest.recipe.boundarySealSubvoxels);
s.ceiling={"ceiling",top-seal,top,seal>0,true,false,
"v3 shared upper boundary density seal; one sided; inset from upper edge","upper/interior side","VF_ApplyUpperBoundarySeal"};
s.bedrock={"bedrock",bottom,bottom,false,true,false,
"no lower boundary seal in geometryVersion 3","none","none"};
if(it!=assembly.floatingIslands.end())
{
for(std::size_t n=0;n<it->manifest.plan.islands.size();++n)
{const auto& a=it->manifest.plan.islands[n];ResolvedLayoutAnchorV1 anchor;
anchor.kind="island_anchor";anchor.stableId="island/"+std::to_string(n);anchor.units="1/256 Core voxel";
anchor.values={a.centerSubvoxels[0],a.centerSubvoxels[1],a.centerSubvoxels[2],a.radiusSubvoxels,a.topHalfSubvoxels};
s.anchors.push_back(std::move(anchor));}
}
}
else if(type==ECaveGeneratorType::SurfaceWorld)
{
s.ceiling={"ceiling",top,top,false,true,false,
"no separate volumetric ceiling band; cap profile is internal to content","none",
"Heightfield2D cap profile + AppendStratumBoundaryCeilings fallback mesh"};
s.bedrock={"bedrock",bottom,bottom,false,true,false,"no lower bedrock band","none","none"};
for(const auto& p:assembly.recipe.biomes)
if(p.hasCapProfile)
{ResolvedLayoutAnchorV1 a;a.kind="cap_profile";a.stableId="biome/"+std::to_string(p.stableId);
a.units="1/256 Core voxel";a.values={p.capHeightSubvoxels,p.capUndulationSubvoxels,
p.capWavelengthSubvoxels,p.capDetailAmplitudeSubvoxels,p.capRidgeAmplitudeSubvoxels};s.anchors.push_back(std::move(a));}
const auto plan=std::find_if(assembly.plan.strates.begin(),assembly.plan.strates.end(),
[&](const auto& p){return p.strateIndex==slot.StrateIndex;});
if(plan!=assembly.plan.strates.end())
{
for(std::size_t n=0;n<plan->route.size();++n)
{const auto& p=plan->route[n];ResolvedLayoutAnchorV1 a;a.kind="route_point";a.stableId="route/"+std::to_string(n);
a.units="1/256 Core voxel";a.values={p.positionSubvoxels[0],p.positionSubvoxels[1],p.positionSubvoxels[2],p.radiusSubvoxels};
s.anchors.push_back(std::move(a));}
}
}
else
{
s.ceiling={"ceiling",top,top+legacySealMicrons,false,true,false,
"manager configures a two-sided legacy seal, but Core's compiled analytic field does not apply this density band",
"configured upper side","legacy VF_ApplyBoundarySeal; absent from current Core density path"};
s.bedrock={"bedrock",bottom-legacySealMicrons,bottom,false,true,false,
"manager configures a two-sided legacy seal, but Core's compiled analytic field does not apply this density band; breakable by default",
"configured lower side","legacy VF_ApplyBoundarySeal; absent from current Core density path"};
}
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;
ResolvedLayoutAnchorV1 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));
}
return out.finalize(error);
}
} // namespace StandaloneWorldAssemblyPrototype } // namespace StandaloneWorldAssemblyPrototype