fix(opstack): two cell-sweep pads assumed MinRadius <= MaxRadius; a third already didn't
Second defect from the full 28-op EffectOverBox audit. Three ops roll a radius as Lerp(MinRadius, MaxRadius, hash01), which lands in [min(A,B), max(A,B)] -- Lerp does not require A <= B. Their EffectOverBox sweeps lattice cells padded by the largest radius a cell could hold, so a pad below the true maximum means the cells are never examined and the op reports Identity for a box its own Eval will fill or carve: no geometry, no collision. FGridColumnMod ~1086 Max(MaxRadius, 0) exposed FShaftFieldSource ~1436 Max(ShaftMaxRadius, ConnectorRadius) exposed FIslandBlobSource ~1803 Max(IslandMinRadius, IslandMaxRadius) already correct The third one is the argument: the concern was met and guarded once in this same file, and the other two shipped without it. Fixed with FMath::Max3, a spelling already used here (~2481) and in VoxelCaveMorphology.cpp. Shipped defaults are ordered correctly (2/5, 2/7, 5/11), so this is a NO-OP at defaults -- that is its acceptance signal, and any moved number means the diff did more than intended. It needs a mis-ordered asset value, which nothing prevents: ClampMin is a per-property floor and UE cannot express "<= that other property". ColumnMinRadius is also settable per-room via UVoxelTerrainOpDefinition. Deliberately NOT fixed by normalising the params: swapping the Lerp endpoints maps the same hash to a different radius, changing geometry and breaking the eight equivalence tests. Only the bound becomes conservative; Eval is byte- identical -- verified, no Eval/Roll/GetCells line in the diff. Also audited and found sound (recorded so they are not re-chased): FPassageCarveOp and the passage bounding sphere, FOriginSpineOp, FBoundarySealOp, FShaftLedgeMod, FCaveArchMod, FDomeMod. Not built -- Jahni builds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1081,9 +1081,10 @@ namespace
|
||||
{
|
||||
if (ColDensity <= 0.0f || Spacing <= 0.0f) { return EVoxelOpEffect::Identity; }
|
||||
|
||||
// Marge : le centre d'une colonne vit dans sa cellule, son influence porte au plus
|
||||
// MaxRadius + ColBlend. Sur-estimer coûte du CPU ; sous-estimer serait un trou.
|
||||
const float Reach = FMath::Max(MaxRadius, 0.0f) + ColBlend;
|
||||
// Marge : l'enveloppe de `Lerp(MinRadius, MaxRadius, t)` est max(MinRadius, MaxRadius),
|
||||
// pas `MaxRadius` seul si l'asset inverse les paramètres. The bound must cover both
|
||||
// endpoints; using `MaxRadius` alone would leave a hole when the asset reverses them.
|
||||
const float Reach = FMath::Max3(MinRadius, MaxRadius, 0.0f) + ColBlend;
|
||||
|
||||
const int32 CX0 = FMath::FloorToInt(((float)VoxelBox.Min.X - Reach) / Spacing);
|
||||
const int32 CX1 = FMath::FloorToInt(((float)VoxelBox.Max.X + Reach) / Spacing);
|
||||
@@ -1433,7 +1434,10 @@ namespace
|
||||
// La source répond pour la paire source+carve (SIMPLIFICATION DE PHASE 1) : `CarveOnly`
|
||||
// si une primitive atteint la boîte, `Identity` sinon. `ExtraReach` couvre la rugosité
|
||||
// et le blend en aval — le sous-estimer serait un TROU.
|
||||
const float Pad = FMath::Max(P.ShaftMaxRadius, P.ConnectorRadius) + ExtraReach;
|
||||
// L'enveloppe doit couvrir les deux bornes de `Lerp(ShaftMinRadius, ShaftMaxRadius, t)`,
|
||||
// pas `ShaftMaxRadius` seul si l'asset inverse les paramètres. The bound must cover
|
||||
// both radius endpoints before adding connector and downstream reach.
|
||||
const float Pad = FMath::Max3(P.ShaftMinRadius, P.ShaftMaxRadius, P.ConnectorRadius) + ExtraReach;
|
||||
const FBox Padded = VoxelBox.ExpandBy(Pad);
|
||||
|
||||
const float Spacing = FMath::Max(P.ShaftSpacing, 1.0f);
|
||||
|
||||
Reference in New Issue
Block a user