parity: Unreal and headless resolve the same four-strate plan (L70, v214)

- Export commandlet writes the live Core adapter's world_assembly + resolved layout pair;
  the headless owner path imports that pair (manifest/layout equal, layout_chunk_width_q
  validated). owner_world.json re-exported from the owner's staged assets on the Linux host.
- Adapter SW route: Core clearance/radius floors, 0.5 m support search, outward-facing
  tie-break; passage mouth/mid radius floors match Core (36/24 owner voxels).
- FI in Unreal now uses the legacy (compiled analytic) generator unless a plan is imported:
  the auto native-FI plan failed the passage audit on the owner plan (1843 blocked samples,
  945 open-air violations). Owner decides whether native FI returns.
- In-engine spawn traces per strate (3 each): SW GenerateTile 42.7 worker-s vs 20.1 headless
  for equal tile counts; LOD0 p95 45-169 ms in UE.
- Fast tier green; all three passages pass on the shared plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-27 06:22:48 +01:00
1 parent 81e2452a3d
commit bd68aee1ca
16 files changed
+939 -62

No files matched your search

@@ -255,7 +255,10 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
constexpr std::int64_t HalfHeightQ = PlayerHeightQ / 2;
constexpr std::int64_t StepQ = 128;
constexpr std::int64_t ProbeRadiusQ = 174;
constexpr std::int64_t SupportDepthQ = 256;
// The live owner surface recipe can sit slightly below the fixed-point route
// heightfield used during candidate search; allow up to 0.5 m for the final
// resolved-field support probe before rejecting an otherwise clear route.
constexpr std::int64_t SupportDepthQ = 512;
constexpr std::int64_t GradeQ16 = 17560;
constexpr std::int64_t RouteRadiusQ = 4 * WorldAssemblySubvoxelsPerVoxel;
struct FDirection { std::int32_t X; std::int32_t Y; };
@@ -265,6 +268,7 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
struct FCandidate
{
std::int64_t Score = 0;
std::int64_t OutwardProjection = 0;
std::uint64_t EndpointProximityCost = 0;
std::int32_t CenterX = 0;
std::int32_t CenterY = 0;
@@ -281,7 +285,7 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
Constraints.sweepSpacingSubvoxels = StepQ;
Constraints.supportProbeRadiusSubvoxels = ProbeRadiusQ;
Constraints.supportSearchDepthSubvoxels = SupportDepthQ;
Constraints.minimumClearanceSubvoxels = 8;
Constraints.minimumClearanceSubvoxels = 128;
Constraints.minimumSupportProbes = 3;
const Heightfield2D BaseField(Recipe, Stratum);
@@ -361,6 +365,8 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
{
if (A.EndpointProximityCost != B.EndpointProximityCost)
return A.EndpointProximityCost < B.EndpointProximityCost;
if (A.OutwardProjection != B.OutwardProjection)
return A.OutwardProjection > B.OutwardProjection;
if (A.Score != B.Score) return A.Score < B.Score;
if (A.CenterY != B.CenterY) return A.CenterY < B.CenterY;
if (A.CenterX != B.CenterX) return A.CenterX < B.CenterX;
@@ -498,6 +504,13 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
if (Index == 32 && DepartureRadiusQ > 0)
{
Candidate.EndpointProximityCost += static_cast<std::uint64_t>(XQ * XQ + YQ * YQ);
// Approach the surface portal from the world-facing side. The
// resolved passage leaves the mouth along its outward tangent;
// a route that approaches from beyond the mouth overlaps that
// bore and turns its centerline into solid ground.
const std::int64_t OutwardProjection = XQ * Directions[DirectionIndex].X
+ YQ * Directions[DirectionIndex].Y;
Candidate.OutwardProjection = OutwardProjection;
}
const auto Column = BaseField.point(WorldAssemblyFixed(static_cast<double>(X)),
WorldAssemblyFixed(static_cast<double>(Y)), 0);
@@ -1515,10 +1528,10 @@ bool AttachCoreGeometryOnlyLayout(const UVoxelStrateManager* StrateManager,
Config.style = static_cast<std::uint32_t>(Passage.Style);
Config.mouthRadiusSubvoxels = std::max(WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMouthRadiusMicrometres, CoreVoxelMicrometres),
WorldAssemblyMicrometresToSubvoxels(12 * OwnerVoxelMicrometres, CoreVoxelMicrometres));
WorldAssemblyMicrometresToSubvoxels(36 * OwnerVoxelMicrometres, CoreVoxelMicrometres));
Config.midRadiusSubvoxels = std::max(WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMidRadiusMicrometres, CoreVoxelMicrometres),
WorldAssemblyMicrometresToSubvoxels(12 * OwnerVoxelMicrometres, CoreVoxelMicrometres));
WorldAssemblyMicrometresToSubvoxels(24 * OwnerVoxelMicrometres, CoreVoxelMicrometres));
Config.reachMinimumSubvoxels = WorldAssemblyMicrometresToSubvoxels(Slot.PassageReachMinimumMicrometres, CoreVoxelMicrometres);
Config.reachMaximumSubvoxels = WorldAssemblyMicrometresToSubvoxels(Slot.PassageReachMaximumMicrometres, CoreVoxelMicrometres);
Config.distanceMinimumSubvoxels = WorldAssemblyMicrometresToSubvoxels(Slot.PassageDistanceMinimumMicrometres, CoreVoxelMicrometres);
@@ -1635,7 +1648,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Generated.plan.strates.push_back(std::move(Stratum));
++BuiltSurfaceCount;
}
else if (Type == ECaveGeneratorType::FloatingIslands)
// Native floating-island evaluation remains an explicit imported-plan opt-in.
// The default owner path uses the legacy generator for this slot; emitting an
// automatic FI plan here silently switches density dispatch and changes the
// shared passage mouth before the owner has opted into that field.
else if (Type == ECaveGeneratorType::FloatingIslands && bImportedOverride)
{
if (!BuildFloatingIslandStratumPlan(StrateManager, Slot, Generated,
CoreVoxelSizeCm, LayoutChunkWidthSubvoxels, OutError))
@@ -1708,10 +1725,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
}
}
if (Manifest->plan.strates.size() != static_cast<std::size_t>(SurfaceSlotCount)
|| Manifest->floatingIslands.size() != static_cast<std::size_t>(FloatingIslandSlotCount))
|| (!Manifest->floatingIslands.empty()
&& Manifest->floatingIslands.size() != static_cast<std::size_t>(FloatingIslandSlotCount)))
{
OutError = FString::Printf(
TEXT("Core world assembly has %d SurfaceWorld and %d floating-island plans for %d resolved SurfaceWorld and %d FloatingIslands strates."),
TEXT("Core world assembly has %d SurfaceWorld and %d floating-island plans for %d resolved SurfaceWorld strates; an explicit FI plan must cover all %d FloatingIslands strates."),
static_cast<int32>(Manifest->plan.strates.size()),
static_cast<int32>(Manifest->floatingIslands.size()),
SurfaceSlotCount, FloatingIslandSlotCount);
@@ -1805,12 +1823,12 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMouthRadiusMicrometres, ExpectedCoreVoxelMicrons),
WorldAssemblyMicrometresToSubvoxels(
12 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
36 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
PassageConfig.midRadiusSubvoxels = std::max(
WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMidRadiusMicrometres, ExpectedCoreVoxelMicrons),
WorldAssemblyMicrometresToSubvoxels(
12 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
24 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
PassageConfig.reachMinimumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageReachMinimumMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.reachMaximumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
@@ -13559,14 +13559,16 @@ bool VerifyStandaloneCoreAdapter(const FString& Json, FString& OutError)
const std::array<std::uint64_t, 4> Actual = StandaloneCoreAggregateHashes(Rows);
const std::array<std::uint64_t, 4> Expected{
0xdb2991c73215af4cull, 0x8236c5edd7fc8e4full,
0x4c48fa75a543d58eull, 0xbea22e76b34a4dfcull};
0xde345030348f2c5dull, 0xcbf0888004618b87ull,
0x7a11e28e052d69a8ull, 0x52b88607e8dbcbfdull};
const VoxelForgeCore::ShapeBuildStats Stats = CoreWorld.buildStats();
const bool bPassageWalkabilityPassed = Stats.walkSamples == 5642 && Stats.walkFailures == 0;
if (Rows.size() != 2226 || Actual != Expected || !bPassageWalkabilityPassed)
constexpr std::uint64_t ExpectedWalkSamples = 21425;
const bool bPassageWalkabilityPassed = Stats.walkSamples == ExpectedWalkSamples
&& Stats.walkFailures == 0;
if (Rows.size() != 2578 || Actual != Expected || !bPassageWalkabilityPassed)
{
OutError = FString::Printf(
TEXT("Core adapter parity failed at workers=%llu: tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx walk=%llu/%llu expected=5642/5642"),
TEXT("Core adapter parity failed at workers=%llu: tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx walk=%llu/%llu expected_walk=%llu"),
static_cast<unsigned long long>(WorkerLimit),
static_cast<unsigned long long>(Rows.size()),
static_cast<unsigned long long>(Actual[0]),
@@ -13574,12 +13576,13 @@ bool VerifyStandaloneCoreAdapter(const FString& Json, FString& OutError)
static_cast<unsigned long long>(Actual[2]),
static_cast<unsigned long long>(Actual[3]),
static_cast<unsigned long long>(Stats.walkSamples - Stats.walkFailures),
static_cast<unsigned long long>(Stats.walkSamples));
static_cast<unsigned long long>(Stats.walkSamples),
static_cast<unsigned long long>(ExpectedWalkSamples));
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreAdapter] pass schema=1 workers=%llu asset_clip_radius=%d asset_max_level=%d reference_clip_radius=3 reference_max_level=8 tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx walk=5642/5642"),
TEXT("[VoxelForgeCoreAdapter] pass schema=2 workers=%llu asset_clip_radius=%d asset_max_level=%d reference_clip_radius=3 reference_max_level=8 tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx walk=%llu/%llu"),
static_cast<unsigned long long>(WorkerLimit),
OwnerClipRadius,
OwnerMaxLod,
@@ -13587,7 +13590,9 @@ bool VerifyStandaloneCoreAdapter(const FString& Json, FString& OutError)
static_cast<unsigned long long>(Actual[0]),
static_cast<unsigned long long>(Actual[1]),
static_cast<unsigned long long>(Actual[2]),
static_cast<unsigned long long>(Actual[3]));
static_cast<unsigned long long>(Actual[3]),
static_cast<unsigned long long>(Stats.walkSamples - Stats.walkFailures),
static_cast<unsigned long long>(ExpectedWalkSamples));
}
return true;
}
@@ -13861,6 +13866,68 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
Writer->WriteObjectEnd();
Writer->Close();
// Export the exact Core manifest and resolved layout that the Unreal adapter just
// generated from these live settings and slots. The standalone research tools consume
// these snapshots instead of maintaining a second copy of the planning rules.
FString RuntimeSnapshotJson;
if (!VoxelForgeCoreAdapter::SerializeResolvedWorld(
ExportSettings, Manager, 3, 8, StandaloneCoreVoxelSizeCm,
RuntimeSnapshotJson, OutError))
{
return false;
}
FTCHARToUTF8 RuntimeSnapshotUtf8(*RuntimeSnapshotJson);
std::string CoreSnapshotError;
const std::optional<VoxelForgeCore::ResolvedWorldSnapshot> CoreSnapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(RuntimeSnapshotUtf8.Get(),
static_cast<std::size_t>(RuntimeSnapshotUtf8.Length())), CoreSnapshotError);
if (!CoreSnapshot || CoreSnapshot->worldAssembly() == nullptr
|| CoreSnapshot->resolvedLayout() == nullptr)
{
OutError = FString::Printf(
TEXT("Core adapter export did not produce a complete manifest and resolved layout: %s"),
UTF8_TO_TCHAR(CoreSnapshotError.c_str()));
return false;
}
const std::string CoreAssemblyText = CoreSnapshot->worldAssembly()->ToJson();
const std::string CoreLayoutText = CoreSnapshot->resolvedLayout()->ToJson();
if (CoreAssemblyText.empty() || CoreLayoutText.empty())
{
OutError = TEXT("Core adapter export produced an empty canonical manifest or layout.");
return false;
}
const FString CanonicalAssemblyJson = UTF8_TO_TCHAR(CoreAssemblyText.c_str());
const FString CanonicalLayoutJson = UTF8_TO_TCHAR(CoreLayoutText.c_str());
TSharedPtr<FJsonObject> ExportRoot;
const TSharedRef<TJsonReader<>> ExportReader = TJsonReaderFactory<>::Create(Json);
if (!FJsonSerializer::Deserialize(ExportReader, ExportRoot) || !ExportRoot.IsValid())
{
OutError = TEXT("Could not reopen the owner export to attach the Core plan snapshots.");
return false;
}
TSharedPtr<FJsonObject> AssemblyObject;
const TSharedRef<TJsonReader<>> AssemblyReader =
TJsonReaderFactory<>::Create(CanonicalAssemblyJson);
if (!FJsonSerializer::Deserialize(AssemblyReader, AssemblyObject) || !AssemblyObject.IsValid())
{
OutError = TEXT("Could not parse the Core adapter's canonical manifest for export.");
return false;
}
ExportRoot->SetObjectField(TEXT("world_assembly"), AssemblyObject.ToSharedRef());
ExportRoot->SetStringField(TEXT("core_world_assembly_json"), CanonicalAssemblyJson);
ExportRoot->SetStringField(TEXT("resolved_layout_json"), CanonicalLayoutJson);
FString ExportedJson;
const TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> ExportWriter =
TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&ExportedJson);
if (!FJsonSerializer::Serialize(ExportRoot.ToSharedRef(), ExportWriter))
{
OutError = TEXT("Could not serialize the owner export with its canonical Core plan.");
return false;
}
Json = MoveTemp(ExportedJson);
const FString Path = FPaths::Combine(Arguments.OutDirectory, TEXT("owner_world.json"));
if (!FFileHelper::SaveStringToFile(
Json, *Path, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))