Files
VoxelForge/Source/VoxelForge/Private/Tests/VoxelForgeDetSinCosTest.cpp
T
Fr0zkaandClaude Opus 5 10fd0b6e77 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
2026-09-16 00:55:04 +02:00

103 lines
4.0 KiB
C++

// Pinned contract for VoxelMath::DetSin/DetCos/DetSinCos.
#include "Misc/AutomationTest.h"
#include "VoxelTypes.h"
#include <math.h>
namespace
{
struct FDetSinCosGolden
{
float Input;
uint32 SinBits;
uint32 CosBits;
};
uint32 FloatBits(float Value)
{
uint32 Bits = 0;
FMemory::Memcpy(&Bits, &Value, sizeof(Bits));
return Bits;
}
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FVoxelForgeDetSinCosTest,
"VoxelForge.Determinism.DetSinCos",
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
bool FVoxelForgeDetSinCosTest::RunTest(const FString& Parameters)
{
(void)Parameters;
const float Pi = 3.1415926535897932384626433832795f;
const float TwoPi = 6.283185307179586476925286766559f;
// The expected values are deliberately bit patterns rather than tolerances. This corpus is
// the pinned implementation contract; update it only with an intentional world re-tune.
static const FDetSinCosGolden Corpus[] =
{
{ 0.0f, 0x00000000u, 0x3F800000u },
{-0.0f, 0x80000000u, 0x3F800000u },
{ Pi, 0xB3BBBD2Eu, 0xBF800000u },
{-Pi, 0x33BBBD2Eu, 0xBF800000u },
{ TwoPi, 0x343BBD2Eu, 0x3F800000u },
{-TwoPi, 0xB43BBD2Eu, 0x3F800000u },
{ 0.001f, 0x3A83126Du, 0x3F7FFFF8u },
{-0.001f, 0xBA83126Du, 0x3F7FFFF8u },
{ 123456.789f, 0xBF7FA83Du, 0x3D53E806u },
{-123456.789f, 0x3F7FA83Du, 0x3D53E806u },
{ 1000000.25f, 0xBDDBDDEBu, 0x3F7E853Du },
{-1000000.25f, 0x3DDBDDEBu, 0x3F7E853Du },
};
for (const FDetSinCosGolden& Sample : Corpus)
{
float ActualSin = 0.0f;
float ActualCos = 0.0f;
VoxelMath::DetSinCos(ActualSin, ActualCos, Sample.Input);
AddInfo(FString::Printf(
TEXT("DetSinCos input %.9g -> sin=0x%08X cos=0x%08X"),
Sample.Input, FloatBits(ActualSin), FloatBits(ActualCos)));
TestEqual(
FString::Printf(TEXT("sin bits for %.9g"), Sample.Input),
FloatBits(ActualSin), Sample.SinBits);
TestEqual(
FString::Printf(TEXT("cos bits for %.9g"), Sample.Input),
FloatBits(ActualCos), Sample.CosBits);
TestEqual(
FString::Printf(TEXT("DetSin agrees with paired sin for %.9g"), Sample.Input),
FloatBits(VoxelMath::DetSin(Sample.Input)), FloatBits(ActualSin));
TestEqual(
FString::Printf(TEXT("DetCos agrees with paired cos for %.9g"), Sample.Input),
FloatBits(VoxelMath::DetCos(Sample.Input)), FloatBits(ActualCos));
}
// The canonical export measured |argument| <= 6.19255877. Sweep a small margin around that
// envelope; the large corpus above separately exercises the double range reduction.
constexpr float MeasuredWorldArgumentMax = 6.25f;
constexpr int32 SweepSamples = 200001;
float MaxSinError = 0.0f;
float MaxCosError = 0.0f;
for (int32 Index = 0; Index < SweepSamples; ++Index)
{
const float Alpha = -MeasuredWorldArgumentMax
+ (2.0f * MeasuredWorldArgumentMax)
* static_cast<float>(Index) / static_cast<float>(SweepSamples - 1);
float ActualSin = 0.0f;
float ActualCos = 0.0f;
VoxelMath::DetSinCos(ActualSin, ActualCos, Alpha);
MaxSinError = FMath::Max(MaxSinError, FMath::Abs(ActualSin - ::sinf(Alpha)));
MaxCosError = FMath::Max(MaxCosError, FMath::Abs(ActualCos - ::cosf(Alpha)));
}
AddInfo(FString::Printf(
TEXT("DetSinCos max abs error vs sinf/cosf over [-%.1f,+%.1f], %d samples: "
"sin %.9g, cos %.9g"),
MeasuredWorldArgumentMax, MeasuredWorldArgumentMax, SweepSamples,
MaxSinError, MaxCosError));
TestTrue(TEXT("DetSin max error over measured world range <= 2e-6"), MaxSinError <= 2.0e-6f);
TestTrue(TEXT("DetCos max error over measured world range <= 2e-6"), MaxCosError <= 2.0e-6f);
return true;
}