wip(unreal): round 5 (stopped early) -- all 15 passage/strate probes walkable
Luna, round 5 goal 1 only (stopped by owner decision before the SurfaceWorld port): - Worm and Cascading passages now pass walkability; the wide floor stays clipped while the narrow route core stays open. - Hidden third failure found and fixed: long VerticalShafts diagonal legs were dropped from Core's spatial bins by a 100,000-bin safety cutoff (invisible to density queries); conservative bins restore them (18,570/18,570). - All 15 saved probes pass a walkability-only gate; build-time walkability gate added. NOT re-verified before the stop: W1/W8/W16 hashes, BuildHost UBT/actor run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
d696463c68
commit
7661fe9d24
3 files changed
+120
-14
No files matched your search
@@ -341,7 +341,11 @@ public:
|
||||
// Route validation is world-wide. Detail regions reuse the same authored route data and
|
||||
// do not need to repeat the 5,642-point world audit for every entering tile group.
|
||||
Substage=Clock::now();
|
||||
if(bValidatePassageRoutes)ValidatePassageRoutes();
|
||||
if(bValidatePassageRoutes&&!ValidatePassageRoutes())
|
||||
{
|
||||
Error="compiled shape world contains a solid passage centreline sample";
|
||||
return false;
|
||||
}
|
||||
Stats.PassageValidationSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
|
||||
Substage=Clock::now();
|
||||
RebuildSharedFieldPrimitives();
|
||||
@@ -2640,14 +2644,16 @@ private:
|
||||
const float CoreRadiusB=std::min(2.5f,RadiusB*0.45f);
|
||||
const double CoreSlopeA=CoreRadiusA<2.5f-1.0e-4?RadiusSlopeA*0.45:0.0;
|
||||
const double CoreSlopeB=CoreRadiusB<2.5f-1.0e-4?RadiusSlopeB*0.45:0.0;
|
||||
// Keep a narrow, un-clipped air core through every authored control segment.
|
||||
// The larger outer tube owns the D-section floor; its interpolated floor curve
|
||||
// can bow above the path on sharp drops, so clipping this safety bore would
|
||||
// close the passage precisely where Cascading/Worm need the continuity repair.
|
||||
// Garder un petit noyau d'air sans coupe de plancher dans chaque segment.
|
||||
// Le tube large porte le plancher en D; sa courbe peut depasser l'axe dans les
|
||||
// chutes brusques et fermer la route si ce noyau herite de la meme coupe.
|
||||
AddTaperedCapsule(Points[I],Points[I+1],CoreRadiusA,
|
||||
CoreRadiusB,true,true,true,ZA,ZB,
|
||||
CoreRadiusB,true,true,false,ZA,ZB,
|
||||
CoreExtendStart,CoreExtendEnd);
|
||||
SetCurvedFloorClip(Primitives.back(),Points[I],Points[I+1],ZA,ZB,
|
||||
FloorTangents[static_cast<size_t>(I)],FloorTangents[static_cast<size_t>(I+1)]);
|
||||
++Stats.CurvedFloorSegments;
|
||||
Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation,
|
||||
Primitives.back().FloorCurveMaxDeviation);
|
||||
FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],CoreRadiusA,
|
||||
Points[I+1],CoreRadiusB);
|
||||
SetCapsuleRadiusProfile(Primitives.back(),Points[I],CoreRadiusA,CoreSlopeA,
|
||||
@@ -2869,7 +2875,59 @@ private:
|
||||
const int32 X0=FloorDiv(P.Min[0]-DetailBand,BinResolution),X1=FloorDiv(P.Max[0]+DetailBand,BinResolution);
|
||||
const int32 Y0=FloorDiv(P.Min[1]-DetailBand,BinResolution),Y1=FloorDiv(P.Max[1]+DetailBand,BinResolution);
|
||||
const int32 Z0=FloorDiv(P.Min[2]-DetailBand,BinResolution),Z1=FloorDiv(P.Max[2]+DetailBand,BinResolution);
|
||||
if(static_cast<int64>(X1-X0+1)*(Y1-Y0+1)*(Z1-Z0+1)>100000)continue;
|
||||
const int64 BoundingBinCount=static_cast<int64>(X1-X0+1)*(Y1-Y0+1)*(Z1-Z0+1);
|
||||
if(BoundingBinCount>100000)
|
||||
{
|
||||
// Long diagonal passages have a huge AABB even though their capsule is narrow.
|
||||
// Rasterize the centerline with a conservative bin halo instead of dropping the
|
||||
// primitive from analytic queries outside the packed near region.
|
||||
// Les longues galeries diagonales ont une grande AABB mais un tube fin.
|
||||
// Echantillonner l'axe avec une marge prudente garde les requetes analytiques.
|
||||
if(!P.bCapsule)continue;
|
||||
const double BinWidth=static_cast<double>(BinResolution);
|
||||
const double AxisStep=std::max(1.0,BinWidth*0.5);
|
||||
const int32 AxisSamples=std::max(1,static_cast<int32>(std::ceil(
|
||||
P.CapsuleAxisLength/AxisStep)));
|
||||
const double CapsuleRadius=std::max(P.CapsuleStartRadius,P.CapsuleEndRadius);
|
||||
const int32 Halo=std::clamp(static_cast<int32>(std::ceil(
|
||||
(CapsuleRadius+DetailBand)/BinWidth))+1,1,8);
|
||||
std::vector<FBinKey> PrimitiveBins;
|
||||
const int64 HaloWidth=2*Halo+1;
|
||||
const uint64 Reserve=std::min<uint64>(
|
||||
static_cast<uint64>(AxisSamples+1)*HaloWidth*HaloWidth*HaloWidth,
|
||||
1000000ull);
|
||||
PrimitiveBins.reserve(static_cast<size_t>(Reserve));
|
||||
for(int32 Sample=0;Sample<=AxisSamples;++Sample)
|
||||
{
|
||||
const double T=static_cast<double>(Sample)/AxisSamples;
|
||||
const int32 CX=static_cast<int32>(std::floor(P.CapsuleStartWorld[0]
|
||||
+P.CapsuleAxisDelta[0]*T));
|
||||
const int32 CY=static_cast<int32>(std::floor(P.CapsuleStartWorld[1]
|
||||
+P.CapsuleAxisDelta[1]*T));
|
||||
const int32 CZ=static_cast<int32>(std::floor(P.CapsuleStartWorld[2]
|
||||
+P.CapsuleAxisDelta[2]*T));
|
||||
const int32 BX=FloorDiv(CX,BinResolution),BY=FloorDiv(CY,BinResolution);
|
||||
const int32 BZ=FloorDiv(CZ,BinResolution);
|
||||
for(int32 DZ=-Halo;DZ<=Halo;++DZ)
|
||||
for(int32 DY=-Halo;DY<=Halo;++DY)
|
||||
for(int32 DX=-Halo;DX<=Halo;++DX)
|
||||
PrimitiveBins.push_back({BX+DX,BY+DY,BZ+DZ});
|
||||
}
|
||||
std::sort(PrimitiveBins.begin(),PrimitiveBins.end(),[](const FBinKey& A,const FBinKey& B)
|
||||
{
|
||||
if(A.X!=B.X)return A.X<B.X;
|
||||
if(A.Y!=B.Y)return A.Y<B.Y;
|
||||
return A.Z<B.Z;
|
||||
});
|
||||
PrimitiveBins.erase(std::unique(PrimitiveBins.begin(),PrimitiveBins.end()),
|
||||
PrimitiveBins.end());
|
||||
for(const FBinKey& Key:PrimitiveBins)
|
||||
{
|
||||
SpatialBins[Key].push_back(Id);
|
||||
if(bCriticalAir)CriticalAirBins[Key].push_back(Id);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for(int32 Z=Z0;Z<=Z1;++Z)for(int32 Y=Y0;Y<=Y1;++Y)for(int32 X=X0;X<=X1;++X)
|
||||
{
|
||||
const FBinKey Key{X,Y,Z};
|
||||
@@ -4660,28 +4718,55 @@ private:
|
||||
return RockDistance>=0.0f;
|
||||
}
|
||||
|
||||
void ValidatePassageRoutes()
|
||||
bool ValidatePassageRoutes()
|
||||
{
|
||||
constexpr double SampleStep=0.5;
|
||||
uint32 PassageIndex=0;
|
||||
for(const FVoxelPassage& P:Owner->Manager->GetPassages())
|
||||
{
|
||||
const auto& Points=P.ControlPoints;
|
||||
if(Points.Num()<2)continue;
|
||||
if(Points.Num()<2){++PassageIndex;continue;}
|
||||
uint64 RouteSamples=0,RouteFailures=0,RouteCritical=0;
|
||||
int32 FirstFailure[3]={0,0,0};bool bFirstFailureCritical=false;
|
||||
for(int32 I=0;I+1<Points.Num();++I)
|
||||
{
|
||||
const FVector A=Points[I],B=Points[I+1];
|
||||
const double L=std::sqrt((B.X-A.X)*(B.X-A.X)+(B.Y-A.Y)*(B.Y-A.Y)+(B.Z-A.Z)*(B.Z-A.Z));
|
||||
const int32 Count=std::max(1,static_cast<int32>(std::ceil(L/SampleStep)));
|
||||
uint64 SegmentSamples=0,SegmentFailures=0,SegmentCritical=0;
|
||||
for(int32 J=0;J<=Count;++J)
|
||||
{
|
||||
const double T=static_cast<double>(J)/Count;
|
||||
const double X=A.X+(B.X-A.X)*T,Y=A.Y+(B.Y-A.Y)*T,Z=A.Z+(B.Z-A.Z)*T;
|
||||
++Stats.WalkSamples;
|
||||
if(IsRock(static_cast<int32>(std::lround(X)),
|
||||
static_cast<int32>(std::lround(Y)),static_cast<int32>(std::lround(Z))))
|
||||
++Stats.WalkFailures;
|
||||
++RouteSamples;++SegmentSamples;
|
||||
const int32 VX=static_cast<int32>(std::lround(X));
|
||||
const int32 VY=static_cast<int32>(std::lround(Y));
|
||||
const int32 VZ=static_cast<int32>(std::lround(Z));
|
||||
const bool bCritical=IsInsideCriticalAir(VX,VY,VZ);
|
||||
if(bCritical){++RouteCritical;++SegmentCritical;}
|
||||
if(IsRock(VX,VY,VZ))
|
||||
{
|
||||
++Stats.WalkFailures;++RouteFailures;++SegmentFailures;
|
||||
if(RouteFailures==1)
|
||||
{FirstFailure[0]=VX;FirstFailure[1]=VY;FirstFailure[2]=VZ;bFirstFailureCritical=bCritical;}
|
||||
}
|
||||
}
|
||||
if(SegmentFailures)
|
||||
std::fprintf(stderr,"[WalkabilitySegment] passage=%u segment=%d failures=%llu/%llu critical=%llu length=%.2f start=(%.2f,%.2f,%.2f) end=(%.2f,%.2f,%.2f)\n",
|
||||
PassageIndex,I,static_cast<unsigned long long>(SegmentFailures),
|
||||
static_cast<unsigned long long>(SegmentSamples),
|
||||
static_cast<unsigned long long>(SegmentCritical),L,
|
||||
A.X,A.Y,A.Z,B.X,B.Y,B.Z);
|
||||
}
|
||||
if(RouteFailures)
|
||||
std::fprintf(stderr,"[WalkabilityRoute] passage=%u type=%u points=%d failures=%llu/%llu critical_centerline=%llu first_solid=(%d,%d,%d) critical=%u\n",
|
||||
PassageIndex,static_cast<uint32>(P.PassageType),Points.Num(),
|
||||
static_cast<unsigned long long>(RouteFailures),
|
||||
static_cast<unsigned long long>(RouteSamples),
|
||||
static_cast<unsigned long long>(RouteCritical),FirstFailure[0],
|
||||
FirstFailure[1],FirstFailure[2],bFirstFailureCritical?1u:0u);
|
||||
++PassageIndex;
|
||||
}
|
||||
if(Stats.WalkFailures)
|
||||
std::fprintf(stderr,"[Walkability] FAIL %llu/%llu passage centerline samples are solid\n",
|
||||
@@ -4689,6 +4774,7 @@ private:
|
||||
else
|
||||
std::printf("[Walkability] PASS %llu/%llu passage centerline samples remain open; native landing floors retained\n",
|
||||
static_cast<unsigned long long>(Stats.WalkSamples),static_cast<unsigned long long>(Stats.WalkSamples));
|
||||
return Stats.WalkFailures==0;
|
||||
}
|
||||
|
||||
static double DetailAutocorrelation(EDetailVariant Variant)
|
||||
|
||||
@@ -459,3 +459,20 @@ beaches, and biome transitions, then check the opening room and landing. The ful
|
||||
work are in Standalone/UNREAL_INTEGRATION_REPORT.md.
|
||||
|
||||
— Luna
|
||||
|
||||
## Claude, after Unreal round 4 (2026-10-08, night)
|
||||
Thank you. That coverage matrix is the most honest picture we've had of what Core actually is: a fast
|
||||
re-expression of TunnelNetwork-style caves, not the owner's full generator. Saved as `d696463`; I
|
||||
rebuilt the owner's project.
|
||||
|
||||
Two things I take from it:
|
||||
- **Cascading and Worm failing walkability is the most urgent finding.** Entrance-to-exit walkability is
|
||||
the owner's primordial law; nothing ships with a strate a player can't cross.
|
||||
- **The SurfaceWorld bridge is exact, but it brings back legacy cost** (23 s full view vs 0.8 s). It's
|
||||
right as a correctness reference, but speed was the reason for Core. Next: a native Core SurfaceWorld
|
||||
that stays close to the bridge, verified against it.
|
||||
|
||||
I'm putting the bigger question to the owner: port every type natively, or accept the bridge for the
|
||||
rare ones.
|
||||
|
||||
— Claude
|
||||
@@ -1551,6 +1551,7 @@ namespace
|
||||
bool bNeighborhoodReadyReported=false;
|
||||
bool bShapeAuditOnly=false;
|
||||
bool bShapeCompare=false;
|
||||
bool bWalkabilityAuditOnly=false;
|
||||
bool bFarShapeStagePending=false;
|
||||
bool bGpuFarEnabled=false;
|
||||
bool bGpuFarPipelineInitialized=false;
|
||||
@@ -7889,6 +7890,7 @@ int main(int Argc,char** Argv)
|
||||
else if(Arg=="--gpu-far-audit")
|
||||
{App.bGpuFarEnabled=true;App.bGpuFarAuditEnabled=true;App.bShapeWorld=true;App.bForceGenerate=true;App.bQuitAfterClipmap=true;}
|
||||
else if(Arg=="--shape-audit")App.bShapeWorld=true,App.bShapeAuditOnly=true;
|
||||
else if(Arg=="--walkability-audit")App.bShapeWorld=true,App.bWalkabilityAuditOnly=true;
|
||||
else if(Arg=="--grid-stair-audit")bGridStairAudit=true,App.bShapeWorld=true;
|
||||
else if(Arg=="--tile-path-hash-audit")bTilePathHashAudit=true,App.bShapeWorld=true;
|
||||
else if(Arg=="--passage-wall-detail-audit")
|
||||
@@ -8059,7 +8061,7 @@ int main(int Argc,char** Argv)
|
||||
?(StreamBenchmarkMaxLevel<0||StreamBenchmarkMaxLevel>=App.World->FullResClipLevels)
|
||||
:(App.bShapeCompare?ShapeCompareLevel>=App.World->FullResClipLevels
|
||||
:(BenchmarkLevel>=App.World->FullResClipLevels));
|
||||
if(App.bShapeWorld&&(App.bShapeAuditOnly||bGridStairAudit||bTilePathHashAudit||App.bShapeCompare||bStreamBenchmark||BenchmarkLevel>=0)
|
||||
if(App.bShapeWorld&&(App.bShapeAuditOnly||App.bWalkabilityAuditOnly||bGridStairAudit||bTilePathHashAudit||App.bShapeCompare||bStreamBenchmark||BenchmarkLevel>=0)
|
||||
&&!bStreamPipeline
|
||||
&&!App.BuildShapeWorldForCamera(Error,bNeedFarShapeWorld))
|
||||
{std::fprintf(stderr,"Shape interval build failed: %s\n",Error.c_str());return 2;}
|
||||
@@ -8071,6 +8073,7 @@ int main(int Argc,char** Argv)
|
||||
if(!App.Snapshot)
|
||||
{std::fprintf(stderr,"Shape interval build failed: %s\n",Error.c_str());return 2;}
|
||||
}
|
||||
if(App.bWalkabilityAuditOnly)return 0;
|
||||
if(App.bShapeAuditOnly)
|
||||
{
|
||||
if(bPassageDetailAuditOnly)
|
||||
|
||||
Reference in new issue
Block a user