perf(apply): stutter is NOT the game thread -- and the flat floor cannot be relaxed
Two things the owner reported from playing412a7ad. One is answered, one is a null result that Codex flagged itself rather than dressing up. THE FLATTENED TUNNEL FLOORS -- capability-safe, and worth almost nothing He has now flagged these twice: every tunnel carries a smooth flat strip running in and out through otherwise craggy rock. The idea was to make the floor CONDITIONAL -- assert it only where the natural rock fails walkability, leave the rock alone elsewhere. Cheaper and better looking, in theory. The floor is now skipped only when the exact no-floor field passes the same player-fit validator at foot offsets 0, -1, +1. Unknown or failed keeps the slab. natural-floor skips accepted 1,539 of 1,529,241 tests = 0.10% player-fit cells 20,153 -> 20,153 reachable cells 10,483 -> 10,483 arrival -> departure connected, both dihedral p50/p90 2.5429 / 33.8499 deg, IDENTICAL mesh time (their batch) 1.757135 -> 1.757140 s Capability preserved exactly. Visual complaint NOT solved, and no speed gained. ⭐ The useful part is what it PROVED by trying to go further. Every broader policy lost walkable space: analytic-only fit -217, reachable -195 tightened analytic fit -76, reachable -76 exact +/-4 foot fit -16, reachable -16 ⇒ The floor cannot be relaxed without changing the walkability contract or adding a route-aware global proof. That is a real constraint, now measured rather than assumed. Codex's own words, and it was right to lead with them: "the conservative floor change is capability-safe, but it does not visibly solve the flattened-strip complaint." THE STUTTER -- measured, and it is not where we thought Game-thread apply, meshed tiles, under -nullrhi: total apply p50 0.107 ms p95 0.153 ms max 0.273 ms stream upload/RMC p50 0.040 p95 0.088 max 0.124 component creation p50 0.019 p95 0.021 max 0.035 registration p50 0.010 p95 0.011 max 0.020 collision config p50 0.005 p95 0.007 max 0.019 A quarter of a millisecond at worst. The game-thread drain is NOT the hitch. ⇒ Remaining visible stutter is most likely render-thread/RHI work or asynchronous collision cooking behind RealtimeMeshComponent, which is off-limits for edits. Added anyway as a cheap safety valve: MaxMeshApplyMilliseconds, default 2 ms of drain budget per frame. Over 729 apply frames: drain p50/p95/max 0.111/0.182/0.313 ms, budget hits 0, at most 2 visible applies in a frame. It costs nothing today and caps a future burst. Request-to-ready, now timed after game-thread application: all p50 57.1 ms p95 432.9 ms max 919.4 ms -> 2.60 m at 6 m/s meshed p50 134.1 ms p95 457.4 ms max 919.4 ms ⚠️ My own verification: 1.197 / 1.131 / 1.118 s vs 1.088-1.102 s on412a7ad. Mean +4.7% -- below the 17% noise floor, but slower in all three runs, and the validator runs 1.5M times to accept 1,539 skips. Geometry moved by 4 triangles (153,740 -> 153,744), which is allowed. If the next round cannot make that validator earn its place, it should come out. Build 0 errors. Peak RSS 1.67 GB export / 1.90 GB game. Nothing outside Plugins/VoxelForge; RealtimeMeshComponent untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
1 parent
412a7ad219
commit
383ec86c37
11 files changed
+508
-43
No files matched your search
@@ -507,7 +507,11 @@ namespace
|
||||
|
||||
static void BuildTunnelSupportColumnsForChunk(
|
||||
FChunkSDFCache& Cache,
|
||||
const FIntVector& ChunkCoord);
|
||||
const FIntVector& ChunkCoord,
|
||||
const UVoxelGenerator* Generator,
|
||||
const FStrateGenerationParams& Params,
|
||||
uint32 ParamsFingerprint,
|
||||
uint32 LayoutVersion);
|
||||
|
||||
static const FTunnelSupportFloorColumn* FindTunnelSupportColumn(
|
||||
const FChunkSDFCache& Cache,
|
||||
@@ -603,6 +607,7 @@ namespace
|
||||
}
|
||||
|
||||
void PrepareTunnelCoreCache(
|
||||
const UVoxelGenerator& Generator,
|
||||
const UVoxelStrateManager& Manager,
|
||||
const FIntVector& ChunkCoord,
|
||||
const FStrateGenerationParams& Params,
|
||||
@@ -638,7 +643,9 @@ namespace
|
||||
ChunkMaxX + Expansion, ChunkMaxY + Expansion,
|
||||
Params, static_cast<uint32>(Manager.GetWorldSeed()),
|
||||
StrateIndex, nullptr);
|
||||
BuildTunnelSupportColumnsForChunk(OutState.Cache, ChunkCoord);
|
||||
BuildTunnelSupportColumnsForChunk(
|
||||
OutState.Cache, ChunkCoord, &Generator, Params,
|
||||
ParamsFingerprint, LayoutVersion);
|
||||
OutState.OwnerId = OwnerId;
|
||||
OutState.ManagerLifetimeId = ManagerLifetimeId;
|
||||
OutState.Chunk = ChunkCoord;
|
||||
@@ -649,7 +656,11 @@ namespace
|
||||
|
||||
static void BuildTunnelSupportColumnsForChunk(
|
||||
FChunkSDFCache& Cache,
|
||||
const FIntVector& ChunkCoord)
|
||||
const FIntVector& ChunkCoord,
|
||||
const UVoxelGenerator* Generator,
|
||||
const FStrateGenerationParams& Params,
|
||||
uint32 ParamsFingerprint,
|
||||
uint32 LayoutVersion)
|
||||
{
|
||||
const int32 MinX = ChunkCoord.X * CHUNK_SIZE - 1;
|
||||
const int32 MinY = ChunkCoord.Y * CHUNK_SIZE - 1;
|
||||
@@ -661,6 +672,25 @@ namespace
|
||||
Cache.SupportColumnEntries.Init(INDEX_NONE, Cells * Cells);
|
||||
Cache.SupportColumns.Reset();
|
||||
|
||||
// The conditional floor is tested against the exact density pipeline with only the
|
||||
// graph support slab disabled. The sampler is synchronous and lives only for this cache
|
||||
// build; the immutable cache stores the resulting interval decision, never the callback.
|
||||
const auto SampleWithoutTunnelSupportFloor =
|
||||
[Generator, &Params, ParamsFingerprint, LayoutVersion](
|
||||
float X, float Y, float Z) -> float
|
||||
{
|
||||
return Generator != nullptr
|
||||
? Generator->GetDensityWithParams(
|
||||
X, Y, Z, Params, ParamsFingerprint, LayoutVersion,
|
||||
/*bApplyLegacyStructuralPosts=*/true,
|
||||
/*bApplyTunnelSupportFloor=*/false)
|
||||
: 0.0f;
|
||||
};
|
||||
FTunnelSupportFloorDensitySampler NaturalSamplerRef =
|
||||
SampleWithoutTunnelSupportFloor;
|
||||
const FTunnelSupportFloorDensitySampler* NaturalSampler =
|
||||
Generator != nullptr ? &NaturalSamplerRef : nullptr;
|
||||
|
||||
// Empty columns are represented only by INDEX_NONE in the slot map, so
|
||||
// each stored column retains the inline bands for actual graph tubes.
|
||||
for (int32 Y = 0; Y < Cells; ++Y)
|
||||
@@ -672,7 +702,11 @@ namespace
|
||||
static_cast<float>(MinX + X),
|
||||
static_cast<float>(MinY + Y),
|
||||
Cache,
|
||||
Column);
|
||||
Column,
|
||||
NaturalSampler,
|
||||
Params.StrateTopWorldZ,
|
||||
Params.StrateBottomWorldZ,
|
||||
Params.BoundarySealThickness);
|
||||
if (Column.Intervals.Num() == 0)
|
||||
{
|
||||
continue;
|
||||
@@ -736,7 +770,8 @@ namespace
|
||||
*/
|
||||
bool VF_BuildOpStackForChunk(ECaveGeneratorType Type, FVoxelStackParamRefs& Refs,
|
||||
int32 Seed, float SpineRadius, const UVoxelStrateManager* SM,
|
||||
FVoxelOpStack& OutStack, FVoxelOpContext& OutCtx)
|
||||
FVoxelOpStack& OutStack, FVoxelOpContext& OutCtx,
|
||||
const UVoxelGenerator* Generator = nullptr)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
@@ -793,7 +828,9 @@ namespace
|
||||
if (!Refs.Tunnel) { return false; }
|
||||
OutCtx.StrateTopWorldZ = Refs.Tunnel->StrateTopWorldZ;
|
||||
OutCtx.StrateBottomWorldZ = Refs.Tunnel->StrateBottomWorldZ;
|
||||
VoxelDensityOps::BuildTunnelNetworkStack(OutStack, *Refs.Tunnel, Seed, SpineRadius, SM);
|
||||
VoxelDensityOps::BuildTunnelNetworkStack(
|
||||
OutStack, *Refs.Tunnel, Seed, SpineRadius, SM,
|
||||
/*bAppendStructuralPosts=*/true, Generator);
|
||||
return true;
|
||||
|
||||
default:
|
||||
@@ -1259,7 +1296,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
|
||||
CP_UseOpStack = VF_BuildOpStackForChunk(
|
||||
CP_GenType, Refs, Seed, OriginSpineRadius,
|
||||
StrateManager, CP_OpStack, OpCtx);
|
||||
StrateManager, CP_OpStack, OpCtx, this);
|
||||
|
||||
// Le test appelle PrepareChunk, pas la production : c'est exactement la divergence
|
||||
// qui rend un opérateur vert en test et faux en jeu. Les sept `PrepareChunk`
|
||||
@@ -1293,7 +1330,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
if (bCanPrepareNativeTunnelCore)
|
||||
{
|
||||
PrepareTunnelCoreCache(
|
||||
*StrateManager, ChunkCoord, CP_Tunnel, CP_TunnelFP,
|
||||
*this, *StrateManager, ChunkCoord, CP_Tunnel, CP_TunnelFP,
|
||||
LayoutVersion, ManagerLifetimeId, DensityCacheOwnerId, GTunnelCoreCache);
|
||||
}
|
||||
else
|
||||
@@ -1647,7 +1684,8 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float WorldZ,
|
||||
const FStrateGenerationParams& Params,
|
||||
uint32 ParamsFingerprint, uint32 LayoutVersion,
|
||||
bool bApplyLegacyStructuralPosts) const
|
||||
bool bApplyLegacyStructuralPosts,
|
||||
bool bApplyTunnelSupportFloor) const
|
||||
{
|
||||
//=========================================================================
|
||||
// STRATE DENSITY FUNCTION (Morphology Pipeline)
|
||||
@@ -2713,8 +2751,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
// the graph's own tunnel floor/core are structural route geometry. Direct callers of
|
||||
// GetDensityWithParams retain this historical tail; GetDensityAt skips it and applies the
|
||||
// same predicates once in its common MC-space post section below both generation paths.
|
||||
const bool bTunnelSupportFloor =
|
||||
VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
||||
const bool bTunnelSupportFloor = bApplyTunnelSupportFloor
|
||||
&& VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
||||
WorldX, WorldY, WorldZ, SDFCache);
|
||||
if (bTunnelSupportFloor)
|
||||
{
|
||||
@@ -4753,7 +4791,7 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
|
||||
FVoxelOpStack TileStack;
|
||||
if (!VF_BuildOpStackForChunk(CaveType, Refs, Seed, OriginSpineRadius,
|
||||
StrateManager, TileStack, OpCtx))
|
||||
StrateManager, TileStack, OpCtx, this))
|
||||
{
|
||||
// Strate dégénérée ou archétype non porté : `GetDensityAt` retomberait sur le `switch`,
|
||||
// donc la pile ne décrit pas ce que le mesher verra. Aucun verdict.
|
||||
|
||||
Reference in new issue
Block a user