feat(math): Route game logic math through WWMath with 3-mode deterministic support#2670
feat(math): Route game logic math through WWMath with 3-mode deterministic support#2670Okladnoj wants to merge 8 commits intoTheSuperHackers:mainfrom
Conversation
Integrate GameMath library (fdlibm) for bit-perfect cross-platform floating-point reproducibility. When USE_DETERMINISTIC_MATH is active, all WWMath functions (Sin, Cos, Sqrt, Inv_Sqrt, Float_To_Long, Acos, Asin, Atan, Atan2) use fdlibm instead of x87 asm or system CRT. - Add GameMath via FetchContent with global include paths - Replace Trig.cpp with inline WWMath::*Trig wrappers - Add WWMath::*Origin wrappers for bare CRT calls in game logic - Prioritize USE_DETERMINISTIC_MATH over _MSC_VER/_M_IX86 guards - Verified: win32 replay CRC matches original x87 output
Add global Sqrt(double) to trig.h/Trig.cpp, routing through WWMath::SqrtOrigin(). Replace 5 bare CRT sqrt() calls in BaseType.h (Coord2D::length, Coord2D::toAngle, ICoord2D::length, Coord3D::length, ICoord3D::length) with the new Sqrt() gateway. This closes the last known CRT math leak in CRC-critical code paths. Same pattern as existing Sin/Cos/ACos routing via Trig.cpp.
|
| Filename | Overview |
|---|---|
| Core/Libraries/Source/WWVegas/WWMath/wwmath.h | Core of the PR: adds conditional gmath.h include via __has_include, USE_DETERMINISTIC_MATH macro, and two new families of inline wrappers (*_Trig and *_Origin) routing all trig/math calls to fdlibm in deterministic mode or CRT otherwise. |
| cmake/gamemath.cmake | New FetchContent declaration for the GameMath fdlibm fork; pins to a specific commit SHA, disables intrinsics (FORCE) for cross-arch determinism, and uses global include_directories so __has_include fires consistently. |
| cmake/compilers.cmake | Adds -ffp-contract=off for non-MSVC compilers to prevent FMA contraction that would break bit-exact CRC parity between MSVC /fp:precise and Clang. |
| Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp | Splits into two distinct CRC functions (deterministic vs native) and adds a runBenchmark utility gated behind RUN_MATH_BENCHMARK_REPLAY400_FLAG=0. Two if (i == 0) guards have statement bodies on the same line as the condition. |
| Core/Libraries/Include/Lib/BaseType.h | Routes sqrt calls in Coord2D, ICoord2D, Coord3D, ICoord3D helper methods to the new Sqrt() free function from trig.h, which delegates to WWMath::Sqrt_Origin. |
| Core/Libraries/Include/Lib/trig.h | Adds double Sqrt(double x) declaration alongside the existing trig function declarations; implementation lives in Trig.cpp. |
| Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp | Mechanical substitution of raw sqrt/fabs calls with WWMath::Sqrt_Origin/FAbs_Origin throughout pathfinding cost calculations. |
| Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt | Links core_wwmath against the gamemath FetchContent target (PUBLIC, non-VC6 only), propagating deterministic math headers to all consumers. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Game Logic call site] --> B{WWMath wrapper e.g. Sqrt_Origin / Sin_Trig}
B --> C{USE_DETERMINISTIC_MATH defined?}
C -- Yes non-VC6 + gmath.h found --> D[fdlibm / GameMath gm_sqrtf / gm_sinf CRC: 76B53840]
C -- No non-VC6 gmath.h absent --> E[CRT system math sqrtf / sinf CRC: E8B6385A]
C -- VC6 build IS_VS6_BUILD --> F[x87 inline asm or CRT CRC: B7B83850]
D --> G[float result]
E --> G
F --> G
G --> H[Game State / CRC]
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp:121-138
Both `if (i == 0)` checks place the statement body on the same line as the condition, violating the project's style rule requiring bodies on a separate line for precise debugger breakpoint placement.
```suggestion
if (i == 0)
crcDet = xfer.getCRC();
}
_fpreset();
clock_t endDet = clock();
double timeDetMs = (double)(endDet - startDet) / CLOCKS_PER_SEC * 1000.0;
clock_t startNat = clock();
UnsignedInt crcNat = 0;
setFPMode();
for (i = 0; i < iterations; ++i)
{
XferCRC xfer;
xfer.open("SimMathNat");
appendSimulationMathCrc_Native(xfer);
xfer.close();
if (i == 0)
crcNat = xfer.getCRC();
```
Reviews (7): Last reviewed commit: "refactor(math): Address xezon review fee..." | Re-trigger Greptile
Here is what replay playback looks like at the moment.
I’m testing this on a separate branch: I slightly adjusted the CI there so I can run Win32 and get access to the game resources. |
854cc7b to
779f714
Compare
|
You did not review the changes you made with AI. It has issues that you should fix before asking it to be reviewed. |
…erage - Restore original Trig.cpp code (author comments, defines, REGENERATE_TRIG_TABLES) - Keep only functional changes: sinf->WWMath::SinTrig redirects + Sqrt(double) - Restore original tab alignment in wwmath.h #define block - Route remaining CRT calls in SimulationMathCrc through WWMath (sinh/cosh/tanh/logf) - Add cmake comments explaining FORCE usage
|
This change does too many things. It is better to first consolidate trig and wwmath and maybe other sources of math, before going into gamemath territory. |
- Add RUN_MATH_BENCHMARK_REPLAY400_FLAG to SimulationMathCrc.h - Implement 10000-iteration dual-path benchmark in SimulationMathCrc.cpp - Inject benchmark trigger into GameLogic::update at replay frame 400 - Benchmark measures CRT vs WWMath precision and performance
4b5675d to
ddea128
Compare
- Declare loop iterator outside of for loop in runBenchmark to support legacy MSVC scoping rules
…ministic math parity
@xezon Hey! I understand your point, but the reason I didn't fully consolidate As we saw in PR #2602, fully removing That's exactly why I chose this "routing" approach for this PR. By keeping the Perhaps the best option would be to test this PR first, and if everything is fine — merge it. And only after that, we can focus on a second PR dedicated purely to the architectural cleanup (removing |
- Merge gmath.h include + USE_DETERMINISTIC_MATH into single __has_include block - Replace all #ifdef/#if defined() with #if USE_DETERMINISTIC_MATH - Remove TheSuperHackers @fix prefix from cmake comment - Expand ODR abbreviation in gamemath.cmake comment - Add blank lines after setFPMode() in benchmark - Fix iters abbreviation in printf - Simplify benchmark: remove replay dependency, auto-trigger at frame 400
- Merge gmath.h include + USE_DETERMINISTIC_MATH into single __has_include block - Replace all #ifdef/#if defined() with #if USE_DETERMINISTIC_MATH - Remove TheSuperHackers @fix prefix from cmake comment - Expand ODR abbreviation in gamemath.cmake comment - Add blank lines after setFPMode() in benchmark - Fix iters abbreviation in printf - Simplify benchmark: remove replay dependency, auto-trigger at frame 400 - Rename WWMath wrappers to Function_Name convention (578 replacements, 79 files)

Rework of #2602, incorporating review feedback:
USE_DETERMINISTIC_MATHunconditional for non-VC6 — missinggmath.his now a compile error instead of silent fallback to x87/CRTCI: win32 + vc6 ✅, replay checks ✅
Open question: Replay checks pass both with and without
USE_DETERMINISTIC_MATH, even though golden replays were recorded with an x87 build. The replays may not containMSG_LOGIC_CRCmessages, meaning the check only validates absence of crashes rather than game state CRC parity. If anyone has insight on this — please share.Testing results
Cross-platform deterministic math parity verified with
SimulationMathCrc::runBenchmark— computes CRC over 10 000 iterations of sin/cos/tan/atan2/sqrt/pow across a fixed input set.fdlibm(deterministic)76B53840fdlibm(deterministic)76B53840E8B6385AE8B6385AB7B838508BB5B841Key fix:
-ffp-contract=offincmake/compilers.cmake— prevents Clang from emitting FMA instructions (fmadd) that skip intermediate rounding, breaking bit-exact parity with MSVC's/fp:precisedefault.