unreal: startup boost spans epoch churn; empty collision tiles stay classified (L92)
- Startup apply boost (24 meshes / 14 ms) now ends on initial LOD0-2 + collision readiness or the 30 s timeout, not on the first desired-epoch change: FI LOD0-2 settled 3.49 -> 3.40 s. - Known-empty LOD0 collision classifications persist across unloads within a modification epoch and are invalidated on edits; commandlet readiness accepts CollisionNotRequired tiles. The 30 m/s movement trace now passes (no re-requests of the 4 support tiles). - Startup trace events: SerializeResolvedWorld ~466 ms synchronous in BeginPlay (unchanged); SW bReady at 2.74 s vs FI 0.75 s (to investigate). Redundant second parse removed. - Core output identical. Fast tier PASS; Linux UE builds. R42 notes attached. 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
8805291d3e
commit
70c37aaab3
8 files changed
+679
-30
No files matched your search
@@ -1051,6 +1051,8 @@ void AVoxelWorld::RegenerateAllChunks()
|
|||||||
CollisionSuppressedAtCycles.Empty();
|
CollisionSuppressedAtCycles.Empty();
|
||||||
CollisionNotRequiredTiles.Empty();
|
CollisionNotRequiredTiles.Empty();
|
||||||
CollisionSolidTiles.Empty();
|
CollisionSolidTiles.Empty();
|
||||||
|
KnownEmptyCollisionTileEpoch.Empty();
|
||||||
|
KnownEmptyCollisionSolidTiles.Empty();
|
||||||
PendingCollisionCooks.Empty();
|
PendingCollisionCooks.Empty();
|
||||||
|
|
||||||
// Decorations/water are keyed per level-0 chunk — clear them all.
|
// Decorations/water are keyed per level-0 chunk — clear them all.
|
||||||
@@ -1929,6 +1931,8 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|||||||
CollisionDesiredTiles.Empty();
|
CollisionDesiredTiles.Empty();
|
||||||
CollisionNotRequiredTiles.Empty();
|
CollisionNotRequiredTiles.Empty();
|
||||||
CollisionSolidTiles.Empty();
|
CollisionSolidTiles.Empty();
|
||||||
|
KnownEmptyCollisionTileEpoch.Empty();
|
||||||
|
KnownEmptyCollisionSolidTiles.Empty();
|
||||||
for (const TPair<URealtimeMesh*, FDelegateHandle>& Pair : CollisionBodyUpdatedHandles)
|
for (const TPair<URealtimeMesh*, FDelegateHandle>& Pair : CollisionBodyUpdatedHandles)
|
||||||
{
|
{
|
||||||
if (Pair.Key != nullptr)
|
if (Pair.Key != nullptr)
|
||||||
@@ -4785,6 +4789,7 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CollisionNotRequiredTiles.Add(EmptyTile);
|
CollisionNotRequiredTiles.Add(EmptyTile);
|
||||||
|
KnownEmptyCollisionTileEpoch.Add(EmptyTile, ModificationEpoch);
|
||||||
UE_LOG(LogTemp, Display,
|
UE_LOG(LogTemp, Display,
|
||||||
TEXT("[VoxelForgeCollisionGateStressTest] deep_drop_empty_tile "
|
TEXT("[VoxelForgeCollisionGateStressTest] deep_drop_empty_tile "
|
||||||
"tile=(0,0,%d) samples=%d min_density=%.6f seeded_known_empty=1"),
|
"tile=(0,0,%d) samples=%d min_density=%.6f seeded_known_empty=1"),
|
||||||
@@ -7643,9 +7648,26 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
|
|||||||
const float ActiveCoreVoxelSizeCm = GetActiveVoxelSizeCm();
|
const float ActiveCoreVoxelSizeCm = GetActiveVoxelSizeCm();
|
||||||
FString Json;
|
FString Json;
|
||||||
FString ExportError;
|
FString ExportError;
|
||||||
if (!VoxelForgeCoreAdapter::SerializeResolvedWorld(
|
const bool bTraceCoreStartup = VoxelForgeStartupTrace::IsActive();
|
||||||
|
const double SnapshotSerializeStartSeconds = bTraceCoreStartup
|
||||||
|
? FPlatformTime::Seconds() : 0.0;
|
||||||
|
if (bTraceCoreStartup)
|
||||||
|
{
|
||||||
|
VoxelForgeStartupTrace::StageBegin(TEXT("CoreSnapshot.SerializeResolvedWorld"));
|
||||||
|
}
|
||||||
|
const bool bSnapshotSerialized = VoxelForgeCoreAdapter::SerializeResolvedWorld(
|
||||||
Settings, StrateManager, ActiveCoreClipRadius, ActiveCoreMaxClipLevel,
|
Settings, StrateManager, ActiveCoreClipRadius, ActiveCoreMaxClipLevel,
|
||||||
ActiveCoreVoxelSizeCm, Json, ExportError))
|
ActiveCoreVoxelSizeCm, Json, ExportError);
|
||||||
|
if (bTraceCoreStartup)
|
||||||
|
{
|
||||||
|
const double SnapshotSerializeEndSeconds = FPlatformTime::Seconds();
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("core_snapshot_serialization"), FString::Printf(
|
||||||
|
TEXT("\"success\":%d,\"json_chars\":%d,\"duration_ms\":%.3f"),
|
||||||
|
bSnapshotSerialized ? 1 : 0, Json.Len(),
|
||||||
|
(SnapshotSerializeEndSeconds - SnapshotSerializeStartSeconds) * 1000.0));
|
||||||
|
VoxelForgeStartupTrace::StageEnd(TEXT("CoreSnapshot.SerializeResolvedWorld"));
|
||||||
|
}
|
||||||
|
if (!bSnapshotSerialized)
|
||||||
{
|
{
|
||||||
OutFailureReason = FString::Printf(TEXT("snapshot export failed: %s"), *ExportError);
|
OutFailureReason = FString::Printf(TEXT("snapshot export failed: %s"), *ExportError);
|
||||||
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreRuntime] %s"), *OutFailureReason);
|
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreRuntime] %s"), *OutFailureReason);
|
||||||
@@ -7653,9 +7675,24 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
|
|||||||
}
|
}
|
||||||
FTCHARToUTF8 Utf8Json(*Json);
|
FTCHARToUTF8 Utf8Json(*Json);
|
||||||
std::string CoreError;
|
std::string CoreError;
|
||||||
|
const double SnapshotResolveStartSeconds = bTraceCoreStartup
|
||||||
|
? FPlatformTime::Seconds() : 0.0;
|
||||||
|
if (bTraceCoreStartup)
|
||||||
|
{
|
||||||
|
VoxelForgeStartupTrace::StageBegin(TEXT("CoreSnapshot.FromJson"));
|
||||||
|
}
|
||||||
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> Snapshot =
|
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> Snapshot =
|
||||||
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
|
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
|
||||||
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())), CoreError);
|
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())), CoreError);
|
||||||
|
if (bTraceCoreStartup)
|
||||||
|
{
|
||||||
|
const double SnapshotResolveEndSeconds = FPlatformTime::Seconds();
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("core_snapshot_from_json"), FString::Printf(
|
||||||
|
TEXT("\"success\":%d,\"utf8_bytes\":%d,\"duration_ms\":%.3f"),
|
||||||
|
Snapshot.has_value() ? 1 : 0, Utf8Json.Length(),
|
||||||
|
(SnapshotResolveEndSeconds - SnapshotResolveStartSeconds) * 1000.0));
|
||||||
|
VoxelForgeStartupTrace::StageEnd(TEXT("CoreSnapshot.FromJson"));
|
||||||
|
}
|
||||||
if (!Snapshot)
|
if (!Snapshot)
|
||||||
{
|
{
|
||||||
OutFailureReason = FString::Printf(TEXT("schema-v1 snapshot resolve failed: %s"),
|
OutFailureReason = FString::Printf(TEXT("schema-v1 snapshot resolve failed: %s"),
|
||||||
@@ -7779,9 +7816,17 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
|
|||||||
VoxelForgeCoreState = State;
|
VoxelForgeCoreState = State;
|
||||||
State->bSnapshotReady.store(true, std::memory_order_release);
|
State->bSnapshotReady.store(true, std::memory_order_release);
|
||||||
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
|
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
const double CoreCompileLaunchSeconds = bTraceCoreStartup
|
||||||
|
? FPlatformTime::Seconds() : 0.0;
|
||||||
|
if (bTraceCoreStartup)
|
||||||
|
{
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("core_compile_launch"), FString::Printf(
|
||||||
|
TEXT("\"initial_tiles\":%d,\"worker_limit\":%u"),
|
||||||
|
static_cast<int32>(InitialDesired.size()), Request.workerLimit));
|
||||||
|
}
|
||||||
UE::Tasks::Launch(TEXT("VoxelForgeCoreCompile"),
|
UE::Tasks::Launch(TEXT("VoxelForgeCoreCompile"),
|
||||||
[this, State, Request, InitialDesired, ActiveCoreClipRadius, ActiveCoreMaxClipLevel,
|
[this, State, Request, InitialDesired, ActiveCoreClipRadius, ActiveCoreMaxClipLevel,
|
||||||
ActiveCoreVoxelSizeCm]()
|
ActiveCoreVoxelSizeCm, bTraceCoreStartup, CoreCompileLaunchSeconds]()
|
||||||
{
|
{
|
||||||
struct FTaskGuard
|
struct FTaskGuard
|
||||||
{
|
{
|
||||||
@@ -7795,6 +7840,14 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const double CoreCompileStartSeconds = bTraceCoreStartup
|
||||||
|
? FPlatformTime::Seconds() : 0.0;
|
||||||
|
if (bTraceCoreStartup)
|
||||||
|
{
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("core_compile_start"), FString::Printf(
|
||||||
|
TEXT("\"launch_queue_s\":%.6f"),
|
||||||
|
CoreCompileStartSeconds - CoreCompileLaunchSeconds));
|
||||||
|
}
|
||||||
std::string Error;
|
std::string Error;
|
||||||
bool bCompiled = false;
|
bool bCompiled = false;
|
||||||
bCompiled = State->World->compile(Request, State->Scheduler, Error);
|
bCompiled = State->World->compile(Request, State->Scheduler, Error);
|
||||||
@@ -7831,6 +7884,14 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
|
|||||||
}
|
}
|
||||||
|
|
||||||
State->bReady.store(true, std::memory_order_release);
|
State->bReady.store(true, std::memory_order_release);
|
||||||
|
if (bTraceCoreStartup)
|
||||||
|
{
|
||||||
|
const double CoreReadySeconds = FPlatformTime::Seconds();
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("core_bready"), FString::Printf(
|
||||||
|
TEXT("\"compile_s\":%.6f,\"launch_to_ready_s\":%.6f"),
|
||||||
|
CoreReadySeconds - CoreCompileStartSeconds,
|
||||||
|
CoreReadySeconds - CoreCompileLaunchSeconds));
|
||||||
|
}
|
||||||
State->bRebuildDesired.store(true, std::memory_order_release);
|
State->bRebuildDesired.store(true, std::memory_order_release);
|
||||||
UE_LOG(LogTemp, Display,
|
UE_LOG(LogTemp, Display,
|
||||||
TEXT("[VoxelForgeCoreRuntime] compiled=1 tiles=%d clip_radius=%d max_level=%d voxel_cm=%.4f"),
|
TEXT("[VoxelForgeCoreRuntime] compiled=1 tiles=%d clip_radius=%d max_level=%d voxel_cm=%.4f"),
|
||||||
@@ -9492,44 +9553,68 @@ void AVoxelWorld::ProcessPendingChunks()
|
|||||||
: 0.0;
|
: 0.0;
|
||||||
LastApplyFrameCycles = ApplyFrameStartCycles;
|
LastApplyFrameCycles = ApplyFrameStartCycles;
|
||||||
|
|
||||||
const double StartupElapsedSeconds = DesiredEpochStartCycles != 0
|
const double StartupBoostElapsedSeconds = StartupApplyBoostStartCycles != 0
|
||||||
&& ApplyFrameStartCycles >= DesiredEpochStartCycles
|
&& ApplyFrameStartCycles >= StartupApplyBoostStartCycles
|
||||||
? FPlatformTime::ToSeconds64(ApplyFrameStartCycles - DesiredEpochStartCycles)
|
? FPlatformTime::ToSeconds64(ApplyFrameStartCycles - StartupApplyBoostStartCycles)
|
||||||
: -1.0;
|
: -1.0;
|
||||||
const bool bInitialDesiredSetReady = DesiredEpoch == 1
|
bool bInitialDesiredSetReady = DesiredEpoch > 0
|
||||||
&& bAllChunksLoaded
|
|
||||||
&& PendingTiles.Num() == 0
|
|
||||||
&& PendingCollisionCooks.Num() == 0
|
|
||||||
&& LastLOD0RingReadyEpoch == DesiredEpoch;
|
&& LastLOD0RingReadyEpoch == DesiredEpoch;
|
||||||
|
if (bInitialDesiredSetReady)
|
||||||
|
{
|
||||||
|
for (const FVoxelTileKey& Tile : DesiredSorted)
|
||||||
|
{
|
||||||
|
if (Tile.Level > 2) continue;
|
||||||
|
if ((!LoadedTiles.Contains(Tile) && !CollisionNotRequiredTiles.Contains(Tile))
|
||||||
|
|| PendingTiles.Contains(Tile))
|
||||||
|
{
|
||||||
|
bInitialDesiredSetReady = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bInitialDesiredSetReady)
|
||||||
|
{
|
||||||
|
for (const FVoxelTileKey& Tile : CollisionDesiredTiles)
|
||||||
|
{
|
||||||
|
if ((!CollisionReadyTiles.Contains(Tile)
|
||||||
|
&& !CollisionNotRequiredTiles.Contains(Tile))
|
||||||
|
|| PendingCollisionCooks.Contains(Tile))
|
||||||
|
{
|
||||||
|
bInitialDesiredSetReady = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bool bStartupApplyOverridden = GVoxelForgeStreamingStartupApplyBoost <= 0
|
||||||
|
|| GVoxelForgeStep1MaxMeshApplies > 0;
|
||||||
if (bStartupApplyBoostStarted && !bStartupApplyBoostEnded
|
if (bStartupApplyBoostStarted && !bStartupApplyBoostEnded
|
||||||
&& (bInitialDesiredSetReady || DesiredEpoch != 1
|
&& (bInitialDesiredSetReady || bStartupApplyOverridden
|
||||||
|| GVoxelForgeStreamingStartupApplyBoost <= 0
|
|| StartupBoostElapsedSeconds >= StreamingStartupApplyMaxSeconds))
|
||||||
|| GVoxelForgeStep1MaxMeshApplies > 0
|
|
||||||
|| StartupElapsedSeconds >= StreamingStartupApplyMaxSeconds))
|
|
||||||
{
|
{
|
||||||
bStartupApplyBoostEnded = true;
|
bStartupApplyBoostEnded = true;
|
||||||
if (VoxelForgeStartupTrace::IsActive())
|
if (VoxelForgeStartupTrace::IsActive())
|
||||||
{
|
{
|
||||||
VoxelForgeStartupTrace::RecordEvent(TEXT("startup_apply_budget"), FString::Printf(
|
VoxelForgeStartupTrace::RecordEvent(TEXT("startup_apply_budget"), FString::Printf(
|
||||||
TEXT("\"state\":\"normal\",\"reason\":\"%s\",\"elapsed_s\":%s"),
|
TEXT("\"state\":\"normal\",\"reason\":\"%s\",\"elapsed_s\":%s"),
|
||||||
bInitialDesiredSetReady ? TEXT("initial_set_and_walkable_ring_ready")
|
bInitialDesiredSetReady ? TEXT("initial_lod02_and_collision_ready")
|
||||||
: (StartupElapsedSeconds >= StreamingStartupApplyMaxSeconds
|
: (StartupBoostElapsedSeconds >= StreamingStartupApplyMaxSeconds
|
||||||
? TEXT("time_limit")
|
? TEXT("time_limit")
|
||||||
: (DesiredEpoch != 1 ? TEXT("desired_epoch_changed")
|
: TEXT("override_disabled")),
|
||||||
: TEXT("override_disabled"))),
|
*FString::SanitizeFloat(StartupBoostElapsedSeconds)));
|
||||||
*FString::SanitizeFloat(StartupElapsedSeconds)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const bool bUseStartupApplyBoost = GVoxelForgeStreamingStartupApplyBoost > 0
|
const bool bUseStartupApplyBoost = GVoxelForgeStreamingStartupApplyBoost > 0
|
||||||
&& GVoxelForgeStep1MaxMeshApplies <= 0
|
&& GVoxelForgeStep1MaxMeshApplies <= 0
|
||||||
&& !bStartupApplyBoostEnded
|
&& !bStartupApplyBoostEnded
|
||||||
&& DesiredEpoch == 1
|
&& DesiredEpoch > 0
|
||||||
&& StartupElapsedSeconds >= 0.0
|
&& DesiredEpochStartCycles != 0
|
||||||
&& !bInitialDesiredSetReady
|
&& !bInitialDesiredSetReady
|
||||||
&& StartupElapsedSeconds < StreamingStartupApplyMaxSeconds;
|
&& (StartupApplyBoostStartCycles == 0
|
||||||
|
|| StartupBoostElapsedSeconds < StreamingStartupApplyMaxSeconds);
|
||||||
if (bUseStartupApplyBoost && !bStartupApplyBoostStarted)
|
if (bUseStartupApplyBoost && !bStartupApplyBoostStarted)
|
||||||
{
|
{
|
||||||
bStartupApplyBoostStarted = true;
|
bStartupApplyBoostStarted = true;
|
||||||
|
StartupApplyBoostStartCycles = ApplyFrameStartCycles;
|
||||||
if (VoxelForgeStartupTrace::IsActive())
|
if (VoxelForgeStartupTrace::IsActive())
|
||||||
{
|
{
|
||||||
VoxelForgeStartupTrace::RecordEvent(TEXT("startup_apply_budget"), FString::Printf(
|
VoxelForgeStartupTrace::RecordEvent(TEXT("startup_apply_budget"), FString::Printf(
|
||||||
@@ -10033,12 +10118,32 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
|
|||||||
CollisionSuppressedUntilCook.Remove(Result.Tile);
|
CollisionSuppressedUntilCook.Remove(Result.Tile);
|
||||||
CollisionSuppressedAtCycles.Remove(Result.Tile);
|
CollisionSuppressedAtCycles.Remove(Result.Tile);
|
||||||
CollisionSolidTiles.Remove(Result.Tile);
|
CollisionSolidTiles.Remove(Result.Tile);
|
||||||
|
KnownEmptyCollisionTileEpoch.Remove(Result.Tile);
|
||||||
|
KnownEmptyCollisionSolidTiles.Remove(Result.Tile);
|
||||||
if (Result.Tile.Level == 0)
|
if (Result.Tile.Level == 0)
|
||||||
{
|
{
|
||||||
CollisionNotRequiredTiles.Add(Result.Tile);
|
CollisionNotRequiredTiles.Add(Result.Tile);
|
||||||
if (Result.ClassifyVerdict == static_cast<int32>(EVoxelTileClass::AllSolid))
|
const bool bAllSolid = Result.ClassifyVerdict
|
||||||
|
== static_cast<int32>(EVoxelTileClass::AllSolid);
|
||||||
|
if (bAllSolid)
|
||||||
{
|
{
|
||||||
CollisionSolidTiles.Add(Result.Tile);
|
CollisionSolidTiles.Add(Result.Tile);
|
||||||
|
if (Result.ModificationEpoch == ModificationEpoch)
|
||||||
|
{
|
||||||
|
KnownEmptyCollisionSolidTiles.Add(Result.Tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Result.ModificationEpoch == ModificationEpoch)
|
||||||
|
{
|
||||||
|
KnownEmptyCollisionTileEpoch.Add(Result.Tile, ModificationEpoch);
|
||||||
|
}
|
||||||
|
if (VoxelForgeStartupTrace::IsActive())
|
||||||
|
{
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("collision_empty_classification"), FString::Printf(
|
||||||
|
TEXT("\"key\":[%d,%d,%d],\"lod\":0,\"modification_epoch\":%u,\"solid\":%d,\"cacheable\":%d"),
|
||||||
|
Result.Tile.Coord.X, Result.Tile.Coord.Y, Result.Tile.Coord.Z,
|
||||||
|
Result.ModificationEpoch, bAllSolid ? 1 : 0,
|
||||||
|
Result.ModificationEpoch == ModificationEpoch ? 1 : 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PendingCollisionCooks.Remove(Result.Tile);
|
PendingCollisionCooks.Remove(Result.Tile);
|
||||||
@@ -10094,6 +10199,8 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
|
|||||||
CollisionReadyTiles.Remove(Result.Tile);
|
CollisionReadyTiles.Remove(Result.Tile);
|
||||||
CollisionNotRequiredTiles.Remove(Result.Tile);
|
CollisionNotRequiredTiles.Remove(Result.Tile);
|
||||||
CollisionSolidTiles.Remove(Result.Tile);
|
CollisionSolidTiles.Remove(Result.Tile);
|
||||||
|
KnownEmptyCollisionTileEpoch.Remove(Result.Tile);
|
||||||
|
KnownEmptyCollisionSolidTiles.Remove(Result.Tile);
|
||||||
PendingCollisionCooks.Remove(Result.Tile);
|
PendingCollisionCooks.Remove(Result.Tile);
|
||||||
LoadedTiles.Remove(Result.Tile);
|
LoadedTiles.Remove(Result.Tile);
|
||||||
LoadedTileGeometry.Remove(Result.Tile);
|
LoadedTileGeometry.Remove(Result.Tile);
|
||||||
@@ -10727,6 +10834,7 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
|||||||
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RestoreKnownEmptyCollisionTiles();
|
||||||
ReconcileCollisionDesiredTiles();
|
ReconcileCollisionDesiredTiles();
|
||||||
for (auto It = DesiredStamped.CreateIterator(); It; ++It)
|
for (auto It = DesiredStamped.CreateIterator(); It; ++It)
|
||||||
{
|
{
|
||||||
@@ -11188,6 +11296,7 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
|
|||||||
StreamingAnchors.Num(), CollisionGateDesiredTiles.Num());
|
StreamingAnchors.Num(), CollisionGateDesiredTiles.Num());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RestoreKnownEmptyCollisionTiles();
|
||||||
ReconcileCollisionDesiredTiles();
|
ReconcileCollisionDesiredTiles();
|
||||||
|
|
||||||
// Balayage UNIQUE de la map : les entrées à stamp périmé viennent de quitter le desired set —
|
// Balayage UNIQUE de la map : les entrées à stamp périmé viennent de quitter le desired set —
|
||||||
@@ -11408,6 +11517,71 @@ void AVoxelWorld::AddAnchorDesiredTiles()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AVoxelWorld::RebaseKnownEmptyCollisionEpoch()
|
||||||
|
{
|
||||||
|
for (TPair<FVoxelTileKey, uint32>& Pair : KnownEmptyCollisionTileEpoch)
|
||||||
|
{
|
||||||
|
Pair.Value = ModificationEpoch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AVoxelWorld::InvalidateKnownEmptyCollisionTile(
|
||||||
|
const FVoxelTileKey& Tile, const TCHAR* Reason)
|
||||||
|
{
|
||||||
|
const bool bHadCachedClassification = KnownEmptyCollisionTileEpoch.Remove(Tile) > 0;
|
||||||
|
const bool bHadSolidClassification = KnownEmptyCollisionSolidTiles.Remove(Tile) > 0;
|
||||||
|
const bool bHadActiveClassification = CollisionNotRequiredTiles.Remove(Tile) > 0;
|
||||||
|
const bool bHadActiveSolidClassification = CollisionSolidTiles.Remove(Tile) > 0;
|
||||||
|
if (!bHadCachedClassification && !bHadSolidClassification
|
||||||
|
&& !bHadActiveClassification && !bHadActiveSolidClassification)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (VoxelForgeStartupTrace::IsActive())
|
||||||
|
{
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("collision_empty_cache_invalidated"), FString::Printf(
|
||||||
|
TEXT("\"key\":[%d,%d,%d],\"lod\":%d,\"modification_epoch\":%u,\"reason\":\"%s\""),
|
||||||
|
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, ModificationEpoch, Reason));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AVoxelWorld::RestoreKnownEmptyCollisionTiles()
|
||||||
|
{
|
||||||
|
for (const FVoxelTileKey& Tile : DesiredSorted)
|
||||||
|
{
|
||||||
|
if (Tile.Level != 0) continue;
|
||||||
|
const uint32* CachedEpoch = KnownEmptyCollisionTileEpoch.Find(Tile);
|
||||||
|
if (CachedEpoch == nullptr) continue;
|
||||||
|
if (*CachedEpoch != ModificationEpoch)
|
||||||
|
{
|
||||||
|
InvalidateKnownEmptyCollisionTile(Tile, TEXT("stale_modification_epoch"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool bWasLoaded = LoadedTiles.Contains(Tile);
|
||||||
|
const bool bWasNoBody = CollisionNotRequiredTiles.Contains(Tile);
|
||||||
|
LoadedTiles.Add(Tile);
|
||||||
|
CollisionNotRequiredTiles.Add(Tile);
|
||||||
|
const bool bAllSolid = KnownEmptyCollisionSolidTiles.Contains(Tile);
|
||||||
|
if (bAllSolid) CollisionSolidTiles.Add(Tile);
|
||||||
|
else CollisionSolidTiles.Remove(Tile);
|
||||||
|
if (!bWasLoaded || !bWasNoBody)
|
||||||
|
{
|
||||||
|
if (VoxelForgeStartupTrace::IsActive())
|
||||||
|
{
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("collision_empty_cache_restored"), FString::Printf(
|
||||||
|
TEXT("\"key\":[%d,%d,%d],\"lod\":0,\"modification_epoch\":%u,\"solid\":%d,\"was_loaded\":%d"),
|
||||||
|
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, ModificationEpoch,
|
||||||
|
bAllSolid ? 1 : 0, bWasLoaded ? 1 : 0));
|
||||||
|
}
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeCollisionEmptyCache] action=restore key=(%d,%d,%d,lod0) epoch=%u solid=%d"),
|
||||||
|
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, ModificationEpoch,
|
||||||
|
bAllSolid ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AVoxelWorld::ReconcileCollisionDesiredTiles()
|
void AVoxelWorld::ReconcileCollisionDesiredTiles()
|
||||||
{
|
{
|
||||||
// A radius/anchor change takes effect on the same desired-set rebuild. Disable old far bodies
|
// A radius/anchor change takes effect on the same desired-set rebuild. Disable old far bodies
|
||||||
@@ -12811,6 +12985,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
{
|
{
|
||||||
int32 DesiredByLevel[9] = {};
|
int32 DesiredByLevel[9] = {};
|
||||||
int32 ResidentByLevel[9] = {};
|
int32 ResidentByLevel[9] = {};
|
||||||
|
int32 SettledByLevel[9] = {};
|
||||||
int32 CoveredByLevel[9] = {};
|
int32 CoveredByLevel[9] = {};
|
||||||
int32 PendingByLevel[9] = {};
|
int32 PendingByLevel[9] = {};
|
||||||
int32 TraceMaxDesiredLevel = 0;
|
int32 TraceMaxDesiredLevel = 0;
|
||||||
@@ -12823,6 +12998,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
if (Tile.Level < 0 || Tile.Level > 8) continue;
|
if (Tile.Level < 0 || Tile.Level > 8) continue;
|
||||||
++DesiredByLevel[Tile.Level];
|
++DesiredByLevel[Tile.Level];
|
||||||
if (LoadedTiles.Contains(Tile)) ++ResidentByLevel[Tile.Level];
|
if (LoadedTiles.Contains(Tile)) ++ResidentByLevel[Tile.Level];
|
||||||
|
if (LoadedTiles.Contains(Tile) || CollisionNotRequiredTiles.Contains(Tile))
|
||||||
|
++SettledByLevel[Tile.Level];
|
||||||
if (PendingTiles.Contains(Tile)) ++PendingByLevel[Tile.Level];
|
if (PendingTiles.Contains(Tile)) ++PendingByLevel[Tile.Level];
|
||||||
bool bCovered = LoadedTiles.Contains(Tile);
|
bool bCovered = LoadedTiles.Contains(Tile);
|
||||||
for (int32 AncestorLevel = Tile.Level + 1;
|
for (int32 AncestorLevel = Tile.Level + 1;
|
||||||
@@ -12839,6 +13016,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
}
|
}
|
||||||
FString DesiredCounts = TEXT("[");
|
FString DesiredCounts = TEXT("[");
|
||||||
FString ResidentCounts = TEXT("[");
|
FString ResidentCounts = TEXT("[");
|
||||||
|
FString SettledCounts = TEXT("[");
|
||||||
FString CoveredCounts = TEXT("[");
|
FString CoveredCounts = TEXT("[");
|
||||||
FString PendingCounts = TEXT("[");
|
FString PendingCounts = TEXT("[");
|
||||||
for (int32 Level = 0; Level <= 8; ++Level)
|
for (int32 Level = 0; Level <= 8; ++Level)
|
||||||
@@ -12847,16 +13025,19 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
{
|
{
|
||||||
DesiredCounts += TEXT(",");
|
DesiredCounts += TEXT(",");
|
||||||
ResidentCounts += TEXT(",");
|
ResidentCounts += TEXT(",");
|
||||||
|
SettledCounts += TEXT(",");
|
||||||
CoveredCounts += TEXT(",");
|
CoveredCounts += TEXT(",");
|
||||||
PendingCounts += TEXT(",");
|
PendingCounts += TEXT(",");
|
||||||
}
|
}
|
||||||
DesiredCounts += FString::FromInt(DesiredByLevel[Level]);
|
DesiredCounts += FString::FromInt(DesiredByLevel[Level]);
|
||||||
ResidentCounts += FString::FromInt(ResidentByLevel[Level]);
|
ResidentCounts += FString::FromInt(ResidentByLevel[Level]);
|
||||||
|
SettledCounts += FString::FromInt(SettledByLevel[Level]);
|
||||||
CoveredCounts += FString::FromInt(CoveredByLevel[Level]);
|
CoveredCounts += FString::FromInt(CoveredByLevel[Level]);
|
||||||
PendingCounts += FString::FromInt(PendingByLevel[Level]);
|
PendingCounts += FString::FromInt(PendingByLevel[Level]);
|
||||||
}
|
}
|
||||||
DesiredCounts += TEXT("]");
|
DesiredCounts += TEXT("]");
|
||||||
ResidentCounts += TEXT("]");
|
ResidentCounts += TEXT("]");
|
||||||
|
SettledCounts += TEXT("]");
|
||||||
CoveredCounts += TEXT("]");
|
CoveredCounts += TEXT("]");
|
||||||
PendingCounts += TEXT("]");
|
PendingCounts += TEXT("]");
|
||||||
const FVector LocalPawn = WorldToLocalCm(CenterPosition);
|
const FVector LocalPawn = WorldToLocalCm(CenterPosition);
|
||||||
@@ -12864,7 +13045,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
VoxelForgeStartupTrace::RecordEvent(TEXT("streaming_sample"), FString::Printf(
|
VoxelForgeStartupTrace::RecordEvent(TEXT("streaming_sample"), FString::Printf(
|
||||||
TEXT("\"scope\":\"desired_residency_not_visual_enclosure\",\"center\":[%d,%d,%d],"
|
TEXT("\"scope\":\"desired_residency_not_visual_enclosure\",\"center\":[%d,%d,%d],"
|
||||||
"\"position_cm\":[%s,%s,%s],\"speed_cm_s\":%s,\"walkable_ready\":%d,"
|
"\"position_cm\":[%s,%s,%s],\"speed_cm_s\":%s,\"walkable_ready\":%d,"
|
||||||
"\"desired_by_lod\":%s,\"resident_by_lod\":%s,\"pending_by_lod\":%s,"
|
"\"desired_by_lod\":%s,\"resident_by_lod\":%s,\"settled_by_lod\":%s,"
|
||||||
|
"\"pending_by_lod\":%s,"
|
||||||
"\"covered_by_lod\":%s,"
|
"\"covered_by_lod\":%s,"
|
||||||
"\"active_generation\":%d,\"admission_occupancy\":%d,"
|
"\"active_generation\":%d,\"admission_occupancy\":%d,"
|
||||||
"\"task_cap\":%d,\"near_reserve\":%d,"
|
"\"task_cap\":%d,\"near_reserve\":%d,"
|
||||||
@@ -12876,7 +13058,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
*FString::SanitizeFloat(LocalPawn.Z),
|
*FString::SanitizeFloat(LocalPawn.Z),
|
||||||
*FString::SanitizeFloat(PlayerHeading.Size()),
|
*FString::SanitizeFloat(PlayerHeading.Size()),
|
||||||
bWalkableRingReadyForReserve ? 1 : 0,
|
bWalkableRingReadyForReserve ? 1 : 0,
|
||||||
*DesiredCounts, *ResidentCounts, *PendingCounts, *CoveredCounts,
|
*DesiredCounts, *ResidentCounts, *SettledCounts, *PendingCounts, *CoveredCounts,
|
||||||
GetActiveGenerationTaskCount(), GetGenerationAdmissionCount(), MaxTasks, NearTaskReserve,
|
GetActiveGenerationTaskCount(), GetGenerationAdmissionCount(), MaxTasks, NearTaskReserve,
|
||||||
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
|
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
|
||||||
GVoxelForgeStreamingLegacyPendingResultCap,
|
GVoxelForgeStreamingLegacyPendingResultCap,
|
||||||
@@ -14327,8 +14509,31 @@ void AVoxelWorld::UnloadTile(const FVoxelTileKey& Tile)
|
|||||||
CollisionReadyTiles.Remove(Tile);
|
CollisionReadyTiles.Remove(Tile);
|
||||||
CollisionSuppressedUntilCook.Remove(Tile);
|
CollisionSuppressedUntilCook.Remove(Tile);
|
||||||
CollisionSuppressedAtCycles.Remove(Tile);
|
CollisionSuppressedAtCycles.Remove(Tile);
|
||||||
|
const uint32* KnownEmptyEpoch = KnownEmptyCollisionTileEpoch.Find(Tile);
|
||||||
|
const bool bPreserveKnownEmpty = Tile.Level == 0
|
||||||
|
&& CollisionNotRequiredTiles.Contains(Tile)
|
||||||
|
&& KnownEmptyEpoch != nullptr && *KnownEmptyEpoch == ModificationEpoch;
|
||||||
|
if (bPreserveKnownEmpty)
|
||||||
|
{
|
||||||
|
if (VoxelForgeStartupTrace::IsActive())
|
||||||
|
{
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("collision_empty_cache_preserved"), FString::Printf(
|
||||||
|
TEXT("\"key\":[%d,%d,%d],\"lod\":0,\"modification_epoch\":%u,\"solid\":%d"),
|
||||||
|
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, ModificationEpoch,
|
||||||
|
KnownEmptyCollisionSolidTiles.Contains(Tile) ? 1 : 0));
|
||||||
|
}
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeCollisionEmptyCache] action=preserve_unload key=(%d,%d,%d,lod0) epoch=%u solid=%d"),
|
||||||
|
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, ModificationEpoch,
|
||||||
|
KnownEmptyCollisionSolidTiles.Contains(Tile) ? 1 : 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
CollisionNotRequiredTiles.Remove(Tile);
|
CollisionNotRequiredTiles.Remove(Tile);
|
||||||
CollisionSolidTiles.Remove(Tile);
|
CollisionSolidTiles.Remove(Tile);
|
||||||
|
KnownEmptyCollisionTileEpoch.Remove(Tile);
|
||||||
|
KnownEmptyCollisionSolidTiles.Remove(Tile);
|
||||||
|
}
|
||||||
PendingCollisionCooks.Remove(Tile);
|
PendingCollisionCooks.Remove(Tile);
|
||||||
// Water + decorations are not tile-bound (water is one player-following ocean plane via
|
// Water + decorations are not tile-bound (water is one player-following ocean plane via
|
||||||
// UpdateWater; decorations stream by distance via UpdateDecorations) — nothing to clear per tile.
|
// UpdateWater; decorations stream by distance via UpdateDecorations) — nothing to clear per tile.
|
||||||
@@ -14386,6 +14591,7 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
|
|||||||
TEXT("[VoxelForgeCollisionRetain] tile=(%d,%d,%d) old_body=1 ready_during_recook=1"),
|
TEXT("[VoxelForgeCollisionRetain] tile=(%d,%d,%d) old_body=1 ready_during_recook=1"),
|
||||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z);
|
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z);
|
||||||
}
|
}
|
||||||
|
InvalidateKnownEmptyCollisionTile(Tile, TEXT("nonempty_tile_result"));
|
||||||
CollisionNotRequiredTiles.Remove(Tile);
|
CollisionNotRequiredTiles.Remove(Tile);
|
||||||
CollisionSolidTiles.Remove(Tile);
|
CollisionSolidTiles.Remove(Tile);
|
||||||
PendingCollisionCooks.Remove(Tile);
|
PendingCollisionCooks.Remove(Tile);
|
||||||
@@ -14962,6 +15168,11 @@ void AVoxelWorld::ApplyModification(const FVoxelModification& Modification)
|
|||||||
if (AffectedChunks.Num() > 0)
|
if (AffectedChunks.Num() > 0)
|
||||||
{
|
{
|
||||||
++ModificationEpoch;
|
++ModificationEpoch;
|
||||||
|
RebaseKnownEmptyCollisionEpoch();
|
||||||
|
for (const FIntVector& Chunk : AffectedChunks)
|
||||||
|
{
|
||||||
|
InvalidateKnownEmptyCollisionTile(FVoxelTileKey(Chunk, 0), TEXT("tile_data_changed"));
|
||||||
|
}
|
||||||
Modification.GetWorldBounds(LastModificationMinVoxel, LastModificationMaxVoxel);
|
Modification.GetWorldBounds(LastModificationMinVoxel, LastModificationMaxVoxel);
|
||||||
if (FVisualGapAuditState* GapAudit = GVoxelForgeVisualGapAudits.Find(this))
|
if (FVisualGapAuditState* GapAudit = GVoxelForgeVisualGapAudits.Find(this))
|
||||||
{
|
{
|
||||||
@@ -15133,6 +15344,7 @@ void AVoxelWorld::ApplyCoreModification(const FVoxelModification& Modification)
|
|||||||
|
|
||||||
++GenerationEpoch;
|
++GenerationEpoch;
|
||||||
++ModificationEpoch;
|
++ModificationEpoch;
|
||||||
|
RebaseKnownEmptyCollisionEpoch();
|
||||||
LastVoxelForgeCoreEditDigest = EditResult->digest;
|
LastVoxelForgeCoreEditDigest = EditResult->digest;
|
||||||
LastVoxelForgeCoreEditSequence = Transaction.sequence;
|
LastVoxelForgeCoreEditSequence = Transaction.sequence;
|
||||||
LastVoxelForgeCoreAffectedTiles.Reset();
|
LastVoxelForgeCoreAffectedTiles.Reset();
|
||||||
@@ -15143,6 +15355,10 @@ void AVoxelWorld::ApplyCoreModification(const FVoxelModification& Modification)
|
|||||||
{
|
{
|
||||||
const FVoxelTileKey RuntimeKey(
|
const FVoxelTileKey RuntimeKey(
|
||||||
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
||||||
|
if (RuntimeKey.Level == 0)
|
||||||
|
{
|
||||||
|
InvalidateKnownEmptyCollisionTile(RuntimeKey, TEXT("tile_data_changed"));
|
||||||
|
}
|
||||||
LastVoxelForgeCoreAffectedTiles.Add(RuntimeKey);
|
LastVoxelForgeCoreAffectedTiles.Add(RuntimeKey);
|
||||||
if (IsDesired(RuntimeKey)) DirtyRemeshQueue.Add(RuntimeKey);
|
if (IsDesired(RuntimeKey)) DirtyRemeshQueue.Add(RuntimeKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -356,6 +356,10 @@ public:
|
|||||||
* the first resolved collidable support tile. */
|
* the first resolved collidable support tile. */
|
||||||
TSet<FVoxelTileKey> CollisionNotRequiredTiles;
|
TSet<FVoxelTileKey> CollisionNotRequiredTiles;
|
||||||
|
|
||||||
|
/** Known-empty collision classifications survive unload for the tile's current edit epoch. */
|
||||||
|
TMap<FVoxelTileKey, uint32> KnownEmptyCollisionTileEpoch;
|
||||||
|
TSet<FVoxelTileKey> KnownEmptyCollisionSolidTiles;
|
||||||
|
|
||||||
/** Subset of CollisionNotRequiredTiles proved all-solid. A support probe inside one of these
|
/** Subset of CollisionNotRequiredTiles proved all-solid. A support probe inside one of these
|
||||||
* tiles searches upward for the boundary tile; all-air/empty tiles search downward. */
|
* tiles searches upward for the boundary tile; all-air/empty tiles search downward. */
|
||||||
TSet<FVoxelTileKey> CollisionSolidTiles;
|
TSet<FVoxelTileKey> CollisionSolidTiles;
|
||||||
@@ -1201,6 +1205,9 @@ public:
|
|||||||
// and records the ones ONLY a CollisionOnly anchor wants in CollisionOnlyTiles.
|
// and records the ones ONLY a CollisionOnly anchor wants in CollisionOnlyTiles.
|
||||||
void AddAnchorDesiredTiles();
|
void AddAnchorDesiredTiles();
|
||||||
void ReconcileCollisionDesiredTiles();
|
void ReconcileCollisionDesiredTiles();
|
||||||
|
void RestoreKnownEmptyCollisionTiles();
|
||||||
|
void RebaseKnownEmptyCollisionEpoch();
|
||||||
|
void InvalidateKnownEmptyCollisionTile(const FVoxelTileKey& Tile, const TCHAR* Reason);
|
||||||
// Forces a desired-set rebuild next Tick even if neither the player nor an anchor crossed a tile
|
// Forces a desired-set rebuild next Tick even if neither the player nor an anchor crossed a tile
|
||||||
// boundary (set by UnregisterStreamingAnchor so a removed anchor's tiles get culled).
|
// boundary (set by UnregisterStreamingAnchor so a removed anchor's tiles get culled).
|
||||||
bool bForceDesiredRebuild = false;
|
bool bForceDesiredRebuild = false;
|
||||||
@@ -1236,6 +1243,7 @@ public:
|
|||||||
bool bSampleStacksStarted = false;
|
bool bSampleStacksStarted = false;
|
||||||
bool bStartupApplyBoostStarted = false;
|
bool bStartupApplyBoostStarted = false;
|
||||||
bool bStartupApplyBoostEnded = false;
|
bool bStartupApplyBoostEnded = false;
|
||||||
|
uint64 StartupApplyBoostStartCycles = 0;
|
||||||
// Trace the resolved startup budget once per world. / Tracer une fois le budget de demarrage resolu par monde.
|
// Trace the resolved startup budget once per world. / Tracer une fois le budget de demarrage resolu par monde.
|
||||||
bool bStartupApplyConfigTraced = false;
|
bool bStartupApplyConfigTraced = false;
|
||||||
uint64 LastApplyFrameCycles = 0;
|
uint64 LastApplyFrameCycles = 0;
|
||||||
|
|||||||
@@ -17266,7 +17266,8 @@ bool RunCoreFloatingIslandActorIntegrationTest(
|
|||||||
{
|
{
|
||||||
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
|
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
|
||||||
for (const FVoxelTileKey& Key : World->DesiredSorted)
|
for (const FVoxelTileKey& Key : World->DesiredSorted)
|
||||||
if (!World->LoadedTiles.Contains(Key)) return false;
|
if (!World->LoadedTiles.Contains(Key)
|
||||||
|
&& !World->CollisionNotRequiredTiles.Contains(Key)) return false;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
const auto CollisionSetSettled = [](const AVoxelWorld* World)
|
const auto CollisionSetSettled = [](const AVoxelWorld* World)
|
||||||
@@ -18129,7 +18130,8 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
|||||||
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
|
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
|
||||||
for (const FVoxelTileKey& Key : World->DesiredSorted)
|
for (const FVoxelTileKey& Key : World->DesiredSorted)
|
||||||
{
|
{
|
||||||
if (!World->LoadedTiles.Contains(Key)) return false;
|
if (!World->LoadedTiles.Contains(Key)
|
||||||
|
&& !World->CollisionNotRequiredTiles.Contains(Key)) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -19583,7 +19585,8 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
|||||||
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
|
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
|
||||||
for (const FVoxelTileKey& Key : World->DesiredSorted)
|
for (const FVoxelTileKey& Key : World->DesiredSorted)
|
||||||
{
|
{
|
||||||
if (!World->LoadedTiles.Contains(Key)) return false;
|
if (!World->LoadedTiles.Contains(Key)
|
||||||
|
&& !World->CollisionNotRequiredTiles.Contains(Key)) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -977,10 +977,10 @@ namespace VoxelForgeCore
|
|||||||
std::optional<ResolvedWorldSnapshot> ResolvedWorldSnapshot::FromJson(
|
std::optional<ResolvedWorldSnapshot> ResolvedWorldSnapshot::FromJson(
|
||||||
std::string_view json,std::string& error)
|
std::string_view json,std::string& error)
|
||||||
{
|
{
|
||||||
auto owner=std::make_shared<FStandaloneOwnerWorld>();
|
|
||||||
if(!StandaloneOwnerLoader::LoadJson(json,*owner,error))return std::nullopt;
|
|
||||||
VFJson::Value root;
|
VFJson::Value root;
|
||||||
if(!VFJson::Parser(std::string(json)).Parse(root,error))return std::nullopt;
|
if(!VFJson::Parser(std::string(json)).Parse(root,error))return std::nullopt;
|
||||||
|
auto owner=std::make_shared<FStandaloneOwnerWorld>();
|
||||||
|
if(!StandaloneOwnerLoader::Resolve(root,*owner,error))return std::nullopt;
|
||||||
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
|
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
|
||||||
if(root.Get("world_assembly").IsObject())
|
if(root.Get("world_assembly").IsObject())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
## Ranked findings
|
||||||
|
|
||||||
|
1. **[CODE] L91’s large gradient measurements do not come through the packed-density route, and they are not measurements of the field entering passage composition.** The sampler finds sign-changing edges in mesh debug densities, then probes `passageSourceDensityAt` at the interpolated location and at ±1 voxel on each axis. That API calls `NativePassageDensityAt`, which evaluates a native FI/surface field or the analytic field, then applies the boundary band; it does not call `PackedDensityAt` or the full cavern graph → boundary → passage composition chain. The actual passage composer receives the output of that graph and boundary chain. See [`StreamBenchmarkPipeline.h:346`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StreamBenchmarkPipeline.h:346), [`VoxelForgeCore.cpp:1912`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:1912), [`ShapeWorldInternal.h:6847`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:6847), and [`ShapeWorldInternal.h:2558`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2558). **Falsifier:** a trace showing the L91 probe taking a packed or fully composed route would overturn this; the current call chain shows neither.
|
||||||
|
|
||||||
|
2. **[MEASURED] The steep tail is real in that probe, but the logs do not locate its cause.** The sampler excludes approximate passage-region category 3, takes every 32nd eligible crossing, keeps source values with `|d|≤2`, and uses central differences with a one-voxel radius. The logs report FI p50/p90/max `1.00068 / 511.674 / 893.726`, TN `0.992669 / 1.04725 / 893.726`, and S3 `0.932169 / 1.03606 / 444.254` vox/vox. The collector retains magnitudes, not the sample coordinates or intermediate field values, so it cannot attribute the outliers to a specific operator. See [`StreamBenchmarkPipeline.h:355`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StreamBenchmarkPipeline.h:355) and the [FI log:15851](/home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l91/clamp-effects/fi-effects.log:15851), [TN log:17515](/home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l91/clamp-effects/tn-effects.log:17515), and [S3 log:17515](/home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l91/clamp-effects/s3-effects.log:17515). **Falsifier:** the corresponding logged summaries or source stamp disagreeing with these values.
|
||||||
|
|
||||||
|
3. **[CODE] Packed proxies and sentinel values are not the explanation for those recorded 511–894 gradients.** In the packed path, a candidate sample uses an `int16` proxy in 1/256-voxel units, clamped to ±32767/256 = **±127.9961 voxels**; a noncandidate sample uses fixed **±4.125 voxels**. Those values are below the FI p90 and max, and the L91 probe bypasses the packed path anyway. The floating-island sentinel is much larger: `MaxDensity=10¹²` subvoxels, or **3,906,250,000 voxels** at 256 subvoxels/voxel. A finite-difference stencil that crossed directly between that sentinel and an ordinary near-surface value would produce a gradient many orders above the logged maximum; the logs do not show that direct sentinel jump. See [`ShapeWorldInternal.h:7164`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:7164), [`ShapeWorldInternal.h:12242`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:12242), [`VoxelForgeCore.h:339`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h:339), and [`FloatingIslandsInternal.h:26`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FloatingIslandsInternal.h:26). **Falsifier:** a coordinate-level probe finding packed values or a sentinel transition in the same L91 finite-difference stencil.
|
||||||
|
|
||||||
|
4. **[INFERENCE] The best-supported candidates are native-field steepness and source/boundary route changes, but no current evidence identifies one as the measured cause.** The native FI path includes a detailed island/root field, quantized fixed-point values and a local mouth filter. The boundary operator can replace density with signed distance inside transition bands, preserve negative native rock on one branch, and switch stratum adapters by chunk. The code also explicitly avoids a native-lattice/boundary substitution that it says can create a discontinuity at a wide transition mouth. By contrast, the smooth-min blend depths themselves are small: the generic `width/6` formula dips at most 2 voxels for width 12 and 2.67 for width 16; FI’s `width/4` formula dips at most 1.25 voxels for 5-voxel root blends and 1.5 voxels for 6-voxel anchor blends. Those blend depths alone do not explain hundreds of vox/vox. A native cave host adapter is constant at −32m, which is **−128 voxels** at a 0.25m voxel; boundary remapping or an adapter transition can change that field locally. See [`ShapeWorldInternal.h:1171`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1171), [`ShapeWorldInternal.h:726`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:726), [`ShapeWorldInternal.h:2571`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2571), [`ShapeWorldInternal.h:192`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:192), [`ShapeWorldInternal.h:9146`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:9146), and [`FloatingIslandsInternal.h:132`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FloatingIslandsInternal.h:132). **Falsifier:** stage-by-stage values at the L91 maximum showing a different operation or a smooth, continuous native field with no route/boundary transition there.
|
||||||
|
|
||||||
|
5. **[CODE] Deep negative values matter because the passage carve uses them as both input and reach.** Its output includes `max(composed, -roughSignedDistance/Q)`, then blends that result back by a geometry-dependent weight. The per-passage reach proof uses the running `composed` value, which may already include earlier passages. With Q=256 subvoxels/voxel and a 0.25m voxel, the L90 reach is `−d·0.25m + 0.24m`. Solving for a 10m reach gives `d=−39.04 voxels`; more-negative values require fallback. This is a density magnitude, not a gradient: the L91 gradient maximum cannot be substituted for `d`. See [`ShapeWorldInternal.h:1689`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1689), [`ShapeWorldInternal.h:2109`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2109), and [`Linux90 REPORT.md:3`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux90/REPORT.md:3). **Falsifier:** a composer trace showing that the bound does not use the running `composed` value or that the max cannot change the output outside the tube.
|
||||||
|
|
||||||
|
## What uses sign, and what uses magnitude
|
||||||
|
|
||||||
|
- **[CODE] Sign checks are only gates in several places.** Packed rock bits, `density<0` support eligibility, and some native-air preservation checks decide which branch to take. They do not preserve the value needed by the later field operations. **Falsifier:** a code path showing one of these branches is the only consumer of the density before meshing.
|
||||||
|
|
||||||
|
- **[CODE] The passage and support results use magnitude.** The carve max and its reach depend directly on `composed`; bridge handling uses a `min`; support uses `min` against a support SDF, and mouth restoration lerps toward the original density. The support sign test gates application, but the applied value still changes density magnitude. See [`ShapeWorldInternal.h:2044`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2044), [`ShapeWorldInternal.h:2109`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2109), and [`ShapeWorldInternal.h:2478`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2478). **Falsifier:** an output audit proving those operations depend only on sign.
|
||||||
|
|
||||||
|
- **[CODE] Other composition stages also consume magnitude.** Cavern graph CSG, boundary signed distances, SDF stamps, and compatibility floor caps apply numeric min/max or smooth min/max operations. Their order is graph → boundary → passage/support → stamps → floor caps on the legacy path; native-lattice fields bypass legacy boundary and passage composition, and native cave graph evaluation ignores its incoming density. See [`ShapeWorldInternal.h:2558`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2558), [`ShapeWorldInternal.h:9420`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:9420), [`ShapeWorldInternal.h:9386`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:9386), and [`ShapeWorldInternal.h:9750`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:9750). **Falsifier:** a path audit showing the tested FI/TN samples bypass these numeric operations.
|
||||||
|
|
||||||
|
- **[MEASURED] L91 demonstrates why sign preservation is insufficient for the mesh.** At c=6, FI and TN had no sign flips but still had moved crossings and up to 43.7793cm displacement; S3 had zero flips at every tested floor but up to 165.434cm displacement at c=6. Marching-cubes edge positions depend on the endpoint values, so changing a negative value while keeping its sign can move the interpolated crossing. See [`Linux91 REPORT.md:27`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux91/REPORT.md:27) and [`StreamBenchmarkPipeline.h:349`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StreamBenchmarkPipeline.h:349). **Falsifier:** paired endpoint values and crossing positions showing no movement when the negative endpoint changes.
|
||||||
|
|
||||||
|
## Bounded-reach options, ranked
|
||||||
|
|
||||||
|
1. **[INFERENCE] Best same-world route: keep the current scalar formula and accelerate exact candidate lookup, including the >10m cases.** An exact spatial program can retain the incoming source magnitude, passage order and support semantics while reducing route-search work. If its candidate coverage includes the full source-dependent reach, L90 reach fallback can fall, but candidate counts will grow; whether total time improves is unmeasured. The current 10m census had 18.28% weighted fallback and active carve/mouth p90 of 13 candidates, so it has not yet shown a fast common path. See [`Linux90 REPORT.md:7`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux90/REPORT.md:7) and [`Linux90 REPORT.md:14`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux90/REPORT.md:14). **Falsifier:** a same-world exact evaluator with full candidate proofs showing the longer-reach lookup is no faster or cannot reduce the fallback cost.
|
||||||
|
|
||||||
|
2. **[INFERENCE] Best bounded-work experiment with a surface-preservation goal: use a conservative surface-cell halo, not a raw source clamp.** Keep the original evaluator at every lattice corner needed by a source crossing, a passage-created surface, or support, mouth, transition, stamp and cap surfaces; only skip the expensive passage work at samples proven irrelevant to final mesh cells. This could reduce the number of L90 queries that need candidate lists, but it does not automatically reduce the reach needed at a surface-relevant corner. Exactness requires proving the halo includes all affected cells and keeping the original values there. **Falsifier:** any changed final crossing, vertex, normal or relevant walkable/support sample outside the covered set.
|
||||||
|
|
||||||
|
3. **[INFERENCE] Replacing raw `d` with a distance-normalized source or clipping the reach would lower candidate counts and fallback, but changes the sampled field and likely the mesh.** A true source distance is not a safe upper bound on the current formula’s raw-density reach; substituting it makes a new operator. L91 already rejected simple source floors c=2–6: the c=6 reach would be 1.74m, yet c=6 still moved crossings by up to 165.434cm. A distance transform may preserve the continuous source zero set, but that alone does not preserve discrete edge interpolation. See [`Linux91 REPORT.md:11`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux91/REPORT.md:11) and [`Linux91 REPORT.md:43`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux91/REPORT.md:43). **Falsifier:** a normalized-source implementation matching the baseline crossing positions and all passage/support audits across strata and LODs.
|
||||||
|
|
||||||
|
4. **[INFERENCE] Moving passage composition after stamps/caps is unlikely to fix the census by itself and changes operation order.** Unless the passage operator’s source or formula also changes, it still needs a source-dependent reach; applying it later also means stamps/caps no longer see the passage output in the same order. **Falsifier:** a reordered implementation with unchanged reach distribution and bit-identical final field, mesh and audits.
|
||||||
|
|
||||||
|
## Passage cost
|
||||||
|
|
||||||
|
**[MEASURED]** Astra’s profile attributes about 40% of passage time to carve distance, 21–24% to wall relief, 14% to support/columns, 4% to mouth shaping, and the remainder to other work. The total passage contribution is reported as roughly 1.4–1.6s. The L90 compiler census was not wired into production and stopped before a prototype. Earlier cache shortcuts regressed timing, and the L90 census still had substantial fallback and candidate counts. See [`BRIEF-pass3.md:7`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Astra1/BRIEF-pass3.md:7), [`BRIEF-pass3.md:22`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Astra1/BRIEF-pass3.md:22), and [`Linux90 REPORT.md:24`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux90/REPORT.md:24). **Falsifier:** a corrected profile or prototype record contradicting those measurements.
|
||||||
|
|
||||||
|
**[INFERENCE]** Same-world acceleration is not logically ruled out: an exact rewrite can precompute route data and speed feature selection while keeping the existing formula and order. But the evidence does **not** show that the full 1.4–1.6s is avoidable, or that the ~1s readiness goal is reachable without changing semantics. A hard geometric reach cap would be a redefinition unless paired with a proven surface-preserving evaluation strategy. **Falsifier:** a complete same-world prototype either meeting the target, which would show the cost is avoidable in practice, or a demonstrated lower bound showing exact evaluation cannot meet it.
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Linux 92 report
|
||||||
|
|
||||||
|
## Startup timing
|
||||||
|
|
||||||
|
All Unreal runs were pinned to CPUs 0–11 and had no address-space limit. Medians below use three runs per row. `LOD0–2 settled` means every currently desired LOD0–2 key was in `LoadedTiles` or `CollisionNotRequiredTiles`.
|
||||||
|
|
||||||
|
| Stratum | Measure | Before | Final | Runs |
|
||||||
|
|---|---|---:|---:|---:|
|
||||||
|
| FI | Snapshot serialization | 491.8 ms | 465.6 ms | 3 / 3 |
|
||||||
|
| FI | `FromJson` | 20.0 ms | 18.9 ms | 3 / 3 |
|
||||||
|
| FI | Compile launch | 0.608 s | 0.569 s | 3 / 3 |
|
||||||
|
| FI | `bReady` | 0.764 s | 0.750 s | 3 / 3 |
|
||||||
|
| FI | BeginPlay end | 0.609 s | 0.570 s | 3 / 3 |
|
||||||
|
| FI | First admission | 0.780 s | 0.762 s | 3 / 3 |
|
||||||
|
| SW | Snapshot serialization | 465.1 ms | 468.0 ms | 3 / 3 |
|
||||||
|
| SW | `FromJson` | 18.9 ms | 19.3 ms | 3 / 3 |
|
||||||
|
| SW | Compile launch | 0.575 s | 0.581 s | 3 / 3 |
|
||||||
|
| SW | `bReady` | 2.738 s | 2.738 s | 3 / 3 |
|
||||||
|
| SW | BeginPlay end | 0.577 s | 0.582 s | 3 / 3 |
|
||||||
|
| SW | First admission | 2.751 s | 2.747 s | 3 / 3 |
|
||||||
|
|
||||||
|
The final FI first-admission times were 0.761, 0.762 and 0.802 s. SW was 2.795, 2.747 and 2.632 s. These are measured timings; the small before/after differences are not a defensible 0.7 s saving. The exporter still runs synchronously in BeginPlay and measured 466 ms FI / 468 ms SW. Attribution of that time to asset reflection and Core assembly preparation is an inference; the trace measures the enclosing `SerializeResolvedWorld` call.
|
||||||
|
|
||||||
|
I removed a redundant second parse in `ResolvedWorldSnapshot::FromJson`. Its measured stage stayed around 19 ms, so this cleanup did not materially move first admission. The serialization/parse/compile/bReady trace events are in `Saved/Linux/l92/unreal-arrival/*.trace.jsonl`.
|
||||||
|
|
||||||
|
Core output stayed identical: world assembly `1ecc162755ab83ac`, recipe `52b010081ad4b550`, plan `a06d76fd11d7a08e`.
|
||||||
|
|
||||||
|
## LOD0–2 arrival
|
||||||
|
|
||||||
|
Times are seconds from BeginPlay start. Before runs used the old `LoadedTiles` count; final runs use the settled predicate above.
|
||||||
|
|
||||||
|
| Stratum | Before, boost on | Final, boost on | Median before → final |
|
||||||
|
|---|---|---|---:|
|
||||||
|
| FI | 3.464, 3.632, 3.588 | 3.396, 3.395, 3.419 | 3.588 → 3.396 s |
|
||||||
|
| SW | 5.072, 5.183, 5.063 | 5.198, 9.256, 5.060 | 5.072 → 5.198 s |
|
||||||
|
|
||||||
|
The LOD result improves for FI and has a long SW tail. Baseline SW commandlets exited 1 because readiness rejected `CollisionNotRequired` tiles; all final boost-on SW commandlets exited 0 with the aligned predicate.
|
||||||
|
|
||||||
|
## Startup apply boost A/B
|
||||||
|
|
||||||
|
The 24-apply / 14 ms boost now spans desired-epoch churn and ends on initial LOD0–2 plus collision-set readiness, or the existing 30 s timeout. All boost-on traces ended on readiness. It remained active 2.2–2.5 s in FI and 2.2–6.4 s in SW.
|
||||||
|
|
||||||
|
| Stratum | Boost on | Boost off | Readout |
|
||||||
|
|---|---|---|---|
|
||||||
|
| FI | 3.396, 3.395, 3.419 s (median 3.396) | 3.490, 3.681, 3.460 s (median 3.490) | 94 ms faster on |
|
||||||
|
| SW | 5.198, 9.256, 5.060 s (median 5.198) | 5.053, incomplete, 5.045 s | No demonstrated gain; one off run did not fully settle by 20 s |
|
||||||
|
|
||||||
|
## Empty collision cache and movement
|
||||||
|
|
||||||
|
Known-empty LOD0 classifications persist across unloads within the current modification epoch, are restored on desired-set rebuilds, and are cleared for affected tiles in both modification paths. The commandlet readiness checks now accept `LoadedTiles` or `CollisionNotRequiredTiles` and separately require the collision desired set to settle.
|
||||||
|
|
||||||
|
The policy-3 30 m/s Unreal trace passed: process and Core world test exited 0, with 46 moving samples and 105 dwell samples. Moving means were 5.568% far holes and 24.709% near readiness; dwell ended at 0% far holes and 100% near readiness. For each target key `(47,-2,-99..-102)`, the trace logged one empty classification, one request/tile result and one unload-preserve event; there were no repeated tile requests. The Core edit integration test passed. No cached-empty key was edited in these traces, so runtime `tile_data_changed` invalidation was not observed; that path is wired and per-key traced in source.
|
||||||
|
|
||||||
|
## Builds, verification and failed runs
|
||||||
|
|
||||||
|
- Final Linux UE build succeeded with `-NoUBA -MaxParallelActions=4`, pinned to CPUs 0–11, without `--as`. Log: `Saved/Linux/l92/unreal-build-final-direct-json.log`.
|
||||||
|
- Final fast tier passed: 274 checks in 288.4 s. Headless build and verification were pinned to CPUs 0–11 and limited to 6 GiB. Logs: `Saved/Linux/l92/fast-build-final.log`, `Saved/Linux/l92/fast-tier-final.log`.
|
||||||
|
- The first fast-tier launch stopped immediately because its stamped binary was missing; the built-binary rerun passed.
|
||||||
|
- Baseline SW commandlets failed the old readiness predicate despite usable arrival samples. One initial SW trace also reached movement before full residency; it was rerun with a 20 s hold. The final SW boost-off repeat 2 remained two LOD0 tiles short at the hold boundary and is reported as incomplete, not assigned an arrival time.
|
||||||
|
- A temporary route-candidate prefilter produced a much shorter export trace, but it touched passage route planning. It was removed; those timings are excluded. The final worktree does not modify the passage composer.
|
||||||
|
- No run exceeded 30 minutes. No commit, push or stash was made.
|
||||||
|
|
||||||
|
## Source files
|
||||||
|
|
||||||
|
- `Source/VoxelForge/Private/VoxelWorld.cpp`
|
||||||
|
- `Source/VoxelForge/Public/VoxelWorld.h`
|
||||||
|
- `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp`
|
||||||
|
- `Standalone/Core/src/VoxelForgeCore.cpp`
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Matched null-RHI FI/SW startup traces for Linux 92."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
HOST = Path("/home/jahni/VFHost")
|
||||||
|
HOST_PLUGIN_SAVED = HOST / "Plugins/VoxelForge/Saved/Linux/l92"
|
||||||
|
HOST_SAVED = HOST / "Saved/Linux/l92"
|
||||||
|
EDITOR = Path("/home/jahni/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd")
|
||||||
|
OUT = ROOT / "Saved/Linux/l92/unreal-arrival"
|
||||||
|
SPAWNS_M = {
|
||||||
|
"FI": (25.8369140625, 3.19921875, -737.0),
|
||||||
|
"SW": (25.8369140625, 3.19921875, -200.0),
|
||||||
|
}
|
||||||
|
MOVE_START_S = 20.0
|
||||||
|
MOVE_DURATION_S = 12.0
|
||||||
|
DWELL_S = 5.0
|
||||||
|
EXIT_S = MOVE_START_S + MOVE_DURATION_S + DWELL_S
|
||||||
|
|
||||||
|
|
||||||
|
def read_events(path: Path) -> list[dict]:
|
||||||
|
events: list[dict] = []
|
||||||
|
if path.is_file():
|
||||||
|
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||||
|
if line.startswith("{"):
|
||||||
|
try:
|
||||||
|
events.append(json.loads(line))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
return events
|
||||||
|
|
||||||
|
|
||||||
|
def run(stratum: str, mode: str, repeat: int, phase: str) -> dict:
|
||||||
|
slot = 1 if stratum == "FI" else 0
|
||||||
|
boost = 0 if mode == "off" else 1
|
||||||
|
label = f"{phase}-{stratum.lower()}-{mode}-{repeat}"
|
||||||
|
trace = OUT / f"{label}.trace.jsonl"
|
||||||
|
abslog = OUT / f"{label}.log"
|
||||||
|
stdout = OUT / f"{label}.stdout.log"
|
||||||
|
output = HOST_PLUGIN_SAVED / "output" / label
|
||||||
|
userdir = HOST_SAVED / "user" / label
|
||||||
|
zendata = HOST_SAVED / "zen" / label
|
||||||
|
ddc = HOST_SAVED / "DDC"
|
||||||
|
for directory in (OUT, output, userdir, zendata, ddc):
|
||||||
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
trace.unlink(missing_ok=True)
|
||||||
|
spawn_cm = ",".join(f"{value * 100.0:.3f}" for value in SPAWNS_M[stratum])
|
||||||
|
command = [
|
||||||
|
str(EDITOR), str(HOST / "VFHost.uproject"), "-nullrhi", "-unattended",
|
||||||
|
"-nop4", "-nosplash", "-NoSound", "-corelimit=12", "-NoZenAutoLaunch",
|
||||||
|
"-notraceserver", "-nocrashreports", "-DisablePlugins=AndroidFileServer",
|
||||||
|
"-ddc=NoZenLocalFallback", f"-userdir={userdir}", f"-ZenDataPath={zendata}",
|
||||||
|
f"-LocalDataCachePath={ddc}", "-run=VoxelForgeExplore", "-seed=0",
|
||||||
|
"-archetype=TunnelNetwork", f"-slot={slot}", "-modes=export", "-gameconfig=1",
|
||||||
|
"-coreworldtest=1", "-ownerfourstrateplan=1", "-corevoxelsizecm=25",
|
||||||
|
"-coreislandtest=0", "-corefallbacktest=0", "-coreofftest=0", "-coreplantest=0",
|
||||||
|
"-settingsref=/Game/VoxelForge/DA_Settings.DA_Settings", "-corehybridmesher=0",
|
||||||
|
f"-out={output}", "-voxel.StreamingCoarseFirst=1",
|
||||||
|
"-voxel.StreamingDynamicNearReserve=1", "-voxel.StreamingLegacyPendingResultCap=0",
|
||||||
|
"-voxel.CoreRegionConcurrentAdmission=1", "-voxel.StreamingResultBacklogCap=256",
|
||||||
|
"-voxel.StreamingReadyQueueDepth=8", "-voxel.StreamingBackgroundWorkerCap=1",
|
||||||
|
"-voxel.CoreColumnCacheDirectMapped=0", "-voxel.CoreMCGradientMemoization=1",
|
||||||
|
"-voxel.StreamingCollisionLookahead=1", f"-voxel.StreamingStartupApplyBoost={boost}",
|
||||||
|
"-voxel.TestMove=1", "-voxel.TestMoveSpeedCmPerSecond=140.0",
|
||||||
|
f"-voxel.TestMoveStartSeconds={MOVE_START_S:.1f}",
|
||||||
|
f"-voxel.TestMoveDurationSeconds={MOVE_DURATION_S:.1f}",
|
||||||
|
f"-voxel.TestExitSeconds={EXIT_S:.1f}", "-voxel.TestMoveDirection=1,0,0",
|
||||||
|
"-voxel.StreamingSchedulerPolicy=3", "-voxel.TestExitOnSteady=0",
|
||||||
|
"-coreworldstreamtrace=1", f"-voxel.StartupTraceSpawnCm={spawn_cm}",
|
||||||
|
"-voxel.StartupTraceUntilEndPlay=1", "-voxel.StartupTraceThroughCrossing=1",
|
||||||
|
"-voxel.StartupTraceMaxTiles=16384", f"-voxel.StartupTraceFile={trace}",
|
||||||
|
f"-abslog={abslog}",
|
||||||
|
]
|
||||||
|
wrapped = ["taskset", "-c", "0-11", "timeout", "1740s", *command]
|
||||||
|
started = time.monotonic()
|
||||||
|
with stdout.open("w", encoding="utf-8") as stream:
|
||||||
|
result = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
|
||||||
|
events = read_events(trace)
|
||||||
|
samples = [e for e in events if e.get("type") == "streaming_sample"]
|
||||||
|
arrival_samples = [e for e in samples if float(e.get("t_s", 0.0)) < MOVE_START_S]
|
||||||
|
ready = None
|
||||||
|
for event in arrival_samples:
|
||||||
|
desired = sum(event.get("desired_by_lod", [0] * 9)[:3])
|
||||||
|
settled = sum(event.get("settled_by_lod",
|
||||||
|
event.get("resident_by_lod", [0] * 9))[:3])
|
||||||
|
if desired and settled >= desired:
|
||||||
|
ready = event
|
||||||
|
break
|
||||||
|
first_admission = next((e for e in events if e.get("type") == "scheduler_admission"
|
||||||
|
and e.get("desired_rebuilt") == 1), None)
|
||||||
|
stage_ends = {e.get("name"): e for e in events if e.get("type") == "stage_end"}
|
||||||
|
core_events = {name: next((e for e in events if e.get("type") == name), None)
|
||||||
|
for name in ("core_snapshot_serialization", "core_snapshot_from_json",
|
||||||
|
"core_compile_launch", "core_compile_start", "core_bready")}
|
||||||
|
row = {
|
||||||
|
"label": label, "stratum": stratum, "slot": slot, "mode": mode,
|
||||||
|
"boost_cvar": boost, "repeat": repeat, "exit_code": result.returncode,
|
||||||
|
"wall_s": round(time.monotonic() - started, 3),
|
||||||
|
"sample_count": len(samples), "arrival_sample_count": len(arrival_samples),
|
||||||
|
"first_lod02_full_ready_s": ready.get("t_s") if ready else None,
|
||||||
|
"first_lod02_full_desired": sum(ready.get("desired_by_lod", [0] * 9)[:3])
|
||||||
|
if ready else None,
|
||||||
|
"first_lod02_full_resident": sum(ready.get("resident_by_lod", [0] * 9)[:3])
|
||||||
|
if ready else None,
|
||||||
|
"first_lod02_full_settled": sum(ready.get("settled_by_lod",
|
||||||
|
ready.get("resident_by_lod", [0] * 9))[:3])
|
||||||
|
if ready else None,
|
||||||
|
"first_admission_t_s": first_admission.get("t_s") if first_admission else None,
|
||||||
|
"begin_play_end_t_s": stage_ends.get("BeginPlay", {}).get("t_s"),
|
||||||
|
"startup_events": {key: value for key, value in core_events.items() if value},
|
||||||
|
"snapshot_serialize_ms": core_events["core_snapshot_serialization"].get("duration_ms")
|
||||||
|
if core_events["core_snapshot_serialization"] else None,
|
||||||
|
"snapshot_from_json_ms": core_events["core_snapshot_from_json"].get("duration_ms")
|
||||||
|
if core_events["core_snapshot_from_json"] else None,
|
||||||
|
"trace": str(trace.relative_to(ROOT)), "abslog": str(abslog.relative_to(ROOT)),
|
||||||
|
"stdout": str(stdout.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
stdout_text = stdout.read_text(encoding="utf-8", errors="replace")
|
||||||
|
failure = re.search(r"\[VoxelForgeCoreWorldTest\] failed: ([^\r\n]+)", stdout_text)
|
||||||
|
if failure:
|
||||||
|
row["world_test_failure"] = failure.group(1)
|
||||||
|
apply_events = [e for e in events if e.get("type") == "startup_apply_budget"]
|
||||||
|
row["apply_budget_events"] = apply_events
|
||||||
|
with (OUT / "runs.jsonl").open("a", encoding="utf-8") as stream:
|
||||||
|
stream.write(json.dumps(row, sort_keys=True) + "\n")
|
||||||
|
print("[L92UnrealArrival] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
if not ready or len(arrival_samples) < 8:
|
||||||
|
raise SystemExit(f"Unreal arrival trace incomplete: {stdout}; exit={result.returncode}")
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--stratum", choices=("FI", "SW"), required=True)
|
||||||
|
parser.add_argument("--mode", choices=("on", "off"), default="on")
|
||||||
|
parser.add_argument("--repeat", type=int, default=1)
|
||||||
|
parser.add_argument("--phase", choices=("before", "after", "after-settled", "after-hold20",
|
||||||
|
"after-direct-json"),
|
||||||
|
default="before")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if not EDITOR.is_file():
|
||||||
|
raise SystemExit(f"missing Unreal commandlet executable: {EDITOR}")
|
||||||
|
row = run(args.stratum, args.mode, args.repeat, args.phase)
|
||||||
|
(OUT / f"{row['label']}.summary.json").write_text(
|
||||||
|
json.dumps(row, indent=2) + "\n", encoding="utf-8")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run the L80 30 m/s policy-3 Unreal trace for Linux 92."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from collections import Counter
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
HOST = Path("/home/jahni/VFHost")
|
||||||
|
HOST_PLUGIN_SAVED = HOST / "Plugins/VoxelForge/Saved/Linux/l92"
|
||||||
|
HOST_SAVED = HOST / "Saved/Linux/l92"
|
||||||
|
EDITOR = Path("/home/jahni/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd")
|
||||||
|
OUT = ROOT / "Saved/Linux/l92/movement/unreal"
|
||||||
|
SPAWN_M = (19.166015625, -14.2001953125, -753.3740234375)
|
||||||
|
SPEED_CM_S = 3000.0
|
||||||
|
MOVE_START_S = 6.0
|
||||||
|
MOVE_DURATION_S = 12.0
|
||||||
|
DWELL_S = 5.0
|
||||||
|
EXIT_S = MOVE_START_S + MOVE_DURATION_S + DWELL_S
|
||||||
|
ORPHAN_KEYS = {(47, -2, -99), (47, -2, -100), (47, -2, -101), (47, -2, -102)}
|
||||||
|
|
||||||
|
|
||||||
|
def read_events(path: Path) -> list[dict]:
|
||||||
|
events = []
|
||||||
|
if path.is_file():
|
||||||
|
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||||
|
if line.startswith("{"):
|
||||||
|
try:
|
||||||
|
events.append(json.loads(line))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
return events
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not EDITOR.is_file():
|
||||||
|
raise SystemExit(f"missing Unreal commandlet executable: {EDITOR}")
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
label = "fast-L80"
|
||||||
|
trace, abslog, stdout = (OUT / f"{label}.trace.jsonl", OUT / f"{label}.log",
|
||||||
|
OUT / f"{label}.stdout.log")
|
||||||
|
output = HOST_PLUGIN_SAVED / "output" / label
|
||||||
|
userdir = HOST_SAVED / "user" / label
|
||||||
|
zendata = HOST_SAVED / "zen" / label
|
||||||
|
ddc = HOST_SAVED / "DDC"
|
||||||
|
for directory in (output, userdir, zendata, ddc):
|
||||||
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
trace.unlink(missing_ok=True)
|
||||||
|
spawn_cm = ",".join(f"{value * 100.0:.3f}" for value in SPAWN_M)
|
||||||
|
command = [
|
||||||
|
str(EDITOR), str(HOST / "VFHost.uproject"), "-nullrhi", "-unattended",
|
||||||
|
"-nop4", "-nosplash", "-NoSound", "-corelimit=12", "-NoZenAutoLaunch",
|
||||||
|
"-notraceserver", "-nocrashreports", "-DisablePlugins=AndroidFileServer",
|
||||||
|
"-ddc=NoZenLocalFallback", f"-userdir={userdir}", f"-ZenDataPath={zendata}",
|
||||||
|
f"-LocalDataCachePath={ddc}", "-run=VoxelForgeExplore", "-seed=0",
|
||||||
|
"-archetype=TunnelNetwork", "-slot=0", "-modes=export", "-gameconfig=1",
|
||||||
|
"-coreworldtest=1", "-ownerfourstrateplan=1", "-corevoxelsizecm=25",
|
||||||
|
"-coreislandtest=0", "-corefallbacktest=0", "-coreofftest=0", "-coreplantest=0",
|
||||||
|
"-settingsref=/Game/VoxelForge/DA_Settings.DA_Settings", "-corehybridmesher=0",
|
||||||
|
f"-out={output}", "-voxel.StreamingCoarseFirst=1",
|
||||||
|
"-voxel.StreamingDynamicNearReserve=1", "-voxel.StreamingLegacyPendingResultCap=0",
|
||||||
|
"-voxel.CoreRegionConcurrentAdmission=1", "-voxel.StreamingResultBacklogCap=256",
|
||||||
|
"-voxel.StreamingReadyQueueDepth=8", "-voxel.StreamingBackgroundWorkerCap=1",
|
||||||
|
"-voxel.CoreColumnCacheDirectMapped=0", "-voxel.CoreMCGradientMemoization=1",
|
||||||
|
"-voxel.StreamingCollisionLookahead=1", "-voxel.StreamingStartupApplyBoost=1",
|
||||||
|
"-voxel.TestMove=1", f"-voxel.TestMoveSpeedCmPerSecond={SPEED_CM_S:.1f}",
|
||||||
|
f"-voxel.TestMoveStartSeconds={MOVE_START_S:.1f}",
|
||||||
|
f"-voxel.TestMoveDurationSeconds={MOVE_DURATION_S:.1f}",
|
||||||
|
f"-voxel.TestExitSeconds={EXIT_S:.1f}", "-voxel.TestMoveDirection=1,0,0",
|
||||||
|
"-voxel.StreamingSchedulerPolicy=3", "-voxel.TestExitOnSteady=0",
|
||||||
|
"-coreworldstreamtrace=1", f"-voxel.StartupTraceSpawnCm={spawn_cm}",
|
||||||
|
"-voxel.StartupTraceUntilEndPlay=1", "-voxel.StartupTraceThroughCrossing=1",
|
||||||
|
"-voxel.StartupTraceMaxTiles=16384", f"-voxel.StartupTraceFile={trace}",
|
||||||
|
f"-abslog={abslog}",
|
||||||
|
]
|
||||||
|
started = time.monotonic()
|
||||||
|
with stdout.open("w", encoding="utf-8") as stream:
|
||||||
|
result = subprocess.run(["taskset", "-c", "0-11", "timeout", "1740s", *command],
|
||||||
|
cwd=ROOT, stdout=stream, stderr=subprocess.STDOUT,
|
||||||
|
check=False, timeout=1740)
|
||||||
|
|
||||||
|
events = read_events(trace)
|
||||||
|
samples = [e for e in events if e.get("type") == "streaming_sample"]
|
||||||
|
move = [e for e in samples if float(e.get("speed_cm_s", 0.0)) >= 50.0]
|
||||||
|
stop = next((e for e in events if e.get("type") == "streaming_test_move_stopped"), None)
|
||||||
|
stop_t = float(stop.get("t_s", 0.0)) if stop else (
|
||||||
|
float(move[0].get("t_s", 0.0)) + MOVE_DURATION_S if move else float("inf"))
|
||||||
|
dwell = [e for e in samples if float(e.get("t_s", 0.0)) >= stop_t
|
||||||
|
and float(e.get("speed_cm_s", 0.0)) < 50.0]
|
||||||
|
rows = []
|
||||||
|
for event in samples:
|
||||||
|
desired = event.get("desired_by_lod", [0] * 9)
|
||||||
|
resident = event.get("resident_by_lod", [0] * 9)
|
||||||
|
covered = event.get("covered_by_lod", resident)
|
||||||
|
far_desired = sum(desired[3:])
|
||||||
|
far_covered = sum(covered[3:])
|
||||||
|
near_desired = sum(desired[:3])
|
||||||
|
near_resident = sum(resident[:3])
|
||||||
|
rows.append({
|
||||||
|
"t_s": event.get("t_s"), "speed_cm_s": event.get("speed_cm_s"),
|
||||||
|
"far_desired": far_desired, "far_covered": far_covered,
|
||||||
|
"far_hole_pct": round(100.0 * (far_desired - far_covered)
|
||||||
|
/ max(1, far_desired), 3),
|
||||||
|
"near_desired": near_desired, "near_resident": near_resident,
|
||||||
|
"near_ready_pct": round(100.0 * near_resident / max(1, near_desired), 3),
|
||||||
|
})
|
||||||
|
moving_holes = [row["far_hole_pct"] for row in rows
|
||||||
|
if float(row["speed_cm_s"] or 0.0) >= 50.0]
|
||||||
|
moving_near = [row["near_ready_pct"] for row in rows
|
||||||
|
if float(row["speed_cm_s"] or 0.0) >= 50.0]
|
||||||
|
dwell_rows = [row for row in rows
|
||||||
|
if float(row["t_s"] or 0.0) >= stop_t
|
||||||
|
and float(row["speed_cm_s"] or 0.0) < 50.0]
|
||||||
|
orphan = Counter()
|
||||||
|
for event in events:
|
||||||
|
key = event.get("tile", event.get("key"))
|
||||||
|
if key is not None and tuple(key) in ORPHAN_KEYS:
|
||||||
|
orphan[(tuple(key), event.get("type"))] += 1
|
||||||
|
stdout_text = stdout.read_text(encoding="utf-8", errors="replace")
|
||||||
|
core_failure = re.search(r"\[VoxelForgeCoreWorldTest\] failed: ([^\r\n]+)", stdout_text)
|
||||||
|
core_pass = "[VoxelForgeCoreWorldTest] pass complete=1" in stdout_text
|
||||||
|
result_row = {
|
||||||
|
"label": label, "policy": "L80", "policy_id": 3,
|
||||||
|
"speed_cm_s": SPEED_CM_S, "exit_code": result.returncode,
|
||||||
|
"wall_s": round(time.monotonic() - started, 3),
|
||||||
|
"sample_count": len(samples), "moving_samples": len(move),
|
||||||
|
"dwell_samples": len(dwell), "core_world_test_pass": core_pass,
|
||||||
|
"core_world_test_failure": core_failure.group(1) if core_failure else None,
|
||||||
|
"far_holes_moving_mean_pct": round(sum(moving_holes) / max(1, len(moving_holes)), 3),
|
||||||
|
"near_ready_moving_mean_pct": round(sum(moving_near) / max(1, len(moving_near)), 3),
|
||||||
|
"far_holes_dwell_last_pct": dwell_rows[-1]["far_hole_pct"] if dwell_rows else None,
|
||||||
|
"near_ready_dwell_last_pct": dwell_rows[-1]["near_ready_pct"] if dwell_rows else None,
|
||||||
|
"move_stopped_t_s": stop_t if stop else None,
|
||||||
|
"orphan_key_events": {f"{key}:{event_type}": count
|
||||||
|
for (key, event_type), count in sorted(orphan.items())},
|
||||||
|
"trace": str(trace.relative_to(ROOT)), "abslog": str(abslog.relative_to(ROOT)),
|
||||||
|
"stdout": str(stdout.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
(OUT / "summary.json").write_text(json.dumps(result_row, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
if rows:
|
||||||
|
with (OUT / "samples.csv").open("w", newline="", encoding="utf-8") as stream:
|
||||||
|
writer = csv.DictWriter(stream, fieldnames=list(rows[0]))
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(rows)
|
||||||
|
print("[L92FastMove] " + json.dumps(result_row, sort_keys=True), flush=True)
|
||||||
|
if result.returncode != 0 or not core_pass or len(move) < 40 or len(dwell) < 16:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in new issue
Block a user