From 5156084cac00e04b6833c3ed62b2ebf7b71ac987 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Fri, 11 Sep 2026 05:05:47 +0200 Subject: [PATCH] audit(gen): which post-hoc ops are tunnel-floors in disguise -- and the X+2A budget is unsound MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The owner's framing for the night: "if it sees performance opportunity by unifying some process that should be, or like the tunnels, have it be native more than patching it after generation". This is the audit. No generator behaviour changed; the only code here is request/generation timing instrumentation in VoxelWorld. STRUCTURAL-POST AUDIT -- verdict per operator FBoundarySealOp 0.000011 us/sample KEEP. A boundary condition, not a workaround. FXYEdgeSealOp 0.000054 us/sample KEEP. Domain constraint; identity today with WorldRadiusVoxels=0. FOriginSpineOp 0.000588 us/sample PARTLY family, but origin anchoring and ordering are special. FPassageCarveOp 0.019977 us/sample Could share the volume/floor representation, but its boundary-crossing order and current-density carve are load-bearing. floor protection 0.061298 us/sample ⭐ THIS IS STILL THE REAL PATCH. TunnelAuthoredFloorProtectionClamps fired 29,634 / 29,634 -- every authored floor sample -- while the named support/room fallbacks fired ZERO. So the backstop is not a rare safety net; it is doing the work, and the tunnel floor is not yet native. ⭐ AND IT NAMED THE PATH OUT, which I had not seen: the floor is authored at build time but LATER WRITERS can still refill or erase the clearance, so a clamp re-asserts it. Native means either composing the floor AFTER all writers, or making every later writer floor-aware with a proof it cannot violate the invariant. Removing the clamp today would be unsafe. It did not remove it. PRIMITIVE FAMILY -- valid, but my proposal was too crude Algebraically sound: solid + floor via SmoothMax + bounded relief + smooth union. Rooms, swept tunnels, landing rooms and some passages can share a cached walkable-volume DESCRIPTOR (shape/profile, baked floor, relief params, mouth/room ownership, capability metadata). ⛔ But they should NOT become one undifferentiated operator: tunnels are swept multi-segment chains, passages cross structural boundaries, origin landings have special finite geometry, seals are domain conditions. Flattening those would WEAKEN interval proofs and operation ordering -- the opposite of what I hoped unification would buy. Renaming the passage carve would not help either; it Lerps against incoming density, so its global bound stays unbounded regardless. X + 2A CLEARANCE BUDGET -- SETTLED, AND IT IS UNSOUND AS SPECIFIED A surface-displacement budget needs BOTH an upper bound on field perturbation AND a lower bound on the gradient near the zero surface. The second fails at flat or degenerate crossings, and the current-density Lerp/Min operators make a global bound unavailable. Perlin derivative bounds alone do not fix it. ⇒ Usable only inside separately proven primitive regions; elsewhere the validator and backstop must stay. The owner and I both assumed interval propagation already gave us this. It does not, and now we know why rather than just that. ITEM ZERO -- the suspected in-game regression: NARROWED, NOT CONFIRMED Queue wait is real, but meshed latency is primarily GENERATION time, not queue. One EMPTY LOD0 generation took 551.7 ms, so it is not pure queue saturation either. RoomGraphSource measures 0.278939 us/sample, which cannot explain a 400-1000 ms mesh. Codex declined to call it a confirmed code regression on this data. Still open, and it now points at the known-unsolved LOD0 mixed-classifier fallback rather than at the floor work. REFERENCE ARCHETYPE -- costs, and a correction to my arithmetic CorridorRadius 12.5 voxels is a 6.25 m DIAMETER (I said 3.1 m), and validation caps it at 16 voxels. The 15-20 m reference hall needs a separate gallery archetype or a larger cell-size budget; raising the value violates current lattice/wall assumptions. FCaveTerraceMod is a true no-op disabled (0.000531 us/sample); enabled it adds two cached SDF probes, estimated lower bound ~0.56 us for a qualifying sample -- estimated, not measured. Columns belong to slab generation; TunnelNetwork needs a baked column op. Water is presentation, not rock density, and null-RHI cannot price it. Build 0 errors. No RMC files, links, historical checkouts or logs touched. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb --- Source/VoxelForge/Private/VoxelWorld.cpp | 35 ++++++++++++++++++++++-- Source/VoxelForge/Public/VoxelWorld.h | 4 +++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/Source/VoxelForge/Private/VoxelWorld.cpp b/Source/VoxelForge/Private/VoxelWorld.cpp index e27c5d9..778cec3 100644 --- a/Source/VoxelForge/Private/VoxelWorld.cpp +++ b/Source/VoxelForge/Private/VoxelWorld.cpp @@ -1306,15 +1306,36 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result) // dequeued. This makes request-to-ready include component setup, stream/buffer submission, // and collision configuration—the point at which the chunk is actually visible/collidable. const bool bResultEmpty = Result.bEmpty || !Result.Streams; + const uint64 ApplyStartCycles = GVoxelForgeProfileTileGeneration != 0 + ? FPlatformTime::Cycles64() : 0; const auto LogTileReady = [&]() { if (GVoxelForgeProfileTileGeneration != 0 && Result.RequestStartCycles != 0) { + const uint64 ReadyCycles = FPlatformTime::Cycles64(); + const bool bHaveWorkerTiming = Result.GenerationStartCycles != 0 + && Result.GenerationEndCycles >= Result.GenerationStartCycles; + const double RequestToReady = FPlatformTime::ToSeconds64( + ReadyCycles - Result.RequestStartCycles); + const double ApplySeconds = ApplyStartCycles != 0 + ? FPlatformTime::ToSeconds64(ReadyCycles - ApplyStartCycles) : 0.0; + const double GenerationSeconds = bHaveWorkerTiming + ? FPlatformTime::ToSeconds64( + Result.GenerationEndCycles - Result.GenerationStartCycles) : 0.0; + const double WorkerQueueSeconds = bHaveWorkerTiming + ? FPlatformTime::ToSeconds64( + Result.GenerationStartCycles - Result.RequestStartCycles) : 0.0; + const double ResultQueueSeconds = bHaveWorkerTiming + ? FMath::Max(0.0, RequestToReady - WorkerQueueSeconds + - GenerationSeconds - ApplySeconds) : 0.0; UE_LOG(LogTemp, Display, - TEXT("[VoxelForgeTileReady] level=%d empty=%d request_to_ready=%.6f"), + TEXT("[VoxelForgeTileReady] tile=(%d,%d,%d) level=%d empty=%d " + "request_to_ready=%.6f worker_queue=%.6f generation=%.6f " + "result_queue=%.6f apply=%.6f"), + Result.Tile.Coord.X, Result.Tile.Coord.Y, Result.Tile.Coord.Z, Result.Tile.Level, bResultEmpty ? 1 : 0, - FPlatformTime::ToSeconds64( - FPlatformTime::Cycles64() - Result.RequestStartCycles)); + RequestToReady, WorkerQueueSeconds, GenerationSeconds, + ResultQueueSeconds, ApplySeconds); } }; @@ -1405,10 +1426,13 @@ void AVoxelWorld::SyncRemeshTile(const FVoxelTileKey& Tile) Result.RequestStartCycles = bProfileModification ? FPlatformTime::Cycles64() : 0; const uint64 GenerateStartCycles = bProfileModification ? FPlatformTime::Cycles64() : 0; + Result.GenerationStartCycles = GenerateStartCycles; GenerateTileResult(Tile, OriginVoxels, Step, Cells, GenerationEpoch, /*bWantCapture*/ false, BandVoxLo, BandVoxHi, BandChunkLo, BandChunkHi, /*bSheetTile*/ false, /*SheetChunkZ*/ 0, /*Hole*/ 0, 0, 0, 0, Result); // hole unused (not a sheet tile) + Result.GenerationEndCycles = GenerateStartCycles != 0 + ? FPlatformTime::Cycles64() : 0; const double GenerateSeconds = bProfileModification ? FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - GenerateStartCycles) @@ -2290,9 +2314,14 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, bool bHighPriority) if (ShouldAbortWork()) return; FChunkResult Result; + const uint64 GenerationStartCycles = GVoxelForgeProfileTileGeneration != 0 + ? FPlatformTime::Cycles64() : 0; + Result.GenerationStartCycles = GenerationStartCycles; GenerateTileResult(Tile, OriginVoxels, Step, Cells, TaskEpoch, bWantCapture, BandVoxLo, BandVoxHi, BandChunkLo, BandChunkHi, bSheetTile, SheetChunkZ, HoleMinX, HoleMinY, HoleMaxX, HoleMaxY, Result); + Result.GenerationEndCycles = GenerationStartCycles != 0 + ? FPlatformTime::Cycles64() : 0; Result.RequestStartCycles = RequestStartCycles; if (!ShouldAbortWork()) diff --git a/Source/VoxelForge/Public/VoxelWorld.h b/Source/VoxelForge/Public/VoxelWorld.h index 75700c4..e81b56c 100644 --- a/Source/VoxelForge/Public/VoxelWorld.h +++ b/Source/VoxelForge/Public/VoxelWorld.h @@ -87,6 +87,10 @@ struct FChunkResult // Monotonic request timestamp used only for streaming telemetry. It is carried through the // worker queue so the profile can report request-to-ready, not just worker generation time. uint64 RequestStartCycles = 0; + // Worker timing boundaries for separating scheduler wait from generation and result/apply wait. + // These are telemetry-only and remain zero when tile profiling is disabled. + uint64 GenerationStartCycles = 0; + uint64 GenerationEndCycles = 0; bool bAborted = false; // Worker observed shutdown; never mark this tile loaded bool bEmpty = true; // true ⇒ all-air tile (Streams null); still marked loaded so we don't re-submit // F17 — the mesher classifies every triangle semantically (sky-cap = down-facing near the