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:
1 parent
81e2452a3d
commit
bd68aee1ca
16 files changed
+939
-62
No files matched your search
@@ -2502,23 +2502,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
{bestAlignment=alignment;phase=candidate;}
|
||||
}
|
||||
}
|
||||
if(hasUpperRoute&&upperIsSurfaceWorld)
|
||||
{
|
||||
const auto& directionAtPhase=directions[phase];
|
||||
const std::int64_t radialAlignment=upperMouth[0]*directionAtPhase[0]
|
||||
+upperMouth[1]*directionAtPhase[1];
|
||||
if(radialAlignment<0)
|
||||
{
|
||||
std::int64_t bestOutwardAlignment=std::numeric_limits<std::int64_t>::min();
|
||||
for(std::uint32_t candidate=0;candidate<directions.size();++candidate)
|
||||
{
|
||||
const std::int64_t alignment=upperMouth[0]*directions[candidate][0]
|
||||
+upperMouth[1]*directions[candidate][1];
|
||||
if(alignment>bestOutwardAlignment)
|
||||
{bestOutwardAlignment=alignment;phase=candidate;}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool clockwise=(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
|
||||
connection,0x44495245u)&1u)!=0;
|
||||
if(style==0&&anchoredIslandPassage&&!anchoredSpiral&&passageAnchor)
|
||||
|
||||
@@ -973,7 +973,7 @@ public:
|
||||
float composed=density;
|
||||
float deferredSupportDensity=std::numeric_limits<float>::infinity();
|
||||
const auto composeRouteMouthSupport=[&](std::int32_t strateIndex,
|
||||
const std::array<std::int64_t,3>& mouth,bool departure)
|
||||
const std::array<std::int64_t,3>& mouth)
|
||||
{
|
||||
// Surface-route treads may reinforce native solid terrain only.
|
||||
if(!(density<0.0f))return;
|
||||
@@ -985,25 +985,18 @@ public:
|
||||
ResolvedLayout->strates.end(),[strateIndex](const auto& value)
|
||||
{return value.strateIndex==strateIndex;});
|
||||
if(stratum==ResolvedLayout->strates.end())return;
|
||||
std::array<const VoxelForgeCore::ResolvedLayoutAnchorV2*,5> route{};
|
||||
std::size_t routeCount=0;
|
||||
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> route;
|
||||
route.reserve(stratum->anchors.size());
|
||||
for(const auto& anchor:stratum->anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||
{
|
||||
if(departure)
|
||||
{
|
||||
if(routeCount<route.size())route[routeCount++]=&anchor;
|
||||
else
|
||||
{
|
||||
std::move(route.begin()+1,route.end(),route.begin());
|
||||
route.back()=&anchor;
|
||||
}
|
||||
}
|
||||
else if(routeCount<route.size())route[routeCount++]=&anchor;
|
||||
}
|
||||
if(routeCount<2)return;
|
||||
const std::size_t lastSegment=routeCount-1;
|
||||
route.push_back(&anchor);
|
||||
if(route.size()<2)return;
|
||||
const std::size_t lastSegment=route.size()-1;
|
||||
constexpr double MouthSupportBlendMetres=0.45;
|
||||
// Route anchors encode the player center at half the authored capsule
|
||||
// height above the tread. Keep the support deck below that same foot
|
||||
// plane so the route audit's 0.5 m support search reaches solid tread.
|
||||
constexpr double RouteCenterAboveFootMetres=0.88;
|
||||
const double mouthBlendWidth=MouthSupportBlendMetres*qPerMetre;
|
||||
double mouthSupportDistance=std::numeric_limits<double>::infinity();
|
||||
for(std::size_t index=0;index<lastSegment;++index)
|
||||
@@ -1023,8 +1016,8 @@ public:
|
||||
const double horizontalX=qx-centerX,horizontalY=qy-centerY;
|
||||
const double horizontalDistance=DeterministicSqrt(
|
||||
horizontalX*horizontalX+horizontalY*horizontalY);
|
||||
const double floorQ=centerZ-(VoxelForgeCore::WorldAssemblyEvaluatorVersion>=184
|
||||
?playerCenterAboveFloorQ+0.25*qPerMetre:1.20*qPerMetre);
|
||||
const double floorQ=centerZ-RouteCenterAboveFootMetres*qPerMetre
|
||||
-0.25*qPerMetre;
|
||||
const double sideDistance=horizontalDistance-halfWidth;
|
||||
const double topDistance=qz-floorQ;
|
||||
const double bottomDistance=floorQ-qPerMetre-qz;
|
||||
@@ -1883,7 +1876,7 @@ public:
|
||||
if(!tileHasSupport
|
||||
||ActivePassageTileQueries->hasMouthSupport(passageIndex))
|
||||
composeRouteMouthSupport(passage.upperStrateIndex,
|
||||
passage.upperMouthSubvoxels,true);
|
||||
passage.upperMouthSubvoxels);
|
||||
}
|
||||
composed=std::min(composed,deferredSupportDensity);
|
||||
return composed;
|
||||
@@ -4986,7 +4979,7 @@ private:
|
||||
// Add a subtle host-rock root only when the unmodified column lacks the
|
||||
// vertical relief required for walkable passage support.
|
||||
const double rootTaper=crossing.needsRootTaper
|
||||
?0.08*qPerMetre*smoothStep((t-0.35)/0.50):0.0;
|
||||
?0.30*qPerMetre*smoothStep((t-0.35)/0.50):0.0;
|
||||
const double radius=static_cast<double>(crossing.minimumRadiusSubvoxels)
|
||||
+std::max(0.15*qPerMetre,irregularity)+rootTaper+baseFlare+ceilingFlare;
|
||||
const double radialDistanceField=radialDistance-radius;
|
||||
@@ -10912,6 +10905,75 @@ private:
|
||||
|
||||
bool ValidatePassageRoutes()
|
||||
{
|
||||
if(Owner&&Owner->bHasUnrealResolvedCorePlan
|
||||
&&ResolvedLayout&&!ResolvedLayout->passages.empty())
|
||||
{
|
||||
constexpr double SampleStep=0.5;
|
||||
uint32 PassageIndex=0;
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
const auto& points=passage.controlPoints;
|
||||
if(points.size()<2){++PassageIndex;continue;}
|
||||
uint64 routeSamples=0,routeFailures=0;
|
||||
int32 firstFailure[3]={0,0,0};
|
||||
for(std::size_t segment=0;segment+1<points.size();++segment)
|
||||
{
|
||||
const auto& a=points[segment].positionSubvoxels;
|
||||
const auto& b=points[segment+1].positionSubvoxels;
|
||||
const double ax=static_cast<double>(a[0])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double ay=static_cast<double>(a[1])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double az=static_cast<double>(a[2])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double bx=static_cast<double>(b[0])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double by=static_cast<double>(b[1])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double bz=static_cast<double>(b[2])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double length=std::sqrt((bx-ax)*(bx-ax)+(by-ay)*(by-ay)+(bz-az)*(bz-az));
|
||||
const int32 count=std::max(1,static_cast<int32>(std::ceil(length/SampleStep)));
|
||||
uint64 segmentFailures=0;
|
||||
for(int32 sample=0;sample<=count;++sample)
|
||||
{
|
||||
const double t=static_cast<double>(sample)/count;
|
||||
const int32 vx=static_cast<int32>(std::lround(ax+(bx-ax)*t));
|
||||
const int32 vy=static_cast<int32>(std::lround(ay+(by-ay)*t));
|
||||
const int32 vz=static_cast<int32>(std::lround(az+(bz-az)*t));
|
||||
++Stats.WalkSamples;
|
||||
++routeSamples;
|
||||
const float density=DensityAt(static_cast<float>(ax+(bx-ax)*t),
|
||||
static_cast<float>(ay+(by-ay)*t),
|
||||
static_cast<float>(az+(bz-az)*t));
|
||||
if(!std::isfinite(density)||density<0.0f)
|
||||
{
|
||||
++Stats.WalkFailures;
|
||||
++routeFailures;
|
||||
++segmentFailures;
|
||||
if(routeFailures==1)
|
||||
{firstFailure[0]=vx;firstFailure[1]=vy;firstFailure[2]=vz;}
|
||||
}
|
||||
}
|
||||
if(segmentFailures)
|
||||
std::fprintf(stderr,"[WalkabilitySegment] source=resolved_core_layout passage=%s segment=%zu failures=%llu/%llu length=%.2f\n",
|
||||
passage.stableId.c_str(),segment,
|
||||
static_cast<unsigned long long>(segmentFailures),
|
||||
static_cast<unsigned long long>(std::max(1,static_cast<int32>(std::ceil(length/SampleStep))+1)),
|
||||
length);
|
||||
}
|
||||
if(routeFailures)
|
||||
std::fprintf(stderr,"[WalkabilityRoute] source=resolved_core_layout passage=%u id=%s points=%zu failures=%llu/%llu first_solid=(%d,%d,%d)\n",
|
||||
PassageIndex,passage.stableId.c_str(),points.size(),
|
||||
static_cast<unsigned long long>(routeFailures),
|
||||
static_cast<unsigned long long>(routeSamples),firstFailure[0],
|
||||
firstFailure[1],firstFailure[2]);
|
||||
++PassageIndex;
|
||||
}
|
||||
if(Stats.WalkFailures)
|
||||
std::fprintf(stderr,"[Walkability] FAIL source=resolved_core_layout %llu/%llu compiled Core passage centerline samples are solid\n",
|
||||
static_cast<unsigned long long>(Stats.WalkFailures),
|
||||
static_cast<unsigned long long>(Stats.WalkSamples));
|
||||
else
|
||||
std::printf("[Walkability] PASS source=resolved_core_layout %llu/%llu compiled Core passage centerline samples remain open\n",
|
||||
static_cast<unsigned long long>(Stats.WalkSamples),
|
||||
static_cast<unsigned long long>(Stats.WalkSamples));
|
||||
return Stats.WalkFailures==0;
|
||||
}
|
||||
constexpr double SampleStep=0.5;
|
||||
uint32 PassageIndex=0;
|
||||
for(const FVoxelPassage& P:Owner->Manager->GetPassages())
|
||||
|
||||
Reference in new issue
Block a user