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

@@ -944,6 +944,199 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
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();
if (CanonicalJson.empty())
{