wip(assembler): round S9 -- collision look-ahead: ground ring ready on arrival up to 40 m/s and 50 m/s falls

voxel.StreamingCollisionLookahead (default on) predicts the support tile 350 ms
ahead along full 3D velocity and prioritises its 9-tile collision ring. Owner
plan, BuildHost actor: 40 m/s arrival-ready 25/74 -> 74/74 (CVar-off control
15/74); 6/12/25 m/s stay 100%; -Z 50 m/s 93/93. Spawn unchanged (~7.7 s).
Critical LOD0 path at 40 m/s: generation 67 ms p50, RMC body update 38 ms p50.
LOD8 cost counted (native horizon 48 ms of 60), optimisation deferred.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-19 10:56:54 +02:00
1 parent 8440adc742
commit e7586a1256
7 files changed
+507 -70

No files matched your search

+39
View File
@@ -1609,6 +1609,45 @@ The cap kept the observed backlog finite without reinstating the S6/S7 finished-
decline. Full timings, per-center records, quality checks and hashes are in
[`Standalone/Research/WorldAssemblerM3/Round12/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round12/REPORT.md).
### 8.30 Velocity-scaled support-ring lookahead — World Assembler S9 / M3 Round 13
The ring gate uses the support tile, which can sit one tile below the clipmap center on a surface
world. Both scheduler branches must use the same `GetPlayerSupportTile` result for the active 3x3
collision-critical ring and its predicted rings. Predicting from the stream center can spend work on
the wrong vertical slice, so the trace includes both `center` and `walkable_center`.
`voxel.StreamingCollisionLookahead` defaults to `1`; `0` disables only the prediction and is the
negative control. `GetStreamingHeadingStep` transforms velocity into actor space and selects its
dominant axis, including Z for a fall; when motion is zero it falls back to pawn forward. The
lookahead targets 350 ms of travel, rounds up to whole active LOD0 tiles, and clamps the lead to one
or two rings. The scheduler adds the future 3x3 keys only when they already belong to the desired
clipmap, then moves those collision keys into `CriticalDesiredTiles`. It does not reserve more worker
slots, expand the clipmap, cancel in-flight jobs, or change tile arithmetic. `collision_lookahead`
records speed, direction, stream/support centers, promoted-key count and missing desired keys.
On the staged SurfaceWorld owner plan at 25 cm, the S9 `+Y` trace found the ring ready at all 74/74
support-center arrivals through 40 m/s; the corrected-source CVar-off control found 15/74. The
S8 no-lookahead baseline was 25/74. At 50 m/s scripted downward motion, 93/93 support-center
arrivals were ready. The 40 m/s matched on/off initial-ready times were 7.743/7.778 s, so the
prediction did not slow spawn in this pair. These figures support 40 m/s as the highest horizontal
speed tested; they do not measure a higher horizontal ceiling. The vertical trace is a scripted
actor move through the owner plan, not a free-fall physics replay.
For 108 critical LOD0 tile jobs with complete stage joins at 40 m/s, request-to-worker start was
0.02/9.59 ms p50/p95, generation 66.6/84.0 ms, result wait 9.3/17.7 ms, game-thread apply to mesh
submit 0.17/0.24 ms, RMC collision submit 0.01/0.02 ms, and RMC submit-to-body-update 37.6/85.9 ms.
The latter is the observable body-ready interval, not a Chaos-only CPU timer. The current trace saw
no scheduler skips, a 113-result peak under the 256 cap and 33 ms maximum region-page compile with
concurrent admission. Generation and body update dominate the measured tile path; the lookahead
gives those stages time to finish before the support center changes. LOD0-missing clipmap frames
remain separately visible in the Round 13 table; they are not equivalent to a missing support ring.
The scheduler change does not alter field, mesh or collision output, so `WorldAssemblerPlanV1` and
the far-horizon recipe versions stay unchanged. Core-off legacy, M2 fixtures, SurfaceWorld and
FloatingIslands checks passed; the S3/S4 ground, cap and coverage audit passed. The full sweep,
negative control, hashes, stage ledger and the deferred LOD8 optimization plan are in
[`Standalone/Research/WorldAssemblerM3/Round13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round13/REPORT.md).
## 9. Multiplayer model (listen-server first, dedicated-friendly)
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
+12
View File
@@ -1041,6 +1041,18 @@ separate from the region-gate control. Round12 found 11/11 ready support centers
immediately ready at 60 m/s with a 400 m teleport; see the traces under `Saved/BuildHost` and
[`Standalone/Research/WorldAssemblerM3/Round12/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round12/REPORT.md).
### 3.16 Velocity-scaled support-ring lookahead — World Assembler S9 / M3 Round 13
`voxel.StreamingCollisionLookahead` defaults on; `0` is the negative control. The shared
`GetStreamingHeadingStep` uses actor-local motion, including vertical speed. The support tile from
`GetPlayerSupportTile` is the reference for both the active 3x3 critical ring and up to two future
3x3 rings. `GetStreamingCollisionLookaheadTiles` aims 350 ms ahead, rounded up to whole LOD0 tiles
and capped at two. Prediction only promotes keys already inside the desired clipmap. The
`collision_lookahead` trace records stream/support centers, direction, speed, ring count and any
missing desired keys. `VoxelForgeExploreCommandlet.cpp` and `Tools/TestVoxelForgeCoreWorldHost.ps1`
accept a movement vector so the same trace covers horizontal and vertical travel. See ARCHITECTURE
§8.30 and [`Standalone/Research/WorldAssemblerM3/Round13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round13/REPORT.md).
## 4. The density pipeline (most-edited hot path)
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
+186 -65
View File
@@ -197,6 +197,12 @@ namespace
GVoxelForgeStreamingDynamicNearReserve,
TEXT("Keep the authored near-worker reserve while moving or before the walkable ring is ready; default 1 releases idle slots after that gate, 0 restores the legacy fixed reserve."));
int32 GVoxelForgeStreamingCollisionLookahead = 1;
FAutoConsoleVariableRef CVarVoxelForgeStreamingCollisionLookahead(
TEXT("voxel.StreamingCollisionLookahead"),
GVoxelForgeStreamingCollisionLookahead,
TEXT("Request up to two collision rings ahead along player velocity; 0 disables predictive ring admission."));
int32 GVoxelForgeStreamingLegacyPendingResultCap = 0;
FAutoConsoleVariableRef CVarVoxelForgeStreamingLegacyPendingResultCap(
TEXT("voxel.StreamingLegacyPendingResultCap"),
@@ -2056,6 +2062,7 @@ void AVoxelWorld::BeginPlay()
int32 CommandLineSampleStacks = GVoxelForgeSampleStacks;
int32 CommandLineCollisionGate = GVoxelForgeCollisionGate;
int32 CommandLineCoreRegionConcurrentAdmission = GVoxelForgeCoreRegionConcurrentAdmission;
int32 CommandLineStreamingCollisionLookahead = GVoxelForgeStreamingCollisionLookahead;
int32 CommandLineStreamingResultBacklogCap = GVoxelForgeStreamingResultBacklogCap;
int32 CommandLineStep1GenerationCap = GVoxelForgeStep1GenerationCap;
int32 CommandLineStep1ExitAfterFrames = GVoxelForgeStep1ExitAfterFrames;
@@ -2134,6 +2141,12 @@ void AVoxelWorld::BeginPlay()
GVoxelForgeStreamingDynamicNearReserve);
FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingLegacyPendingResultCap="),
GVoxelForgeStreamingLegacyPendingResultCap);
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingCollisionLookahead="),
CommandLineStreamingCollisionLookahead))
{
GVoxelForgeStreamingCollisionLookahead =
CommandLineStreamingCollisionLookahead != 0 ? 1 : 0;
}
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.CoreRegionConcurrentAdmission="),
CommandLineCoreRegionConcurrentAdmission))
{
@@ -6896,8 +6909,8 @@ void AVoxelWorld::Tick(float DeltaTime)
AdvanceHeadlessCollisionGateStressTest(PlayerLastPos, PlayerHeading, PlayerPawn);
AdvanceHeadlessDigMotionTest(PlayerLastPos, PlayerPawn);
PeakObservedPawnSpeedCmPerSecond = FMath::Max(
PeakObservedPawnSpeedCmPerSecond, PlayerHeading.Size2D());
StartupTraceSpeedCmPerSecond = PlayerHeading.Size2D();
PeakObservedPawnSpeedCmPerSecond, PlayerHeading.Size());
StartupTraceSpeedCmPerSecond = PlayerHeading.Size();
UpdatePawnCollisionGate(PlayerPawn, PlayerLastPos, DeltaTime);
RecordHeadlessDigMotionFrame(PlayerPawn, PlayerLastPos, DeltaTime);
ObserveHeadlessStrateCrossingTest(PlayerLastPos, PlayerPawn);
@@ -10253,6 +10266,52 @@ void AVoxelWorld::BuildDesiredTransitionIndex()
}
}
FIntVector AVoxelWorld::GetStreamingHeadingStep(const FVector& PlayerHeading,
const APawn* PlayerPawn) const
{
FVector Heading = GetActorTransform().InverseTransformVectorNoScale(PlayerHeading);
if (Heading.IsNearlyZero() && IsValid(PlayerPawn))
{
Heading = GetActorTransform().InverseTransformVectorNoScale(
PlayerPawn->GetActorForwardVector());
}
const float AbsX = FMath::Abs(Heading.X);
const float AbsY = FMath::Abs(Heading.Y);
const float AbsZ = FMath::Abs(Heading.Z);
if (AbsX <= KINDA_SMALL_NUMBER && AbsY <= KINDA_SMALL_NUMBER
&& AbsZ <= KINDA_SMALL_NUMBER)
{
return FIntVector::ZeroValue;
}
if (AbsX >= AbsY && AbsX >= AbsZ)
{
return FIntVector(Heading.X >= 0.0f ? 1 : -1, 0, 0);
}
if (AbsY >= AbsZ)
{
return FIntVector(0, Heading.Y >= 0.0f ? 1 : -1, 0);
}
return FIntVector(0, 0, Heading.Z >= 0.0f ? 1 : -1);
}
int32 AVoxelWorld::GetStreamingCollisionLookaheadTiles(
const FVector& PlayerHeading, const FIntVector& HeadingStep) const
{
if (GVoxelForgeStreamingCollisionLookahead == 0
|| HeadingStep == FIntVector::ZeroValue || PlayerHeading.Size() < 50.0f)
{
return 0;
}
// Aim 350 ms ahead, round up to a whole level-0 tile, and cap the lead at two tiles.
// Viser 350 ms d'avance, arrondir au tile L0 superieur et limiter l'avance a deux tiles.
const float TileWidthCm = static_cast<float>(CHUNK_SIZE) * GetActiveVoxelSizeCm();
const int32 RequestedTiles = FMath::CeilToInt(
PlayerHeading.Size() * 0.35f / FMath::Max(TileWidthCm, 1.0f));
return FMath::Clamp(RequestedTiles, 1, 2);
}
void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector& PlayerPosition,
APawn* PlayerPawn, const FVector& PlayerHeading,
TArray<FVoxelTileKey>& OutLeavers)
@@ -10279,6 +10338,12 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
{CameraVoxels.X, CameraVoxels.Y, CameraVoxels.Z});
}
const FIntVector LookaheadHeadingStep = GetStreamingHeadingStep(PlayerHeading, PlayerPawn);
const int32 LookaheadTiles = GetStreamingCollisionLookaheadTiles(
PlayerHeading, LookaheadHeadingStep);
int32 LookaheadCollisionTilesAdded = 0;
int32 LookaheadDesiredTilesMissing = 0;
TArray<FVoxelTileKey> CandidateKeys;
TSet<FVoxelTileKey> CandidateSet;
TSet<FVoxelTileKey> BaseVisualTiles;
@@ -10357,6 +10422,49 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
CollisionAnchorTiles.Add(Key);
}
const FVoxelTileKey PlayerTile(GetTileCoordAtLocalCm(WorldToLocalCm(PlayerPosition)), 0);
FVoxelTileKey OccupiedTile = PlayerTile;
FVoxelTileKey SupportTile;
if (GetPlayerSupportTile(PlayerPawn, PlayerPosition, SupportTile))
{
OccupiedTile = SupportTile;
}
// Reserve the future 3x3 collision rings inside the existing LOD0 clipmap.
// Reserver les prochains anneaux 3x3 dans le clipmap LOD0 deja present.
for (int32 Ahead = 1; Ahead <= LookaheadTiles; ++Ahead)
{
const FIntVector AheadCenter = OccupiedTile.Coord + LookaheadHeadingStep * Ahead;
for (int32 Dy = -1; Dy <= 1; ++Dy)
for (int32 Dx = -1; Dx <= 1; ++Dx)
{
const FVoxelTileKey Key(AheadCenter + FIntVector(Dx, Dy, 0), 0);
if (!CandidateSet.Contains(Key))
{
++LookaheadDesiredTilesMissing;
continue;
}
if (!CollisionDesiredTiles.Contains(Key))
{
CollisionDesiredTiles.Add(Key);
++LookaheadCollisionTilesAdded;
}
if (!BaseVisualTiles.Contains(Key)) CollisionAnchorTiles.Add(Key);
}
}
if (LookaheadTiles > 0 && VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("collision_lookahead"), FString::Printf(
TEXT("\"center\":[%d,%d,%d],\"walkable_center\":[%d,%d,%d],"
"\"heading_step\":[%d,%d,%d],\"speed_cm_s\":%s,"
"\"lookahead_tiles\":%d,\"collision_tiles_added\":%d,\"desired_tiles_missing\":%d"),
Center.X, Center.Y, Center.Z, OccupiedTile.Coord.X, OccupiedTile.Coord.Y,
OccupiedTile.Coord.Z,
LookaheadHeadingStep.X, LookaheadHeadingStep.Y, LookaheadHeadingStep.Z,
*FString::SanitizeFloat(PlayerHeading.Size()), LookaheadTiles,
LookaheadCollisionTilesAdded, LookaheadDesiredTilesMissing));
}
for (const FVoxelTileKey& Key : CollisionAnchorTiles)
{
if (!BaseVisualTiles.Contains(Key) && !FullVisualAnchorTiles.Contains(Key))
@@ -10391,13 +10499,6 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
}
CancelObsoleteTileWork();
const FVoxelTileKey PlayerTile(GetTileCoordAtLocalCm(WorldToLocalCm(PlayerPosition)), 0);
FVoxelTileKey OccupiedTile = PlayerTile;
FVoxelTileKey SupportTile;
if (GetPlayerSupportTile(PlayerPawn, PlayerPosition, SupportTile))
{
OccupiedTile = SupportTile;
}
auto AddCriticalTile = [this](const FVoxelTileKey& Key)
{
if (IsDesired(Key) && !CriticalDesiredTiles.Contains(Key))
@@ -10415,22 +10516,7 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
}
AddCriticalTile(OccupiedTile);
FVector Heading = GetActorTransform().InverseTransformVectorNoScale(PlayerHeading);
if (Heading.IsNearlyZero() && IsValid(PlayerPawn))
{
Heading = GetActorTransform().InverseTransformVectorNoScale(
PlayerPawn->GetActorForwardVector());
}
FIntVector HeadingStep = FIntVector::ZeroValue;
const float AbsX = FMath::Abs(Heading.X);
const float AbsY = FMath::Abs(Heading.Y);
const float AbsZ = FMath::Abs(Heading.Z);
if (AbsX > KINDA_SMALL_NUMBER || AbsY > KINDA_SMALL_NUMBER || AbsZ > KINDA_SMALL_NUMBER)
{
if (AbsX >= AbsY && AbsX >= AbsZ) HeadingStep.X = Heading.X >= 0.0f ? 1 : -1;
else if (AbsY >= AbsZ) HeadingStep.Y = Heading.Y >= 0.0f ? 1 : -1;
else HeadingStep.Z = Heading.Z >= 0.0f ? 1 : -1;
}
const FIntVector HeadingStep = LookaheadHeadingStep;
if (HeadingStep != FIntVector::ZeroValue)
{
FVoxelTileKey NextTile = OccupiedTile;
@@ -10443,9 +10529,19 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
for (int32 Dy = -1; Dy <= 1; ++Dy)
for (int32 Dx = -1; Dx <= 1; ++Dx)
{
const FVoxelTileKey WalkableKey(PlayerTile.Coord + FIntVector(Dx, Dy, 0), 0);
const FVoxelTileKey WalkableKey(OccupiedTile.Coord + FIntVector(Dx, Dy, 0), 0);
if (CollisionDesiredTiles.Contains(WalkableKey)) AddCriticalTile(WalkableKey);
}
for (int32 Ahead = 1; Ahead <= LookaheadTiles; ++Ahead)
{
const FIntVector AheadCenter = OccupiedTile.Coord + HeadingStep * Ahead;
for (int32 Dy = -1; Dy <= 1; ++Dy)
for (int32 Dx = -1; Dx <= 1; ++Dx)
{
const FVoxelTileKey WalkableKey(AheadCenter + FIntVector(Dx, Dy, 0), 0);
if (CollisionDesiredTiles.Contains(WalkableKey)) AddCriticalTile(WalkableKey);
}
}
for (int32 Index = CriticalDesiredTiles.Num() - 1; Index >= 0; --Index)
{
const int32 ExistingIndex = DesiredSorted.Find(CriticalDesiredTiles[Index]);
@@ -10710,6 +10806,11 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
++DesiredEpoch;
DesiredEpochStartCycles = FPlatformTime::Cycles64();
DesiredEpochCenterChunk = Center;
const FIntVector LookaheadHeadingStep = GetStreamingHeadingStep(PlayerHeading, PlayerPawn);
const int32 LookaheadTiles = GetStreamingCollisionLookaheadTiles(
PlayerHeading, LookaheadHeadingStep);
int32 LookaheadCollisionTilesAdded = 0;
int32 LookaheadDesiredTilesMissing = 0;
const int32 R = Settings ? FMath::Max(1, Settings->ClipRadius) : 6;
const int32 MaxLevel = Settings ? FMath::Clamp(Settings->MaxClipLevel, 0, 8) : 4;
@@ -10791,6 +10892,49 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
CollisionDesiredTiles.Add(Key);
}
const FVoxelTileKey PlayerTile(
WorldToChunkCoord(WorldToLocalCm(PlayerPosition)), 0);
FVoxelTileKey OccupiedTile = PlayerTile;
FVoxelTileKey SupportTile;
if (GetPlayerSupportTile(PlayerPawn, PlayerPosition, SupportTile))
{
OccupiedTile = SupportTile;
}
// Reserve predictive collision inside the current clipmap so those tiles cook before arrival.
// Reserver la collision predictive dans le clipmap courant pour cuire avant l'arrivee.
for (int32 Ahead = 1; Ahead <= LookaheadTiles; ++Ahead)
{
const FIntVector AheadCenter = OccupiedTile.Coord + LookaheadHeadingStep * Ahead;
for (int32 Dy = -1; Dy <= 1; ++Dy)
for (int32 Dx = -1; Dx <= 1; ++Dx)
{
const FVoxelTileKey Key(AheadCenter + FIntVector(Dx, Dy, 0), 0);
if (!IsDesired(Key))
{
++LookaheadDesiredTilesMissing;
continue;
}
if (!CollisionDesiredTiles.Contains(Key))
{
CollisionDesiredTiles.Add(Key);
++LookaheadCollisionTilesAdded;
}
}
}
if (LookaheadTiles > 0 && VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("collision_lookahead"), FString::Printf(
TEXT("\"center\":[%d,%d,%d],\"walkable_center\":[%d,%d,%d],"
"\"heading_step\":[%d,%d,%d],\"speed_cm_s\":%s,"
"\"lookahead_tiles\":%d,\"collision_tiles_added\":%d,\"desired_tiles_missing\":%d"),
Center.X, Center.Y, Center.Z, OccupiedTile.Coord.X, OccupiedTile.Coord.Y,
OccupiedTile.Coord.Z,
LookaheadHeadingStep.X, LookaheadHeadingStep.Y, LookaheadHeadingStep.Z,
*FString::SanitizeFloat(PlayerHeading.Size()), LookaheadTiles,
LookaheadCollisionTilesAdded, LookaheadDesiredTilesMissing));
}
if (GVoxelForgeProfileTileGeneration != 0)
{
int32 RenderLOD0Count = 0;
@@ -10860,15 +11004,6 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
// one that can catch a falling pawn — followed by the adjacent tile in the dominant movement
// direction. Keep the actor-centre tile as a fallback for capsule/offset edge cases. Only keys
// in the current desired set are promoted, so this never expands the streaming footprint.
const FVoxelTileKey PlayerTile(
WorldToChunkCoord(WorldToLocalCm(PlayerPosition)), 0);
FVoxelTileKey OccupiedTile = PlayerTile;
FVoxelTileKey SupportTile;
if (GetPlayerSupportTile(PlayerPawn, PlayerPosition, SupportTile))
{
OccupiedTile = SupportTile;
}
auto AddCriticalTile = [this](const FVoxelTileKey& Key)
{
if (IsDesired(Key) && !CriticalDesiredTiles.Contains(Key))
@@ -10889,31 +11024,7 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
}
AddCriticalTile(OccupiedTile);
FVector Heading = PlayerHeading;
if (Heading.IsNearlyZero() && IsValid(PlayerPawn))
{
Heading = PlayerPawn->GetActorForwardVector();
}
Heading = GetActorTransform().InverseTransformVectorNoScale(Heading);
const float AbsX = FMath::Abs(Heading.X);
const float AbsY = FMath::Abs(Heading.Y);
const float AbsZ = FMath::Abs(Heading.Z);
FIntVector HeadingStep = FIntVector::ZeroValue;
if (AbsX > KINDA_SMALL_NUMBER || AbsY > KINDA_SMALL_NUMBER || AbsZ > KINDA_SMALL_NUMBER)
{
if (AbsX >= AbsY && AbsX >= AbsZ)
{
HeadingStep.X = Heading.X >= 0.0f ? 1 : -1;
}
else if (AbsY >= AbsZ)
{
HeadingStep.Y = Heading.Y >= 0.0f ? 1 : -1;
}
else
{
HeadingStep.Z = Heading.Z >= 0.0f ? 1 : -1;
}
}
const FIntVector HeadingStep = LookaheadHeadingStep;
if (HeadingStep != FIntVector::ZeroValue)
{
FVoxelTileKey NextTile = OccupiedTile;
@@ -10926,9 +11037,19 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
for (int32 Dy = -1; Dy <= 1; ++Dy)
for (int32 Dx = -1; Dx <= 1; ++Dx)
{
const FVoxelTileKey WalkableKey(PlayerTile.Coord + FIntVector(Dx, Dy, 0), 0);
const FVoxelTileKey WalkableKey(OccupiedTile.Coord + FIntVector(Dx, Dy, 0), 0);
if (CollisionDesiredTiles.Contains(WalkableKey)) AddCriticalTile(WalkableKey);
}
for (int32 Ahead = 1; Ahead <= LookaheadTiles; ++Ahead)
{
const FIntVector AheadCenter = OccupiedTile.Coord + HeadingStep * Ahead;
for (int32 Dy = -1; Dy <= 1; ++Dy)
for (int32 Dx = -1; Dx <= 1; ++Dx)
{
const FVoxelTileKey WalkableKey(AheadCenter + FIntVector(Dx, Dy, 0), 0);
if (CollisionDesiredTiles.Contains(WalkableKey)) AddCriticalTile(WalkableKey);
}
}
// Insert in reverse so CriticalDesiredTiles[0] remains the occupied floor, [1] the heading
// tile, and all ordinary distance-sorted work follows both.
@@ -11378,7 +11499,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
? LastStreamingSchedulerSampleSeconds : StreamingUpdateStartSeconds;
const double IntervalSeconds = FMath::Max(0.0, Now - IntervalStart);
const double BusySeconds = FPlatformTime::ToSeconds64(WorkerCyclesDelta);
const double SpeedCmPerSecond = PlayerHeading.Size2D();
const double SpeedCmPerSecond = PlayerHeading.Size();
VoxelForgeStartupTrace::RecordEvent(TEXT("scheduler_skip"), FString::Printf(
TEXT("\"frame\":%llu,\"reason\":\"%s\",\"desired_count\":%d,"
"\"desired_missing\":%d,\"desired_missing_by_lod\":%s,"
@@ -11589,7 +11710,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
bGateCenterChanged ? WalkableCenter.Y - PreviousGateCenter.Y : 0,
bGateCenterChanged ? WalkableCenter.Z - PreviousGateCenter.Z : 0,
bGateCenterChanged ? 1 : 0, bWalkableRingReadyForReserve ? 1 : 0,
WalkableRingReadyCount, *FString::SanitizeFloat(PlayerHeading.Size2D()), DesiredEpoch));
WalkableRingReadyCount, *FString::SanitizeFloat(PlayerHeading.Size()), DesiredEpoch));
bHasLastWalkableGateSample = true;
LastWalkableGateCenter = WalkableCenter;
bLastWalkableGateReady = bWalkableRingReadyForReserve;
@@ -11600,7 +11721,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
RecordSchedulerSkip(TEXT("core_region_compile_pending"));
return;
}
const bool bPawnMoving = PlayerHeading.Size2D() >= 50.0f;
const bool bPawnMoving = PlayerHeading.Size() >= 50.0f;
EffectiveNearTaskReserve = GVoxelForgeStreamingDynamicNearReserve == 0
? AuthoredNearTaskReserve
: ((bPawnMoving || !bWalkableRingReadyForReserve) ? AuthoredNearTaskReserve : 0);
@@ -12219,7 +12340,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
CenterChunk.X, CenterChunk.Y, CenterChunk.Z,
*FString::SanitizeFloat(LocalPawn.X), *FString::SanitizeFloat(LocalPawn.Y),
*FString::SanitizeFloat(LocalPawn.Z),
*FString::SanitizeFloat(PlayerHeading.Size2D()),
*FString::SanitizeFloat(PlayerHeading.Size()),
bWalkableRingReadyForReserve ? 1 : 0,
*DesiredCounts, *ResidentCounts, *PendingCounts,
GetActiveGenerationTaskCount(), GetGenerationAdmissionCount(), MaxTasks, NearTaskReserve,
+4
View File
@@ -702,6 +702,10 @@ private:
void BuildCoreDesiredTiles(const FIntVector& Center, const FVector& PlayerPosition,
APawn* PlayerPawn, const FVector& PlayerHeading,
TArray<FVoxelTileKey>& OutLeavers);
FIntVector GetStreamingHeadingStep(const FVector& PlayerHeading,
const APawn* PlayerPawn) const;
int32 GetStreamingCollisionLookaheadTiles(const FVector& PlayerHeading,
const FIntVector& HeadingStep) const;
void StartPendingCoreRegionCompile();
void LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority WorkPriority);
void EnqueueStreamingResult(FChunkResult&& Result);
@@ -17490,6 +17490,24 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
float WalkSpeedCmPerSecond = 600.0f;
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestMoveSpeedCmPerSecond="),
WalkSpeedCmPerSecond);
FString WalkDirectionText(TEXT("0,1,0"));
const FString DirectionKey(TEXT("voxel.TestMoveDirection="));
const FString CommandLineText(FCommandLine::Get());
const int32 DirectionKeyIndex = CommandLineText.Find(
DirectionKey, ESearchCase::IgnoreCase, ESearchDir::FromStart);
if (DirectionKeyIndex != INDEX_NONE)
{
WalkDirectionText = CommandLineText.Mid(DirectionKeyIndex + DirectionKey.Len());
const int32 DirectionEnd = WalkDirectionText.Find(
TEXT(" "), ESearchCase::CaseSensitive, ESearchDir::FromStart);
if (DirectionEnd != INDEX_NONE)
{
WalkDirectionText.LeftInline(DirectionEnd, EAllowShrinking::No);
}
WalkDirectionText.TrimStartAndEndInline();
WalkDirectionText.RemoveFromStart(TEXT("\""));
WalkDirectionText.RemoveFromEnd(TEXT("\""));
}
TArray<double> SpawnTickMilliseconds;
TArray<double> WalkTickMilliseconds;
TArray<TPair<double, double>> TickSamples;
@@ -17569,11 +17587,11 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldStreamTrace] pass complete=1 recipe=%s voxel_cm=%.3f walk_speed_cm_s=%.0f walk_direction=+Y"),
TEXT("[VoxelForgeCoreWorldStreamTrace] pass complete=1 recipe=%s voxel_cm=%.3f walk_speed_cm_s=%.0f walk_direction=%s"),
Arguments.CoreHybridMesherVersion == 2 ? TEXT("WorldAssemblerHybridV2")
: (Arguments.CoreHybridMesherVersion == 1 ? TEXT("WorldAssemblerHybridV1")
: TEXT("MarchingCubesV1")),
Arguments.CoreVoxelSizeCm, WalkSpeedCmPerSecond);
Arguments.CoreVoxelSizeCm, WalkSpeedCmPerSecond, *WalkDirectionText);
return true;
}
@@ -0,0 +1,226 @@
# World assembler S9 / M3 Round 13 — support-ring lookahead
**Result:** the support ring is ready at all tested center arrivals through 40 m/s horizontally,
and through a scripted 50 m/s vertical descent. At 40 m/s the matched CVar-off control reached
the ring at only 15/74 arrivals; lookahead reached it at 74/74. Initial readiness was 7.743 s with
lookahead and 7.778 s without it. I did not reserve more workers, and I left the LOD8 geometry
unchanged; its counted optimization plan is in §5.
The lookahead is default-on through `voxel.StreamingCollisionLookahead=1`; `0` disables prediction.
It follows the support tile, predicts 350 ms ahead (rounded up to one or two 8 m LOD0 tiles at the
25 cm owner scale), and promotes only rings already inside the desired clipmap. I first predicted
from the stream center. The trace showed that this was one tile above the walkable center on this
SurfaceWorld plan. I corrected both scheduler branches to predict and prioritize from
`GetPlayerSupportTile`. No field or mesh arithmetic changed, so the recipe versions remain as-is.
## 1. Speed sweep
Fresh BuildHost commandlets used the staged owner SurfaceWorld plan, seed 0, 25 cm Core scale,
Legacy streaming order, Dynamic near reserve, apply boost 0, region concurrent admission 1, result
backlog cap 256, Normal process priority, a 12-action host limit, and movement from 30 to 45 s.
The S8 traces are the pre-lookahead baseline. Arrival readiness is the `walkable_gate.ready` value
on the support-center-change event. Lag is the maximum time to a later 9/9 sample before the next
support-center change; unresolved centers are counted separately. LOD0 missing is the number of
`scheduler_admission` samples with any desired LOD0 key missing during 30–45 s.
| Motion | S8 arrival-ready | S8 worst resolved lag / unresolved | S8 LOD0-missing frames | S9 arrival-ready | S9 worst lag / unresolved | S9 LOD0-missing frames |
|---|---:|---:|---:|---:|---:|---:|
| +Y 6 m/s | 11/11 (100%) | 0 ms / 0 | 168/838 (20.0%) | 11/11 (100%) | 0 ms / 0 | 165/839 (19.7%) |
| +Y 12 m/s | 22/22 (100%) | 0 ms / 0 | 431/776 (55.5%) | 22/22 (100%) | 0 ms / 0 | 436/775 (56.3%) |
| +Y 25 m/s | 46/46 (100%) | 0 ms / 0 | 625/677 (92.3%) | 46/46 (100%) | 0 ms / 0 | 627/679 (92.3%) |
| +Y 40 m/s | 25/74 (33.8%) | 191.1 ms / 8 | 579/595 (97.3%) | 74/74 (100%) | 0 ms / 0 | 576/593 (97.1%) |
| −Z 50 m/s | — | — | — | 93/93 (100%) | 0 ms / 0 | 605/626 (96.6%) |
The matched 40 m/s CVar-negative control on the corrected source reached 15/74 centers (20.3%),
with 196.3 ms worst resolved lag, 7 unresolved centers, and 574/590 LOD0-missing samples (97.3%).
This isolates prediction from the support-center correction. The old S8 60 m/s + 400 m teleport
remains a separate discontinuity case: 8/113 centers were ready at arrival, 3 more became ready
before the next center, and 102/113 never reached 9/9.
At 40 m/s the current run therefore supports **40 m/s as the highest tested horizontal speed**;
it does not establish a higher horizontal ceiling. The 50 m/s trace used the harness's scripted
downward actor movement through the staged owner plan, not a Chaos free-fall simulation in a shaft.
The ring remained ready through all 93 center changes.
Spawn did not regress in the matched 40 m/s pair: `initial_ready_s` was 7.743349 s on and 7.777605 s
off, with spawn-tick p95 2.288 ms and 2.292 ms respectively. The S9 on-run initial-ready values
at 6/12/25/40 m/s were 7.813133/7.815265/7.821660/7.743349 s. Movement begins at 30 s, and the
lookahead adds no stationary-spawn requests.
## 2. Critical path at 40 m/s
For 108 traced critical LOD0 jobs with complete tile-event joins, p50/p95 were:
| Stage | p50 | p95 |
|---|---:|---:|
| Request → worker start | 0.02 ms | 9.59 ms |
| Worker generation | 66.6 ms | 84.0 ms |
| Result queue → game-thread apply | 9.3 ms | 17.7 ms |
| Game-thread apply → mesh submit | 0.17 ms | 0.24 ms |
| Mesh → collision submit | 0.01 ms | 0.02 ms |
| RMC submit → body-update event | 37.6 ms | 85.9 ms |
The body-update interval is the observable collision-ready delay; it is not an isolated Chaos CPU
cook timer. Generation and body update dominate. Request-to-worker delay is small at the median,
and game-thread apply/submission is below a quarter millisecond at p95. The lookahead gives the
generation and body-update stages time to finish before support-center arrival.
At 40 m/s the trace reported two lookahead tiles and three newly collision-promoted keys per
scheduler rebuild, with zero desired keys outside the clipmap. Worker occupancy hit the 10-task
limit, while the result queue peaked at 113 entries / 16.8 MB under the 256 cap and recorded no cap
stops. Region compile time was 1.74 ms p50, 22.16 ms p95, 33.32 ms max; concurrent admission
recorded no global scheduler skips. Those systems still consume time, but the matched CVar test
shows prediction is sufficient at this plan's 40 m/s horizontal limit without adding reserved
slots.
## 3. Output quality and determinism
- BuildHost Core-off legacy passed at 12.5 cm. M2 HybridV1 and HybridV2 each passed five analytic
fixtures plus border checks at 25 cm.
- SurfaceWorld passed at 25 cm with collision, anchor movement, carve and travel checks. Its
W1/W8/W16 actor hash matrix was stable: density `0cd2a1a4152ee4aa`, mesh `80a5b054b904b1a2`,
payload `bf2f1b40dff38acc`, topology `56074198682d5e18`. Peak commandlet private memory was
3.90 GB.
- FloatingIslands passed at 25 cm with fixed water, edits, collision and LOD0/1 coverage; the
BuildHost actor matrix reported W1/W8/W16. Peak private memory was 3.25 GB.
- The current Standalone W1/W8/W12 full-clipmap and per-LOD 0–8 density, mesh, payload and topology
hashes match. Full-clipmap hash tuple: density `c10697a8f48380c7`, mesh `430d2be2bdd8b988`,
payload `b7b33e14092225c9`, topology `c5c46d66606a819e`. Full-detail hashes also match.
- The Standalone S4 audit passed: 385/385 far-cap bands, 128 agreement points (maximum 0.0504 cm
under the 2 cm gate), the 100 cm offset negative control, and 2,520/2,520 cap-coverage rays.
The current BuildHost SurfaceWorld S3 check passed 128 points with 0.0427 cm maximum far/near
error and detected its 100 cm offset control.
- A sampled Standalone W12 benchmark process peaked at 0.68 GB private memory.
I did **not** run Standalone `--workers=16`: that implementation constructs 16 worker threads,
which conflicts with the explicit 12-thread launch cap. Current Standalone W1/W8/W12 match, and
the BuildHost SurfaceWorld and FloatingIslands W1/W8/W16 matrices passed. I requested whether the
owner wants a one-run W16 exception; until then the 12-thread cap stands. No new `UECC-*` folder
was present; the only folder was the existing 2026-09-17 crash.
No geometry, field, density or recipe output changed. The far-horizon recipe stays at v2 and the
SurfaceWorld plan identity stays unchanged. Water remains fixed for the season; player carve/fill
remains unrestricted.
## 4. Goal 2 — counted LOD8 plan, optimization deferred
The S8 cold owner trace measured 306 LOD8 jobs at 18.798 worker-seconds total (61.4 ms average,
79.8 ms p95, 94.7 ms max), 45.6% of worker time. Its full set reached readiness in about 7.7 s.
The current 25 cm BuildHost feature-cost probe warmed eight LOD8 keys and reported p95 per-tile
diagnostics: full/candidate feature path 59.508/59.585 ms, column computation 30.771 ms, marching
cubes 6.018 ms, native ground+cap horizon refinement 48.205 ms, classification 0.0046 ms, feature
list 0.0007 ms, and shell work 0.0001 ms (repair skipped). The profile counters are overlapping
inclusive measurements, so they must not be summed. The same probe reported 15,529 field calls,
9,679 column-cache misses, 8,192 refined ground triangles, 531 ground triangles removed, and
6,732 cap triangles at the sampled high-cost LOD8 tile.
`HeightfieldColumnCache` is a 65,536-entry thread-local cache keyed by global XY. It reuses columns
within a worker, but does not share cache entries across workers. The current probe does not count
cross-tile duplicate samples by worker, and does not split ground-refinement time from cap-refinement
time. The next measurement should add exclusive counters for column evaluation, MC, ground refine,
cap refine, classification, and cache hits/misses per tile and worker, then trace duplicate global
columns across neighboring LOD8 jobs.
No LOD8 optimization or far-mesh change was made this round, so no before/after far-horizon/cap
captures were produced and the below-7.7 s Goal 2 target remains open. The counted next step is to
prototype shared immutable heightfield-column pages for planar families, compare against sampling
at displayed resolution and tolerance-based refinement skips, then rerun S3 ground/S4 cap gates
(2 cm agreement, 100% coverage) and matched viewer captures at VSync/60 fps, closing the viewer
right after each capture. Do not relax tolerances without a metre-scale error bound.
## 5. Builds, commands, binaries and run record
BuildHost and Standalone builds passed on their first attempt. Standalone build printed the existing
`vswhere.exe` lookup warning and C4838 conversions in `VoxelStrateManager.cpp:3974–3975`, then
built both targets successfully. All commandlets were fresh, serial, Normal priority and below
8 GB; the largest sampled BuildHost private memory was 3.90 GB. No process exceeded the 15-minute
step limit.
Build commands:
```powershell
.\Tools\BuildVoxelForgeCoreHost.ps1 -MaxParallelActions 12 -TimeoutSeconds 900
.\Standalone\Build.ps1 -Target All -CompileWorkers 12 -BuildPriority Normal
```
The final horizontal trace command (repeat with speeds `600`, `1200`, `2500`, and `4000` cm/s):
```powershell
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StartupApplyBoost 0 -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 600 -TimeoutSeconds 900
```
The pre-S9 baseline used the same staged plan and settings with the S8 binary, before the lookahead
CVar existed; repeat with the same four speed values:
```powershell
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StartupApplyBoost 0 -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 600 -TimeoutSeconds 900
```
Vertical descent and the matched 40 m/s CVar-negative control:
```powershell
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StartupApplyBoost 0 -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,0,-1' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 5000 -TimeoutSeconds 900
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StartupApplyBoost 0 -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 0 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 4000 -TimeoutSeconds 900
```
Quality commands:
```powershell
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -CoreOffTest -CoreVoxelSizeCm 12.5 -TimeoutSeconds 900
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -HybridFixturesOnly -CoreMesherRecipe WorldAssemblerHybridV1 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -HybridFixturesOnly -CoreMesherRecipe WorldAssemblerHybridV2 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -FloatingIslandsOnly -CoreVoxelSizeCm 25 -TimeoutSeconds 900
.\Standalone\bin\VoxelForgeCoreStandalone.exe --world-assembler-audit --workers=12 --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --process-priority=Normal --process-affinity=4095 --frame-limit=60
```
Standalone determinism command template (fresh process, `--workers=1`, `8`, and `12`; `16` was
not launched under the thread cap):
```powershell
.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=1 --world-assembler-prototype --stream-benchmark --stream-hash --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --process-priority=Normal --process-affinity=4095 --frame-limit=60
```
One initial CVar-off command was rejected by PowerShell before launch because the movement vector
was not quoted (`Cannot convert value to type System.String`). I retried with
`-TestMoveDirection '0,1,0'`; the BuildHost run passed. No Unreal process launched for the rejected
attempt. No other test/build attempt failed or retried.
Binary SHA-256:
| Binary | SHA-256 |
|---|---|
| UE 5.7 `UnrealEditor-Cmd.exe` | `ECCE9996191F7F7E69B3D104EECDD0E678AE69176FDCCD79828C3E75A0654D6D` |
| S8 baseline BuildHost `UnrealEditor-VoxelForge.dll` | `8B82EE6C6FBB511AE766EB3D8646A7E9491952F41445CB62D49D71D0B9783418` |
| S8 baseline BuildHost `UnrealEditor-VoxelForgeEditor.dll` | `B3BC5E61172C5B21D928A5ACB5F22F8D9E00179B481B675804838DE9A620010D` |
| BuildHost `UnrealEditor-VoxelForge.dll` | `F542FC1F014190798485CC088470FD498D368D2E369639EAD32A0EEE8FDB1193` |
| BuildHost `UnrealEditor-VoxelForgeCore.dll` | `0A1EB7BB27C3C74A4AA0996E0A24E74224D7B8FFFFF113B1071FA42E18E8375A` |
| BuildHost `UnrealEditor-VoxelForgeEditor.dll` | `15B6460FD4E48D14CBE751C99447F06B62B44CF35FFD440AED106585AECEDC79` |
| `Standalone/bin/VoxelForgeCoreStandalone.exe` | `FD4CD39CD49BB0E8429250295BD16FACA0A6FBE412240522AB79C086245978C0` |
Trace JSONL and actor stdout remain under
`Saved/BuildHost/HostProject/Plugins/VoxelForge/Saved/CoreWorldValidation/Logs/`. The initial
lookahead results are retained in `Saved/BuildHost/Round13/BeforeSupportTileCorrection/`; Standalone
hash logs and the S4 audit log are under `Saved/BuildHost/Round13/`. No file over 300 KB was added
outside `Saved/BuildHost/`. No `.uproject`, editor, `.uasset`, RealtimeMeshComponent source, commit,
push or stash was touched.
Modified source files:
- `Source/VoxelForge/Private/VoxelWorld.cpp` — CVar, support-centered lookahead and critical ring ordering; BuildHost `VoxelForge` runtime compile.
- `Source/VoxelForge/Public/VoxelWorld.h` — private helper declarations; same runtime compile.
- `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp` — movement-vector harness; BuildHost `VoxelForgeEditor` compile.
- `Tools/TestVoxelForgeCoreWorldHost.ps1` — movement-vector and CVar arguments.
New source files: none. Standalone source files were not changed. Documentation updated:
`CODEMAP.md` §3.16 and `ARCHITECTURE.md` §8.30.
Async-safety diff review: `Epoch` propagation/checks are untouched; all result queues remain
`EQueueMode::Mpsc`; `EndPlay` still drains after active tasks finish; workers still check
`bShuttingDown`; `ModsLock` and immutable region-page lifetime are unchanged; UObject and RMC
mutation remain on the game thread.
### Dialogue
**User:** Continue S9 from the 6 and 12 m/s lookahead traces after the service interruption.
**Luna:** Resumed at the 25 m/s run, found and fixed the support/stream-center mismatch, then measured the corrected speed sweep and matched 40 m/s CVar control.
+20 -3
View File
@@ -9,6 +9,8 @@ param(
[ValidateRange(0, 1)][int]$StartupApplyBoost = 0,
[switch]$StreamingTraceOnly,
[ValidateRange(1, 6000)][int]$TestMoveSpeedCmPerSecond = 600,
[ValidateRange(0, 1)][int]$StreamingCollisionLookahead = 1,
[string]$TestMoveDirection = '0,1,0',
[ValidateRange(0, 1)][int]$CoreRegionConcurrentAdmission = 1,
[ValidateRange(0, 4096)][int]$StreamingResultBacklogCap = 256,
[ValidateRange(0, 900)][int]$Step1MaxMeshApplies = 0,
@@ -79,7 +81,18 @@ if ($StreamingTraceOnly -and ($SharedFeatureAuditOnly -or $EnableHybridFixturePl
$HybridCliffOnly -or $HybridFixturesOnly -or $WorldAssemblyManifestPath)) {
throw '-StreamingTraceOnly uses the staged owner plan without additional fixture or manifest overrides.'
}
if ($TestMoveDirection -notmatch '^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+),[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+),[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)$') {
throw '-TestMoveDirection must be three comma-separated invariant-culture numbers without spaces.'
}
$MoveDirectionComponents = @($TestMoveDirection.Split(',') | ForEach-Object {
[double]::Parse($_, [Globalization.CultureInfo]::InvariantCulture)
})
if ($MoveDirectionComponents[0] -eq 0 -and $MoveDirectionComponents[1] -eq 0 -and
$MoveDirectionComponents[2] -eq 0) {
throw '-TestMoveDirection must not be the zero vector.'
}
if (-not $StreamingTraceOnly -and ($TestMoveSpeedCmPerSecond -ne 600 -or
$StreamingCollisionLookahead -ne 1 -or $TestMoveDirection -ne '0,1,0' -or
$CoreRegionConcurrentAdmission -ne 1 -or $StreamingResultBacklogCap -ne 256 -or
$Step1MaxMeshApplies -ne 0 -or $TestTeleportAfterSeconds -ne 0 -or
$TestTeleportDistanceCm -ne 0)) {
@@ -169,7 +182,8 @@ $RunName += switch ($NearReserveMode) {
if ($EnforceCollisionGate) { $RunName += '_collision_gate' }
if ($StartupTrace -or $StreamingTraceOnly) { $RunName += '_trace' }
if ($StreamingTraceOnly) {
$RunName += "_move_${TestMoveSpeedCmPerSecond}_region_${CoreRegionConcurrentAdmission}_backlog_${StreamingResultBacklogCap}"
$DirectionTag = $TestMoveDirection.Replace(',', '_').Replace('-', 'm').Replace('+', 'p').Replace('.', 'p')
$RunName += "_move_${TestMoveSpeedCmPerSecond}_lookahead_${StreamingCollisionLookahead}_dir_${DirectionTag}_region_${CoreRegionConcurrentAdmission}_backlog_${StreamingResultBacklogCap}"
if ($Step1MaxMeshApplies -gt 0) { $RunName += "_applymax_$Step1MaxMeshApplies" }
if ($TestTeleportAfterSeconds -gt 0) {
$RunName += "_teleport_${TestTeleportAfterSeconds}s_${TestTeleportDistanceCm}cm"
@@ -310,7 +324,8 @@ if ($StreamingTraceOnly) {
$BaseArguments += '-voxel.TestMove=1'
$BaseArguments += "-voxel.TestMoveSpeedCmPerSecond=$TestMoveSpeedCmPerSecond"
$BaseArguments += '-voxel.TestMoveStartSeconds=30'
$BaseArguments += '-voxel.TestMoveDirection=0,1,0'
$BaseArguments += "-voxel.TestMoveDirection=$TestMoveDirection"
$BaseArguments += "-voxel.StreamingCollisionLookahead=$StreamingCollisionLookahead"
if ($TestTeleportAfterSeconds -gt 0) {
$BaseArguments += "-voxel.TestTeleportAfterSeconds=$TestTeleportAfterSeconds"
$BaseArguments += "-voxel.TestTeleportDistanceCm=$TestTeleportDistanceCm"
@@ -447,8 +462,10 @@ if ($SharedFeatureAuditOnly) {
return
}
if ($StreamingTraceOnly) {
$ExpectedWalkDirection = "walk_direction=$TestMoveDirection"
if ($Text -notmatch '\[VoxelForgeCoreWorldStreamTrace\] initial_ready=1 .*walk_tick_ms_p95=.*walk_tick_ms_max=' -or
$Text -notmatch ("\[VoxelForgeCoreWorldStreamTrace\] pass complete=1 .*walk_speed_cm_s=$TestMoveSpeedCmPerSecond walk_direction=\+Y") -or
$Text -notmatch ("\[VoxelForgeCoreWorldStreamTrace\] pass complete=1 .*walk_speed_cm_s=$TestMoveSpeedCmPerSecond") -or
-not $Text.Contains($ExpectedWalkDirection) -or
$Text -notmatch '\[VoxelForgeCoreWorldTest\] pass complete=1') {
throw "BuildHost actor-only stream trace did not prove initial readiness and a completed walk sample. See $FinalLog."
}