fix(determinism): own sin/cos on the world path -- same world on every CPU, 3.5% faster
On Win64, FMath::Sin/Cos resolve to CRT sinf/cosf (GenericPlatformMath.h:521/530; no platform override), and the UCRT picks FMA3 or non-FMA3 implementations at startup by CPU. /fp:precise closed only the compiler half, so the same build and seed could build a slightly different world on two Windows PCs, and on Linux. - VoxelMath::DetSin/DetCos/DetSinCos (VoxelTypes.h): fixed double-precision period reduction to a quarter period, then fixed float minimax polynomials; no CRT, FMath or CPU-dependent intrinsic. Measured max error vs sinf 5.96e-8 over the used range (|arg| <= 6.19). - Every world-deciding FMath::Sin/Cos replaced (generator, op stacks, cave morphology, strate manager, content placement, strate measure); none remain in runtime code. - Pinned test VoxelForge.Determinism.DetSinCos (golden bit patterns + error bound). - Diagnostic start-up switch -voxel.CrtFma3=0 (Windows) forces the CRT's non-FMA3 path. - Parse-once console switches now initialise in StartupModule on the game thread; workers only read them (was an unsynchronised first parse inside GetDensityAt). - Corrected the CRC comment that claimed a CRC32 cannot give a false positive. Proof (Codex round; orchestrator re-checked): - Before the fix, FMA3 on vs off was identical on this Ryzen 7 5800X3D: the hole exists but was not observed in this world. After the fix, the canonical exports are byte-identical with FMA3 on and off. - The world changed once, as expected. New canonical export SHA-256 CBB8AE18...906F (geometry B9EF84AC; was B3E5F4C3...377); worms region 9242C093 (was 311CA42C), walk OBJ 20CCF7BD.... Triangle counts unchanged in every tile. Order-independent nearest-vertex distance, worms region: max 8.7 micrometres (the reported "11.4 m" was a line-order artefact of a line-by-line OBJ diff); export region max 3e-5 m. - Capability exact: player-fit 20,830, walk-reachable 10,909, connected. TilePostReachProof, sealed-proof, cross-platform and strate-crossing tests pass. - Performance, headless game path, S/M/S/M vs the committed-tip DLL: static worker 27.41 s vs 28.44 s (-3.6%), moving 59.59 s vs 61.74 s (-3.5%); LOD0 p50/p95 not worse. From Sol's code review (DESIGN-SOL-2026-09-15-CODE.md, section 4.1), branch sol/deterministic-math. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
cda082fa10
commit
10fd0b6e77
16 files changed
+393
-85
No files matched your search
@@ -2928,7 +2928,7 @@ namespace
|
||||
const int32 MaxDownwardSearchCells = FMath::CeilToInt(
|
||||
static_cast<float>(MaxStepHeightVoxelsReal));
|
||||
const float MaxStepHeightVoxels = static_cast<float>(MaxStepHeightVoxelsReal);
|
||||
const float MinimumWalkableNormalZ = FMath::Cos(FMath::DegreesToRadians(
|
||||
const float MinimumWalkableNormalZ = VoxelMath::DetCos(FMath::DegreesToRadians(
|
||||
Settings.PlayerWalkableFloorAngleDegrees));
|
||||
|
||||
TArray<float> SupportHeights;
|
||||
@@ -3263,7 +3263,9 @@ namespace
|
||||
const float DirectionAngle = VoxelHash::ToFloat01(
|
||||
VoxelHash::Mix(Site.Hash ^ 0xCAFEBABEu)) * 2.0f * PI;
|
||||
const float StretchDistance = Site.RadiusXY * 0.7f;
|
||||
const FVector Direction(FMath::Cos(DirectionAngle), FMath::Sin(DirectionAngle), 0.0f);
|
||||
float SinDirection = 0.0f, CosDirection = 0.0f;
|
||||
VoxelMath::DetSinCos(SinDirection, CosDirection, DirectionAngle);
|
||||
const FVector Direction(CosDirection, SinDirection, 0.0f);
|
||||
RoomSDF = VoxelSDF::Capsule(
|
||||
Position,
|
||||
Site.Center + Direction * StretchDistance,
|
||||
@@ -6823,7 +6825,7 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
{
|
||||
const float T = static_cast<float>(ControlIndex)
|
||||
/ static_cast<float>(WanderSegments);
|
||||
const float Envelope = FMath::Sin(T * PI);
|
||||
const float Envelope = VoxelMath::DetSin(T * PI);
|
||||
FVector WorldControlPoint = FMath::Lerp(WorldEndA, WorldEndB, T);
|
||||
const float RadiusBase = FMath::Lerp(RadA, RadB, T);
|
||||
float ControlRadius = RadiusBase;
|
||||
@@ -6840,7 +6842,7 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
// serpentine route without moving either mouth.
|
||||
const float Side = RawSide * 0.65f + PreviousSide * 0.35f;
|
||||
const float WindWave = bNeedsWind
|
||||
? FMath::Sin(static_cast<float>(WindWaves) * PI * T)
|
||||
? VoxelMath::DetSin(static_cast<float>(WindWaves) * PI * T)
|
||||
* WindAmplitude * Envelope
|
||||
* ((LedgeChoiceHash & 1u) != 0u ? 1.0f : -1.0f)
|
||||
: 0.0f;
|
||||
@@ -7364,7 +7366,9 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
CR.ShapeType = 2;
|
||||
const float DirAngle = VoxelHash::ToFloat01(VoxelHash::Mix(CR.Hash ^ 0xCAFEBABEu)) * 2.0f * PI;
|
||||
const float StretchDist = CR.RadiusXY * 0.7f;
|
||||
const FVector Dir(FMath::Cos(DirAngle), FMath::Sin(DirAngle), 0.0f);
|
||||
float SinDir = 0.0f, CosDir = 0.0f;
|
||||
VoxelMath::DetSinCos(SinDir, CosDir, DirAngle);
|
||||
const FVector Dir(CosDir, SinDir, 0.0f);
|
||||
CR.ShapeA = CR.Center + Dir * StretchDist;
|
||||
CR.ShapeB = CR.Center - Dir * StretchDist;
|
||||
CR.ShapeR = FMath::Min(CR.RadiusXY * 0.6f, CR.RadiusZ);
|
||||
@@ -7484,8 +7488,8 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
const float Angle = VoxelHash::ToFloat01(AH4) * PI;
|
||||
const float HalfSpan = CR.RadiusXY
|
||||
* (0.5f + VoxelHash::ToFloat01(VoxelHash::Mix(AH4)) * 0.35f);
|
||||
const float CosA = FMath::Cos(Angle);
|
||||
const float SinA = FMath::Sin(Angle);
|
||||
float SinA = 0.0f, CosA = 0.0f;
|
||||
VoxelMath::DetSinCos(SinA, CosA, Angle);
|
||||
FCachedArch& Arch = CR.Arches[i];
|
||||
Arch.bActive = true;
|
||||
Arch.EndpointA = FVector(ArcCX - CosA * HalfSpan,
|
||||
@@ -7517,8 +7521,7 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
Pinch.CenterX = PnX;
|
||||
Pinch.CenterY = PnY;
|
||||
Pinch.CenterZ = PnZ;
|
||||
Pinch.CosAngle = FMath::Cos(PnAngle);
|
||||
Pinch.SinAngle = FMath::Sin(PnAngle);
|
||||
VoxelMath::DetSinCos(Pinch.SinAngle, Pinch.CosAngle, PnAngle);
|
||||
Pinch.MaxExtent = FMath::Max(
|
||||
FeatureParams.PinchLength, FeatureParams.PinchStrength) + 5.0f;
|
||||
Pinch.HalfLength = FeatureParams.PinchLength * 0.5f;
|
||||
|
||||
Reference in new issue
Block a user