feat(worms): asset-owned worm off switch; offline stack symboliser -- cheap-worm default to be reverted
Worms round (Codex; reviewed by me against its report and PERF-SAMPLED.md).
Part 1, a real off switch: UVoxelStrateDefinition::bEnableWorms (default true, so existing assets
are unchanged). It is deliberately not in FStrateGenerationParams, so the 83-field memo-key guard
is untouched. It resolves after composer candidates, season vectors and boundary blending, is
included in the generation fingerprint, and clamps composer overrides, so a roll cannot re-enable
it. With strength 0 the fused path skips the worm block, the op-stack builder omits the worm
source, and the interval/classifier paths do no worm work. Proof: disabled == hand-zeroed control
byte for byte (70D310A4, 74,672 triangles), capability 20,856 / 10,917, connected, zero worm rows
in the symbolised sampler. voxel.WormsForceOff is a dev-only, world-changing measurement
override, included in the fingerprint.
Part 0: the sampler writes a module map (733 modules, identity checked), and a post-exit
offline symboliser (VoxelStackOfflineSymbolizer, editor module) rebuilds the all-LOD / LOD0 /
LOD1+ exclusive / inclusive / source-line tables. A planted cost was found (5 named rows) and then
removed. DbgHelp still never runs in the measured process.
Part 2, "cheap worms": FAILED to deliver, and the default is wrong. No candidate was faster:
lattice-2/4 gave no gain, and value noise was within noise static and ~2.1% WORSE in moving worker
seconds. Yet this commit carries voxel.WormNoiseMode=1 (value noise) as the default, a field change
(29/64 test tiles) with no benefit, selected after value-noise variants were tried until one
passed capability (three were rejected at 20,821-20,828). The next round reverts the default to
exact Perlin (mode 0) and removes the no-gain world-changing experiment modes.
The finding that matters: disabling worms entirely (WormsForceOff A/B, two interleaved runs each)
saves only ~2.5% static and ~3.4% moving worker time. The sampler's "worm line
VoxelGenerator.cpp:3985 = 19.4% of LOD0" was a misattribution of inlined noise code to one PDB
line. Line-level attribution of inlined code must be confirmed by ablation before a round targets
it. That is why the worm block skip (26b3476) found nothing.
Runs: the full ledger is in PERF-SAMPLED.md. Failures: one moving wrapper hit the graceful-close
timeout and was rerun; the first offline-symboliser commandlet hit the DDC no-writable-nodes fatal
(crash folder UECC-...CE75ACD5, 08:01) and was retried with the writable Saved\DDC.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
11f0c1335b
commit
c132243b30
15 files changed
+2406
-552
No files matched your search
@@ -12,6 +12,7 @@
|
||||
#include "VoxelStartupTrace.h"
|
||||
#include "VoxelTerrainOpDefinition.h" // For UVoxelTerrainOpDefinition::ApplyTo
|
||||
#include "VoxelBiomeDefinition.h" // For UVoxelBiomeDefinition (biome context flatten)
|
||||
#include "VoxelWormField.h" // World-changing worm evaluator mode/lattice identity
|
||||
#include "HAL/IConsoleManager.h"
|
||||
#include "Misc/CommandLine.h"
|
||||
#include "Misc/Crc.h"
|
||||
@@ -33,6 +34,42 @@ namespace
|
||||
GVoxelForgeFloorRound1PassageProjectionCache,
|
||||
TEXT("Reuse exact passage floor projections within one density sample; 0 disables the A/B cache."));
|
||||
|
||||
// This is a measurement override only. The asset-owned bEnableWorms field remains the real
|
||||
// per-strate contract; this process-wide switch exists so the game harness can measure the
|
||||
// cost of enabled versus disabled worms without editing an owner's asset. It is resolved once
|
||||
// before generation params are published so workers cannot disagree mid-world.
|
||||
int32 GVoxelForgeWormsForceOff = 0;
|
||||
std::atomic<int32> GResolvedWormsForceOff(-1);
|
||||
FAutoConsoleVariableRef CVarVoxelForgeWormsForceOff(
|
||||
TEXT("voxel.WormsForceOff"),
|
||||
GVoxelForgeWormsForceOff,
|
||||
TEXT("WORLD-CHANGING DEVELOPMENT-ONLY measurement override. 0=asset behavior, 1=force "
|
||||
"all worm strengths to zero. Never vary this between multiplayer peers or world "
|
||||
"regenerations; it is not the per-strate off switch."),
|
||||
ECVF_Default);
|
||||
|
||||
bool VF_WormsForceOff()
|
||||
{
|
||||
int32 Existing = GResolvedWormsForceOff.load(std::memory_order_acquire);
|
||||
if (Existing >= 0)
|
||||
{
|
||||
return Existing != 0;
|
||||
}
|
||||
|
||||
int32 CommandLineValue = GVoxelForgeWormsForceOff;
|
||||
FParse::Value(
|
||||
FCommandLine::Get(), TEXT("voxel.WormsForceOff="), CommandLineValue);
|
||||
const int32 Resolved = CommandLineValue != 0 ? 1 : 0;
|
||||
int32 Expected = -1;
|
||||
if (!GResolvedWormsForceOff.compare_exchange_strong(
|
||||
Expected, Resolved, std::memory_order_release, std::memory_order_acquire))
|
||||
{
|
||||
return Expected != 0;
|
||||
}
|
||||
GVoxelForgeWormsForceOff = Resolved;
|
||||
return Resolved != 0;
|
||||
}
|
||||
|
||||
struct FRuntimeRoughnessOverrides
|
||||
{
|
||||
bool bSurfaceRoughness = false;
|
||||
@@ -1429,6 +1466,15 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
|
||||
Override->CandidateSeed = CandidateSeed;
|
||||
Override->Archetype = Archetype;
|
||||
Override->Params = Params;
|
||||
if ((VF_WormsForceOff() || !TargetSlot->Definition->bEnableWorms)
|
||||
&& (Archetype == ECaveGeneratorType::TunnelNetwork
|
||||
|| Archetype == ECaveGeneratorType::Underwater))
|
||||
{
|
||||
// The composer is allowed to roll a complete candidate, but a disabled asset must never
|
||||
// retain a worm-enabled candidate in its runtime override, even for code that inspects
|
||||
// the override before GetGenerationParams applies the final resolution gate.
|
||||
Override->Params.TunnelNetworkParams.WormStrength = 0.0f;
|
||||
}
|
||||
VF_SetComposerRuntimeBounds(
|
||||
Override->Params,
|
||||
(float)(TargetSlot->TopChunkZ + 1) * CHUNK_SIZE,
|
||||
@@ -5474,6 +5520,19 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
|
||||
const FStrateSlot& Slot = StrateLayout[SlotIdx];
|
||||
|
||||
// This is the one final resolution gate for the asset-owned worm switch. It is deliberately
|
||||
// applied after composer/season replacements and after boundary blending: a disabled current
|
||||
// strate can never be re-enabled by a rolled candidate or by an enabled neighbour.
|
||||
const auto ResolveFinalParams = [&Slot](FStrateGenerationParams Result)
|
||||
{
|
||||
if (VF_WormsForceOff()
|
||||
|| (Slot.Definition != nullptr && !Slot.Definition->bEnableWorms))
|
||||
{
|
||||
Result.WormStrength = 0.0f;
|
||||
}
|
||||
return VF_ApplyRuntimeRoughnessOverrides(Result);
|
||||
};
|
||||
|
||||
#if WITH_EDITOR
|
||||
if (const FVoxelStrateComposerSlotOverride* Override =
|
||||
FindComposerOverride(Slot.StrateIndex))
|
||||
@@ -5483,7 +5542,7 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
// A candidate is a hard replacement of this slot. Do not blend its rolled vector with an
|
||||
// authored neighbour at a boundary; this is also how the offline fixture measures it.
|
||||
return VF_ApplyRuntimeRoughnessOverrides(Result);
|
||||
return ResolveFinalParams(Result);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -5494,7 +5553,7 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
// Season vectors are already final, measured slot records. Vertical structural posts own
|
||||
// their boundary; blending them with another selected recipe would describe neither one.
|
||||
return VF_ApplyRuntimeRoughnessOverrides(Result);
|
||||
return ResolveFinalParams(Result);
|
||||
}
|
||||
|
||||
FStrateGenerationParams BaseParams = BuildParamsFromDefinition(Slot.Definition);
|
||||
@@ -5589,7 +5648,7 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
float Alpha = 1.0f - ((float)DistFromBottom / (float)EffectiveBlend);
|
||||
Alpha = FMath::Clamp(Alpha, 0.0f, 1.0f);
|
||||
|
||||
return VF_ApplyRuntimeRoughnessOverrides(
|
||||
return ResolveFinalParams(
|
||||
FStrateGenerationParams::Lerp(BaseParams, BelowParams, Alpha));
|
||||
}
|
||||
break;
|
||||
@@ -5643,7 +5702,7 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
// Only blend if alpha > 0 (we're inside the warped transition zone)
|
||||
if (Alpha > 0.0f)
|
||||
{
|
||||
return VF_ApplyRuntimeRoughnessOverrides(
|
||||
return ResolveFinalParams(
|
||||
FStrateGenerationParams::Lerp(BaseParams, BelowParams, Alpha));
|
||||
}
|
||||
}
|
||||
@@ -5685,7 +5744,7 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
float Alpha = 1.0f - ((float)DistFromTop / (float)EffectiveBlend);
|
||||
Alpha = FMath::Clamp(Alpha, 0.0f, 1.0f);
|
||||
|
||||
return VF_ApplyRuntimeRoughnessOverrides(
|
||||
return ResolveFinalParams(
|
||||
FStrateGenerationParams::Lerp(BaseParams, AboveParams, Alpha));
|
||||
}
|
||||
break;
|
||||
@@ -5715,7 +5774,7 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
|
||||
if (Alpha > 0.0f)
|
||||
{
|
||||
return VF_ApplyRuntimeRoughnessOverrides(
|
||||
return ResolveFinalParams(
|
||||
FStrateGenerationParams::Lerp(BaseParams, AboveParams, Alpha));
|
||||
}
|
||||
}
|
||||
@@ -5725,7 +5784,7 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
}
|
||||
|
||||
// Not near any boundary (or Hard transition) — use this strate's params directly
|
||||
return VF_ApplyRuntimeRoughnessOverrides(BaseParams);
|
||||
return ResolveFinalParams(BaseParams);
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
@@ -5745,8 +5804,15 @@ FStrateGenerationParams UVoxelStrateManager::BuildParamsFromDefinition(const UVo
|
||||
{
|
||||
if (!Definition) return FStrateGenerationParams();
|
||||
|
||||
// Base params only — terrain op fields stay 0 until per-room assignment.
|
||||
return Definition->GenerationParams;
|
||||
// Base params only — terrain op fields stay 0 until per-room assignment. Resolve the asset
|
||||
// switch here as well as at the final return so a disabled neighbour cannot leak worm strength
|
||||
// back through a boundary blend.
|
||||
FStrateGenerationParams Result = Definition->GenerationParams;
|
||||
if (VF_WormsForceOff() || !Definition->bEnableWorms)
|
||||
{
|
||||
Result.WormStrength = 0.0f;
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
uint64 UVoxelStrateManager::GetGenerationParamsFingerprint() const
|
||||
@@ -5817,6 +5883,13 @@ uint64 UVoxelStrateManager::GetGenerationParamsFingerprint() const
|
||||
HashValue(Slot.HeightInChunks);
|
||||
HashValue(Slot.Definition->GeneratorType);
|
||||
HashValue(Slot.Definition->bUseOperatorStack);
|
||||
HashValue(Slot.Definition->bEnableWorms);
|
||||
HashValue(VF_WormsForceOff());
|
||||
// The selected evaluator is a world input even though it is deliberately not part of
|
||||
// FStrateGenerationParams (the fixed 83-field memo key). Keep persistent/verdict keys
|
||||
// from crossing an explicit WormNoiseMode/WormLatticeStep experiment.
|
||||
HashValue(VoxelWormField::GetNoiseMode());
|
||||
HashValue(VoxelWormField::GetLatticeStep());
|
||||
HashValue(Slot.Definition->StrateHeightInChunks);
|
||||
HashValue(Slot.Definition->TransitionType);
|
||||
HashValue(Slot.Definition->TransitionBlendChunks);
|
||||
|
||||
Reference in new issue
Block a user