fix: the column key omitted the params — my perf fix broke the overhang
VerticalShaftEquivalence is bit-identical (966 samples inside a shaft), so operator
reuse across archetypes is measured now, not intended.
But SurfaceHeightEquivalence failed: 69/20000 overhang samples differ, 1 crossing the
isosurface. Cause is the ColumnKey from f3faa3b, which was
hash(StrateBottomWorldZ, LayoutVersion, Seed) and omitted the params. Two stacks of
the same strate with different overhang settings therefore shared a key, and the
second read the first's columns, computed with OverhangAmp = 0. The overhang silently
vanished wherever a column was already cached.
Not a test artifact: this is the weakness the codebase already documents for
GSurfColCache (extended AUDIT C2 note) — a live edit that changes params without
moving the strate leaves the key unchanged and serves stale columns. Production masks
it because RebuildStrates bumps LayoutVersion; my key inherited the hole.
Fixed by folding an FCrc::MemCrc32 fingerprint of the params, and of every per-biome
param set, into the key. FSurfaceGenerationParams is verified pure POD, so a memory
CRC cannot produce a false hit; padding can only cause a false miss, i.e. a recompute.
A perf optimisation introduced a correctness bug and the tests caught it the same
day. The failure was invisible to inspection and produced plausible terrain.
Known and not fixed: VerticalShafts proves 0 of 60 tiles because EffectOverBox
returns CarveOnly whenever any shaft sits within a Spacing*1.6 halo rather than
testing real connector capsules. Pessimistic, not wrong — lost CPU, never a hole.
UNVERIFIED: not compiled.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -470,6 +470,19 @@ namespace
|
||||
// would falsely hit the pristine slot at (0,0). PrepareChunk upgrades this to the shared
|
||||
// strate key; without it we simply cache per instance. Degrade, never lie.
|
||||
ColumnKey = InstanceId;
|
||||
|
||||
// Empreinte des params qui déterminent une colonne. `FSurfaceGenerationParams` est du
|
||||
// POD pur (que des float/int/bool, vérifié : aucun TArray, FString ni pointeur), donc
|
||||
// un CRC mémoire ne peut pas produire de FAUX POSITIF — au pire du padding non
|
||||
// initialisé donne un faux NÉGATIF, c'est-à-dire un recalcul. Se tromper du côté qui
|
||||
// coûte du CPU plutôt que du côté qui rend une mauvaise colonne.
|
||||
// Pure POD (verified: no TArray/FString/pointer), so a memory CRC cannot produce a false
|
||||
// HIT; at worst padding causes a false miss, i.e. a recompute. Err toward CPU, not lies.
|
||||
ParamsFingerprint = FCrc::MemCrc32(&P, sizeof(P));
|
||||
for (const FSurfaceGenerationParams& BP : BiomeParams)
|
||||
{
|
||||
ParamsFingerprint = VoxelHash::Mix(ParamsFingerprint ^ FCrc::MemCrc32(&BP, sizeof(BP)));
|
||||
}
|
||||
}
|
||||
|
||||
/** Sans biomes — délègue, pour qu'il n'existe qu'UN corps de construction et UN compteur
|
||||
@@ -648,8 +661,27 @@ namespace
|
||||
const uint32 A = (uint32)FMath::RoundToInt(Ctx.StrateBottomWorldZ);
|
||||
const uint32 B = Ctx.LayoutVersion;
|
||||
const uint32 C = Ctx.Seed;
|
||||
|
||||
// ⚠️ `ParamsFingerprint` EST OBLIGATOIRE, et son absence a été un vrai bug — attrapé par
|
||||
// `SurfaceHeightEquivalence` au build suivant (69/20000 écarts, 1 traversée d'iso).
|
||||
//
|
||||
// Sans lui, la clé ne contenait que (strate, layout, seed). Deux piles de la MÊME strate
|
||||
// avec des params DIFFÉRENTS obtenaient donc la même clé et se partageaient les colonnes :
|
||||
// la seconde lisait les colonnes de la première, calculées avec `OverhangAmp = 0`, et
|
||||
// l'overhang disparaissait purement et simplement.
|
||||
//
|
||||
// ET CE N'EST PAS QU'UN ARTEFACT DE TEST : c'est exactement la faiblesse que le code
|
||||
// documente déjà pour `GSurfColCache` (VoxelGenerator.cpp, note AUDIT §C2 étendue) —
|
||||
// « une édition à chaud qui change les params SANS déplacer la strate laisse la clé
|
||||
// identique et sert des colonnes périmées ». En production `LayoutVersion` bouge à chaque
|
||||
// `RebuildStrates`, ce qui masque le trou ; ma clé en avait hérité, et le test l'a trouvé
|
||||
// tout de suite. Empreinte incluse ⇒ le trou est fermé ici, pas seulement masqué.
|
||||
//
|
||||
// The fingerprint is REQUIRED: without it two stacks of the same strate with different
|
||||
// params shared columns, and the overhang silently vanished. Same weakness the codebase
|
||||
// already documents for GSurfColCache, which LayoutVersion merely masks.
|
||||
ColumnKey = ((uint64)VoxelHash::Mix(A ^ VoxelHash::Mix(B)) << 32)
|
||||
| (uint64)VoxelHash::Mix(C ^ VoxelHash::Mix(A));
|
||||
| (uint64)VoxelHash::Mix(C ^ VoxelHash::Mix(A) ^ ParamsFingerprint);
|
||||
if (ColumnKey == 0) { ColumnKey = 1; } // 0 = « jamais préparé »
|
||||
}
|
||||
|
||||
@@ -692,7 +724,8 @@ namespace
|
||||
TArray<TUniquePtr<IVoxelHeightOp>> PerBiomeStructural; // pente d'overhang par biome
|
||||
|
||||
uint64 InstanceId = 0; // unique, jamais recyclée — le repli quand PrepareChunk n'a pas eu lieu
|
||||
uint64 ColumnKey = 0; // l'identité PARTAGÉE (strate + layout + seed) : voir PrepareChunk
|
||||
uint64 ColumnKey = 0; // l'identité PARTAGÉE (strate + layout + seed + params) : voir PrepareChunk
|
||||
uint32 ParamsFingerprint = 0; // sans lui, deux piles de la même strate se volaient leurs colonnes
|
||||
};
|
||||
|
||||
//=========================================================================
|
||||
|
||||
Reference in New Issue
Block a user