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
103 lines
4.0 KiB
C++
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;
|
|
}
|