streaming: speed-aware scheduler with hysteresis (L80); arrival near-first, fast travel keeps the horizon

Policy 3 (headless + Unreal): walkable ring always first; at rest/arrival LOD0-2 before far;
walking interleaves near:far ~4:1 toward heading; fast travel admits far coarse tiles right after
the walkable ring. Hysteresis: walking 0.75/0.35 m/s, fast 12/8 m/s.
Headless, far holes while moving 30 m/s: L77 9.0% / L79 77.2% / L80 8.9%. Cold LOD0-2 ready
(one clean protocol, medians): SW 1.30, FI 3.61, TN 3.07, VS 1.43 s.
Unreal 30 m/s traces end 2,233/2,237 loaded (commandlet readiness exit 1) for all policies.
Passage zero-padding cut: ~50% fewer passage iterations on active tiles, max 6.6 cm surface
displacement, no sign flips; not adopted yet (padding sweep next). World unchanged (v213).

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 21:39:21 +01:00
1 parent 2c8a26c0d0
commit b06397df9c
10 files changed
+830 -25

No files matched your search

@@ -1414,7 +1414,7 @@ namespace
std::printf("Usage: %s [owner_world.json] --world-assembler-prototype --stream-benchmark --stream-pipeline [options]\n"
" --workers=N --clip-radius=N --max-clip-level=N --max-level=N\n"
" --camera=X,Y,Z,YAW,PITCH (position in metres) --stream-hash\n"
" --stream-scheduler-policy=0|1|2 (L77 coarse, L78 near, L79 fair share)\n"
" --stream-scheduler-policy=0|1|2|3 (L77 coarse, L78 near, L79 fair, L80 speed-aware)\n"
" --moving-camera-speed-mps=N [--moving-camera-duration-s=N] [--moving-camera-dwell-s=N]\n"
" --stream-hash-tiles (print per-tile hashes; implies --stream-hash)\n"
" --stream-hash-tiles-allow-skips (mesh/topology falsifier with proven uniform skips)\n"
@@ -1600,8 +1600,8 @@ namespace
else if(arg.starts_with("--stream-scheduler-policy="))
{
if(!readInt("--stream-scheduler-policy=",options.StreamingSchedulerPolicy)
||options.StreamingSchedulerPolicy<0||options.StreamingSchedulerPolicy>2)
{std::fprintf(stderr,"Invalid --stream-scheduler-policy (expected 0..2).\n");return false;}
||options.StreamingSchedulerPolicy<0||options.StreamingSchedulerPolicy>3)
{std::fprintf(stderr,"Invalid --stream-scheduler-policy (expected 0..3).\n");return false;}
}
else if(arg.starts_with("--moving-camera-speed-mps="))
{
@@ -1676,7 +1676,7 @@ namespace
if(options.MovingCameraTrace&&(!options.StreamPipeline||!options.WorldAssemblerPrototype))
{std::fprintf(stderr,"--moving-camera-speed-mps requires --world-assembler-prototype --stream-pipeline\n");return false;}
if(options.MovingCameraTrace&&options.StreamingSchedulerPolicy<0)
{std::fprintf(stderr,"--moving-camera-speed-mps requires --stream-scheduler-policy=0|1|2\n");return false;}
{std::fprintf(stderr,"--moving-camera-speed-mps requires --stream-scheduler-policy=0|1|2|3\n");return false;}
if(options.LandmarkL45RampControl&&!options.LandmarkChamberDemo)
{std::fprintf(stderr,"--landmark-l45-ramp-control requires --landmark-chamber-demo\n");return false;}
if(options.AuditOreVeinYield&&!options.OreVeinsDemo)
+16 -6
View File
@@ -38,6 +38,8 @@ namespace StandaloneStreamBenchmark
double sumAbsoluteDelta=0.0;
double maxSurfaceDisplacementVoxels=0.0;
double sumSurfaceDisplacementVoxels=0.0;
std::array<std::int32_t,3> maxSurfaceDisplacementStartVoxel{};
std::int32_t maxSurfaceDisplacementAxis=-1;
bool valid=false;
};
@@ -94,7 +96,7 @@ namespace StandaloneStreamBenchmark
}
const auto CompareEdge=[&](std::int32_t X0,std::int32_t Y0,std::int32_t Z0,
std::int32_t X1,std::int32_t Y1,std::int32_t Z1)
std::int32_t X1,std::int32_t Y1,std::int32_t Z1,std::int32_t Axis)
{
const bool CrossA=EdgeCrosses(A.debugDensitySamples,X0,Y0,Z0,X1,Y1,Z1);
const bool CrossB=EdgeCrosses(B.debugDensitySamples,X0,Y0,Z0,X1,Y1,Z1);
@@ -107,8 +109,13 @@ namespace StandaloneStreamBenchmark
const double TA=static_cast<double>(A0)/(static_cast<double>(A0)-A1);
const double TB=static_cast<double>(B0)/(static_cast<double>(B0)-B1);
const double Displacement=std::abs(TA-TB)*Step;
Result.maxSurfaceDisplacementVoxels=std::max(
Result.maxSurfaceDisplacementVoxels,Displacement);
if(Displacement>Result.maxSurfaceDisplacementVoxels)
{
Result.maxSurfaceDisplacementVoxels=Displacement;
Result.maxSurfaceDisplacementStartVoxel={
Origin.X+(X0-1)*Step,Origin.Y+(Y0-1)*Step,Origin.Z+(Z0-1)*Step};
Result.maxSurfaceDisplacementAxis=Axis;
}
Result.sumSurfaceDisplacementVoxels+=Displacement;
++Result.sharedCrossingEdges;
};
@@ -116,9 +123,9 @@ namespace StandaloneStreamBenchmark
for(std::int32_t Y=0;Y<Dim;++Y)
for(std::int32_t X=0;X<Dim;++X)
{
if(X+1<Dim)CompareEdge(X,Y,Z,X+1,Y,Z);
if(Y+1<Dim)CompareEdge(X,Y,Z,X,Y+1,Z);
if(Z+1<Dim)CompareEdge(X,Y,Z,X,Y,Z+1);
if(X+1<Dim)CompareEdge(X,Y,Z,X+1,Y,Z,0);
if(Y+1<Dim)CompareEdge(X,Y,Z,X,Y+1,Z,1);
if(Z+1<Dim)CompareEdge(X,Y,Z,X,Y,Z+1,2);
}
Result.valid=true;
return Result;
@@ -5014,6 +5021,9 @@ namespace StandaloneStreamBenchmark
const double MovingSeconds=std::min(MovingCameraDurationSeconds,ElapsedSeconds);
FVector Camera=CameraPositionVoxels;
Camera.X+=static_cast<float>(MovingCameraSpeedMetersPerSecond*MovingSeconds/VoxelMetres);
Streamer.SetSchedulerMotionSpeedMetersPerSecond(
ElapsedSeconds<MovingCameraDurationSeconds
?MovingCameraSpeedMetersPerSecond:0.0);
DrainReady();
if(SampleIndex==0)
{
+118 -5
View File
@@ -552,7 +552,37 @@ public:
}
void SetSchedulerPolicy(int32 Policy)
{SchedulerPolicy=std::clamp(Policy,-1,2);}
{SchedulerPolicy=std::clamp(Policy,-1,3);}
void SetSchedulerMotionSpeedMetersPerSecond(double Speed)
{
const double ClampedSpeed=std::max(0.0,Speed);
int32 CurrentMode=SpeedAwareSchedulerMode.load(std::memory_order_relaxed);
for(;;)
{
int32 NextMode=CurrentMode;
if(CurrentMode==0)
{
if(ClampedSpeed>=12.0)NextMode=2;
else if(ClampedSpeed>=0.75)NextMode=1;
}
else if(CurrentMode==1)
{
if(ClampedSpeed>=12.0)NextMode=2;
else if(ClampedSpeed<=0.35)NextMode=0;
}
else if(ClampedSpeed<=8.0)
{
NextMode=ClampedSpeed<=0.35?0:1;
}
if(NextMode==CurrentMode
||SpeedAwareSchedulerMode.compare_exchange_weak(
CurrentMode,NextMode,std::memory_order_relaxed))
{
break;
}
}
SchedulerMotionSpeedMetersPerSecond.store(ClampedSpeed,std::memory_order_relaxed);
}
bool QueuePriorityTasks(std::vector<std::function<void()>> Tasks)
{
@@ -800,8 +830,7 @@ private:
for(const auto& Pair:CancelledByCrossing)Stats.CancelledByCrossing.push_back(Pair);
std::vector<FVoxelTileKey> Prioritized=NewDesired.Tiles;
const int32 ActiveSchedulerPolicy=SchedulerPolicy<0
?(IsCoarseFirstSchedulingEnabled()?2:3):SchedulerPolicy;
const int32 ActiveSchedulerPolicy=SchedulerPolicy<0?3:SchedulerPolicy;
if(ActiveSchedulerPolicy<=2)
{
// Policy 0 retains L77 coarse-first ordering after the local support ring.
@@ -865,6 +894,84 @@ private:
Prioritized.swap(FairOrder);
}
}
else if(ActiveSchedulerPolicy==3)
{
std::vector<FVoxelTileKey> WalkableRing,NearTiles,FarTiles,SpeedAwareOrder;
WalkableRing.reserve(27);
NearTiles.reserve(Prioritized.size());
FarTiles.reserve(Prioritized.size());
SpeedAwareOrder.reserve(Prioritized.size());
for(const FVoxelTileKey& Key:Prioritized)
{
const bool bInWalkableRing=Key.Level==0
&&std::abs(Key.Coord.X-NewDesired.CenterChunk.X)<=1
&&std::abs(Key.Coord.Y-NewDesired.CenterChunk.Y)<=1
&&std::abs(Key.Coord.Z-NewDesired.CenterChunk.Z)<=1;
if(bInWalkableRing)WalkableRing.push_back(Key);
else if(Key.Level<=2)NearTiles.push_back(Key);
else FarTiles.push_back(Key);
}
const auto IsAhead=[&](const FVoxelTileKey& Key)
{
if(TravelDirection.X*TravelDirection.X+TravelDirection.Y*TravelDirection.Y<=1.0e-12f)
return false;
const double Scale=static_cast<double>(1<<Key.Level);
const double OffsetX=(static_cast<double>(Key.Coord.X)+0.5)*Scale-NewDesired.CenterChunk.X;
const double OffsetY=(static_cast<double>(Key.Coord.Y)+0.5)*Scale-NewDesired.CenterChunk.Y;
return OffsetX*TravelDirection.X+OffsetY*TravelDirection.Y>0.0;
};
auto SortGroup=[&](std::vector<FVoxelTileKey>& Group,bool bNearFirst)
{
std::sort(Group.begin(),Group.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
{
if(A.Level!=B.Level)return bNearFirst?A.Level<B.Level:A.Level>B.Level;
const bool bAheadA=IsAhead(A),bAheadB=IsAhead(B);
if(bAheadA!=bAheadB)return bAheadA;
const double DA=StandaloneWorldMesher::TileDistanceSquaredChunks(A,NewDesired.CenterChunk);
const double DB=StandaloneWorldMesher::TileDistanceSquaredChunks(B,NewDesired.CenterChunk);
if(DA!=DB)return DA<DB;
if(A.Coord.Z!=B.Coord.Z)return A.Coord.Z<B.Coord.Z;
if(A.Coord.Y!=B.Coord.Y)return A.Coord.Y<B.Coord.Y;
return A.Coord.X<B.Coord.X;
});
};
SortGroup(WalkableRing,true);
SortGroup(NearTiles,true);
SortGroup(FarTiles,false);
SpeedAwareOrder.insert(SpeedAwareOrder.end(),WalkableRing.begin(),WalkableRing.end());
const double SpeedMetersPerSecond=SchedulerMotionSpeedMetersPerSecond.load(
std::memory_order_relaxed);
const int32 SpeedAwareMode=SpeedAwareSchedulerMode.load(std::memory_order_relaxed);
if(SpeedAwareMode==2)
{
// Fast travel keeps parent coverage ahead of detail work.
SpeedAwareOrder.insert(SpeedAwareOrder.end(),FarTiles.begin(),FarTiles.end());
SpeedAwareOrder.insert(SpeedAwareOrder.end(),NearTiles.begin(),NearTiles.end());
}
else if(SpeedAwareMode==1)
{
// Walking reserves a speed-scaled share for the horizon after the support ring.
const size_t NearPerFar=static_cast<size_t>(std::clamp(
static_cast<int>(std::lround(6.0/(1.0+SpeedMetersPerSecond/4.0))),1,4));
size_t NearIndex=0,FarIndex=0;
while(NearIndex<NearTiles.size())
{
const size_t NearEnd=std::min(NearTiles.size(),NearIndex+NearPerFar);
SpeedAwareOrder.insert(SpeedAwareOrder.end(),
NearTiles.begin()+NearIndex,NearTiles.begin()+NearEnd);
NearIndex=NearEnd;
if(FarIndex<FarTiles.size())SpeedAwareOrder.push_back(FarTiles[FarIndex++]);
}
SpeedAwareOrder.insert(SpeedAwareOrder.end(),FarTiles.begin()+FarIndex,FarTiles.end());
}
else
{
// Arrival and dwell finish near LOD0-2 before low-priority horizon work.
SpeedAwareOrder.insert(SpeedAwareOrder.end(),NearTiles.begin(),NearTiles.end());
SpeedAwareOrder.insert(SpeedAwareOrder.end(),FarTiles.begin(),FarTiles.end());
}
Prioritized.swap(SpeedAwareOrder);
}
else
{
std::vector<VoxelForgeCore::TileKey> Unordered;Unordered.reserve(NewDesired.Tiles.size());
@@ -1084,8 +1191,11 @@ private:
std::unique_lock<std::mutex> Lock(Mutex);
const auto HasEligibleTile=[&]()
{
return !Queue.empty()&&(SchedulerPolicy!=1||NearOutstandingJobs==0
||Queue.front().Key.Level<=2);
const bool bStationaryNearGate=SchedulerPolicy==3
&&SpeedAwareSchedulerMode.load(std::memory_order_relaxed)==0
&&NearOutstandingJobs>0;
return !Queue.empty()&&((SchedulerPolicy!=1&&!bStationaryNearGate)
||NearOutstandingJobs==0||Queue.front().Key.Level<=2);
};
Wake.wait(Lock,[&](){return bStop||!PriorityTasks.empty()||HasEligibleTile();});
if(bStop)return;
@@ -1250,6 +1360,9 @@ private:
bool bHasLastCameraPosition=false;
int32 WorkerCount=8;
int32 SchedulerPolicy=-1;
std::atomic<double> SchedulerMotionSpeedMetersPerSecond{0.0};
// Match Unreal's Schmitt thresholds to keep noisy speed samples from flapping modes.
std::atomic<int32> SpeedAwareSchedulerMode{0};
bool bHashDensity=false;
const FStandaloneMeshCache* MeshCache=nullptr;
int32 MaxLevelFilter=-1;