feat: wire SurfaceWorld (biome-less) + fix a column-memo perf trap
All six checks green, 9399 samples inside the overhang window. The single-entry column memo was correct only if the caller walks a Z column before changing XY, which the mesher does not promise. Iterating X first within a Z slice would miss on every voxel and re-run the whole height stack per voxel, cliff resamples included — an order of magnitude on the plugin's most expensive archetype. The tests could not have caught it: they sample random XY, where a one-entry memo and a 256-entry one behave identically. Only reading the access pattern finds this. Replaced with a direct-mapped 256-entry thread_local table hashed on the XY bit patterns, full key compared on hit, so a collision costs a recompute and never returns the wrong column. Wiring: UsesOperatorStackForChunk returns true for SurfaceWorld only when the strate has no biomes. The original blends heights toward the neighbouring biome across the border band; the stack evaluates one param set, so a biome strate would get a hard seam at every border rather than a subtle shift. The guard sits beside the archetype list so "can this strate take the stack?" stays one question in one place, and GetDensityAt keeps a defensive CP_BiomeCtx check that falls back if the two disagree. UNVERIFIED: not compiled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4dc55b1af3
commit
f7ed9407bf
4 files changed
+107
-6
No files matched your search
@@ -449,13 +449,31 @@ namespace
|
||||
|
||||
const FColumn& GetColumn(float WorldX, float WorldY) const
|
||||
{
|
||||
thread_local FColumn C{};
|
||||
thread_local uint64 CachedId = 0;
|
||||
thread_local float CachedX = FLT_MAX, CachedY = FLT_MAX;
|
||||
// ⚠️ POURQUOI UNE TABLE ET PAS UNE SEULE ENTRÉE. Un mémo à une entrée n'est correct que
|
||||
// si l'appelant descend une colonne Z avant de changer de XY. Le mesher n'en promet
|
||||
// RIEN — s'il itère X en premier dans une tranche Z, chaque voxel raterait et on
|
||||
// relancerait toute la pile de hauteur par voxel, cliff compris (4 resamples
|
||||
// structurels). Ce n'est pas « un peu plus lent », c'est un ordre de grandeur sur
|
||||
// l'archétype le plus cher du plugin.
|
||||
//
|
||||
// Table à correspondance directe, 256 entrées, clé COMPLÈTE comparée sur touche : une
|
||||
// collision ne peut que coûter un recalcul, jamais rendre une mauvaise colonne.
|
||||
//
|
||||
// A single-entry memo is only correct-by-luck: it assumes the caller walks a Z column
|
||||
// before changing XY, which the mesher does not promise. Direct-mapped 256-entry table
|
||||
// with the FULL key compared on hit — a collision costs a recompute, never a wrong column.
|
||||
struct FSlot { uint64 Id; float X, Y; FColumn C; };
|
||||
thread_local FSlot Slots[256] = {};
|
||||
|
||||
if (CachedId != InstanceId || CachedX != WorldX || CachedY != WorldY)
|
||||
const uint32 HX = *reinterpret_cast<const uint32*>(&WorldX);
|
||||
const uint32 HY = *reinterpret_cast<const uint32*>(&WorldY);
|
||||
const uint32 Idx = (HX * 0x9E3779B9u ^ HY * 0x85EBCA6Bu) >> 24; // [0,255]
|
||||
|
||||
FSlot& S = Slots[Idx];
|
||||
if (S.Id != InstanceId || S.X != WorldX || S.Y != WorldY)
|
||||
{
|
||||
CachedId = InstanceId; CachedX = WorldX; CachedY = WorldY;
|
||||
S.Id = InstanceId; S.X = WorldX; S.Y = WorldY;
|
||||
FColumn& C = S.C;
|
||||
|
||||
C.TerrainZ = TerrainStack.EvalHeight(WorldX, WorldY);
|
||||
C.CeilSurf = CeilingStack.EvalHeight(WorldX, WorldY);
|
||||
@@ -482,7 +500,7 @@ namespace
|
||||
if (Slope > KINDA_SMALL_NUMBER) { C.DirX = GX / Slope; C.DirY = GY / Slope; }
|
||||
}
|
||||
}
|
||||
return C;
|
||||
return S.C;
|
||||
}
|
||||
|
||||
/** Le champ structurel nu — l'overhang s'en sert pour emprunter la roche amont. */
|
||||
|
||||
Reference in new issue
Block a user