Skip to content

feat(math): Route game logic math through WWMath with 3-mode deterministic support - #2670

Open
Okladnoj wants to merge 95 commits into
TheSuperHackers:mainfrom
Okladnoj:okji/feat/deterministic-math-v2
Open

feat(math): Route game logic math through WWMath with 3-mode deterministic support#2670
Okladnoj wants to merge 95 commits into
TheSuperHackers:mainfrom
Okladnoj:okji/feat/deterministic-math-v2

Conversation

@Okladnoj

@Okladnoj Okladnoj commented May 1, 2026

Copy link
Copy Markdown

Rework of #2602, incorporating review feedback:

  • GameMath via FetchContent (per @stephanmeesters, @OmniBlade recommendation)
  • Trig.cpp preserved, redirected to WWMath instead of deleted (per @xezon request for standalone change)
  • 3 math modes: VC6 (x87 inline asm), CRT (standard library), GameMath deterministic (per @Mauller recommendation)
  • USE_DETERMINISTIC_MATH unconditional for non-VC6 — missing gmath.h is now a compile error instead of silent fallback to x87/CRT
  • Clean history from main

CI: 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 contain MSG_LOGIC_CRC messages, 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.

System Compiler Math Library CRC Perf (10 000 iters)
Win32 x86 MSVC (modern) fdlibm (deterministic) 🟩 76B53840 ~6 ms
macOS ARM64 Apple Clang fdlibm (deterministic) 🟩 76B53840 ~11 ms
Win32 x86 MSVC (modern) system math (native) 🟦 E8B6385A ~3 ms
macOS ARM64 Apple Clang system math (native) 🟦 E8B6385A ~5 ms
Win32 x86 VC6 (legacy) x87 CRT (no fdlibm) 🟧 B7B83850 ~17 ms
Win32 x86 VC6 (legacy) system math (native) 🟥 8BB5B841 ~5 ms
  • 🟩 cross-platform deterministic parity achieved (Win32 modern = macOS ARM64)
  • 🟦 native system math match (Win32 modern = macOS ARM64)
  • 🟧 VC6 deterministic (x87 CRT, separate group — fdlibm not supported)
  • 🟥 VC6 native (x87 CRT, separate group)

Key fix: -ffp-contract=off in cmake/compilers.cmake — prevents Clang from emitting FMA instructions (fmadd) that skip intermediate rounding, breaking bit-exact parity with MSVC's /fp:precise default.

image

@greptile-apps

greptile-apps Bot commented May 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR routes game math through WWMath and adds deterministic GameMath support. The main changes are:

  • GameMath integration through CMake for non-VC6 builds.
  • WWMath wrappers for deterministic, native, and legacy math paths.
  • Trig.cpp forwarding preserved through WWMath.
  • A diagnostic math CRC benchmark for deterministic/native comparisons.
  • Broad game logic and rendering math call sites moved from raw CRT calls to WWMath.

Confidence Score: 4/5

This is close, but the math-mode gate should be fixed before merging.

  • The new default gate still turns off the GameMath path in normal non-VC6 builds.
  • The updated call sites can look deterministic while still using CRT math underneath.
  • The CRC helper itself now routes the previously raw math calls through WWMath.

Core/Libraries/Include/Lib/BaseDefines.h

Important Files Changed

Filename Overview
Core/Libraries/Include/Lib/BaseDefines.h Adds the shared math-mode defaults, but the default CRC setting still disables the GameMath path.
Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp Updates the diagnostic CRC path to use WWMath wrappers for the benchmarked math operations.
Core/Libraries/Source/WWVegas/WWMath/wwmath.h Provides the wrapper surface used by the changed math call sites.
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/Libraries/Include/Lib/BaseDefines.h:35-36
**Deterministic math disabled**

`RETAIL_COMPATIBLE_CRC` defaults to `1`, so this condition is true even when `gmath.h` is present. That undefines `USE_DETERMINISTIC_MATH`, and the WWMath wrappers compile their CRT branches instead of the GameMath branches. A default non-VC6 build can therefore run the old platform math path and still pass through the new WWMath call sites, so cross-platform simulation can diverge even though GameMath was fetched.

Reviews (11): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread Generals/Code/GameEngine/Source/Common/System/Trig.cpp Outdated
Comment thread Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp Outdated
Comment thread Core/Libraries/Source/WWVegas/WWMath/wwmath.h Outdated
Comment thread Core/Libraries/Source/WWVegas/WWMath/wwmath.h Outdated
@Okladnoj

Okladnoj commented May 1, 2026

Copy link
Copy Markdown
Author
image Here is what replay playback looks like at the moment.

I’m testing this on a separate branch:
https://github.com/Okladnoj/GeneralsGameCode/okji/test/deterministic-math-v2

I slightly adjusted the CI there so I can run Win32 and get access to the game resources.

@Okladnoj
Okladnoj force-pushed the okji/feat/deterministic-math-v2 branch from 854cc7b to 779f714 Compare May 1, 2026 00:49
@Skyaero42

Copy link
Copy Markdown

You did not review the changes you made with AI. It has issues that you should fix before asking it to be reviewed.

@Okladnoj Okladnoj left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviwed all changes

@xezon

xezon commented May 2, 2026

Copy link
Copy Markdown

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.

@Okladnoj
Okladnoj force-pushed the okji/feat/deterministic-math-v2 branch from 4b5675d to ddea128 Compare May 3, 2026 15:09
@Okladnoj

Okladnoj commented May 3, 2026

Copy link
Copy Markdown
Author

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.

@xezon Hey! I understand your point, but the reason I didn't fully consolidate trig and wwmath in this PR is exactly to avoid doing too many things at once.

As we saw in PR #2602, fully removing trig.h and replacing it with WWMath across the codebase touches over 120 files. Mixing a massive 120+ file architectural refactoring with a core feature addition (GameMath) made the previous PR extremely difficult to review and broke compilation for some standalone utilities, because trig.h is used outside of just game math.

That's exactly why I chose this "routing" approach for this PR. By keeping the trig.h interface intact and just routing its internal implementation to WWMath, we achieve the deterministic math goals with a much smaller and safer footprint.

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 trig.h across all 120+ files)?

Comment thread Core/Libraries/Source/WWVegas/WWMath/wwmath.h Outdated
Comment thread Core/Libraries/Source/WWVegas/WWMath/wwmath.h Outdated
Comment thread Core/Libraries/Source/WWVegas/WWMath/wwmath.h Outdated
static WWINLINE float ASinTrig(float x) { return asinf(x); }
#endif

// Origin wrappers: replace bare CRT math calls in GameLogic.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a fan of the "origin" terminology for these functions. What is this supposed to mean?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Origin" means the original EA code called bare CRT functions (sqrt, acos, sinf...). The suffix marks which exact CRT function was used originally: SqrtOrigin(double) = was sqrt(), SqrtfOrigin(float) = was sqrtf(). These are not just type variants — they are different math paths. Will rename to _ convention: Sqrt_Origin, Sqrtf_Origin.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not good naming. They should just be called "Sqrt", "Cos", "Sin", etc.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing gm math variants.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ceil(float) and Floor(float) are original EA code (line 157 in main). They are only used in rendering (visrasterizer.cpp) and Normalize_Angle. Not part of CRC game logic — no need to wrap.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep it simple and consolidate code. No math function duplicates.

Comment thread Core/Libraries/Include/Lib/BaseType.h Outdated
Real x, y, z;

Real length() const { return (Real)sqrt( x*x + y*y + z*z ); }
Real length() const { return (Real)Sqrt( x*x + y*y + z*z ); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now calling a Sqrt(double). Is this intentional? If yes, why?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, intentional. Sqrt(double) is a free function from trig.h → WWMath::SqrtOrigin(x). Original EA called bare sqrt(). Coord3D::length() is used in game logic and participates in CRC — must be deterministic.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And (Real)sqrt( x*x + y*y + z*z ); was calling double sqrt(double) ?

Comment thread cmake/gamemath.cmake Outdated
Comment thread Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp Outdated
Comment thread Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp Outdated
Comment thread Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp Outdated
Okladnoj added a commit to Okladnoj/GeneralsGameCode that referenced this pull request May 5, 2026


- 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
Okladnoj added a commit to Okladnoj/GeneralsGameCode that referenced this pull request May 5, 2026


- 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)
fbraz3 added a commit to fbraz3/GeneralsX that referenced this pull request May 7, 2026
* feat(deterministic-math): scaffold phase 4 routing

Port the first deterministic math batch derived from TheSuperHackers PR TheSuperHackers#2670 with incremental gating and attribution compliance.

- add non-MSVC anti-FMA compile flag (-ffp-contract=off)

- route trig and sqrt gateways through WWMath wrappers

- add gamemath.cmake integration scaffold with deterministic flag

- update project rule for upstream PR attribution comments

- update lessons learned and May dev diary

* fix(headless): stabilize replay simulation on macOS

- Override ParticleSystemManagerDummy::update() as no-op to prevent
  headless replay from executing the full particle update path, which
  caused EXC_BAD_ACCESS crash at ParticleSystemManager::update()+560

- Route SDL3GameEngine::createRadar() and createParticleSystemManager()
  to their Dummy counterparts when dummy=true (headless mode), matching
  upstream Win32GameEngine factory behavior

- Guard ParticleSystemManager::update() loop against stale null entries
  with early continue before sys->update() dispatch

- Skip smudge rendering path in headless via m_headless guard in
  ParticleSystemManager::update()

- Add null-file guards in RecorderClass::readNextFrame(),
  appendNextCommand(), and updatePlayback() for both Generals and ZH
  to prevent null dereference when playback file is closed mid-loop

* fix(replay-headless): harden texture creation flow

Guard D3DX8 and DX8 wrapper texture allocation paths when device or caps are unavailable in headless replay windows. Fail texture load tasks safely instead of dereferencing null state.

Also harden missing texture fallback handling and record session notes in May diary and lessons.

* fix(replay-recording): handle mixed path separators correctly when serializing map name

The loop condition checking for path separators was incomplete on Linux/macOS paths:
- realMapPathToPortableMapPath() converts platform paths to portable format
- Portable paths may contain forward slashes (Linux/macOS standard)
- Loop condition find(backslash) never matched forward-slash-only paths
- This left newMapName EMPTY when writing replay header
- Result: replays stored with corrupted map name field

Fix: Check !isEmpty() AND (find(backslash) OR find(forward slash))
- Loop correctly terminates when last token (filename) is reached
- Works with both Windows (backslash) and Unix (forward slash) separators
- Applies to both GameInfoToAsciiString() and GameInfo::setMap()

Test results:
- macos_skirmish_1v1.rep: PASS
- macos_6p_custom_map_2.rep: PASS (CRC fallback resolves map)
- macos_1v1_custom_map_1.rep: CRC mismatch (expected, data incompatible)

* fix(replay-mapcache): normalize map cache path and replay map field

Fix cross-platform replay/map issues found on macOS:\n- write/read MapCache.ini using portable path join (no literal \ filename)\n- keep replay header path handling for absolute and directory-based -replay inputs\n- add explicit replay CRC mismatch diagnostics for headless runs\n- encode/decode replay map field to preserve special characters in map names\n\nValidation:\n- macOS z_generals build completed successfully\n- replay tests: official/custom map cases load natively; incompatible replay reports frame-0 CRC mismatch

* fix(particle-emitter): null-safe strdup in copy constructor

ParticleEmitterClass copy constructor called ::_strdup() on NameString
and UserString without null checks, causing SIGSEGV when either field
was null.

Crash observed at:
  ParticleEmitterClass::Clone() -> copy ctor -> ::_strdup(nullptr)
  -> strlen(nullptr) -> SIGSEGV (KERN_INVALID_ADDRESS at 0x0)

Triggered by W3DGhostObject::snapShot() during normal gameplay.

Fix: guard strdup calls with null check before dereferencing.
Applied to both GeneralsMD and Generals variants.

* docs(replay): add headless testing reference and tech debt notes

- HEADLESS_REPLAY_TESTING.md: commands, parameters, output interpretation,
  platform notes, debug tips (GDB/lldb) for macOS and Linux
- REPLAY_MAPCACHE_TECH_DEBT.md: tracked known issues for custom map CRC
  fallback and (resolved) MapCache.ini backslash filename bug
@Okladnoj

Okladnoj commented May 8, 2026

Copy link
Copy Markdown
Author

Hi @xezon! I have addressed all your review feedback points and updated the PR.

CI Status:
The CI is completely green. I ran the benchmarks on both Win32 and VC6 with the latest changes, and the CRC results perfectly match our previous deterministic baselines (76B53840 for deterministic, E8B6385A for native).

To save you from hunting through all the comment threads, here is a consolidated list of the answers and solutions to your review points:

  • Function_Name convention / Naming inconsistencies
    Fixed. Renamed all math wrappers to use the _Origin and _Trig convention. The _Trig suffix also cleanly resolves conflicts with legacy EA names (e.g., ACos_Trig vs Acos).
  • Move #define next to #include gmath
    Fixed.
  • Redundant VC6 guard
    Fixed — removed the outer #if !(defined(_MSC_VER)...) guard, kept only __has_include. VC6 doesn't support __has_include, so the block is naturally skipped.
  • "origin" terminology
    "Origin" means the original EA code called bare CRT functions (sqrt, acos, sinf...). The suffix explicitly marks which exact CRT function was used originally. These are not just type variants — they are different precision math paths.
  • Missing gm math variants / CeilfOrigin identical to Ceil
    Ceil(float) and Floor(float) are original EA code used only in rendering (visrasterizer.cpp). Determinism isn't needed there. However, CeilfOrigin(float) is a game logic wrapper that routes to gm_ceilf. Therefore, they are not identical.
  • C++ overloads instead of f suffix
    Overloads are dangerous here. GameMath only provides float functions (the double version always narrows). With overloads, the compiler silently picks the version by argument type and could inadvertently change the precision path. Explicit names protect against this.
  • No @fix prefix in CMake / What is ODR? / Line breaks / iters typo
    Fixed.
  • Benchmark in GameLogic::update()
    Moved the auto-benchmark out of the replay loop. It is now a simple compile-time flag. (Did not prepare an ImGui stub since ImGui does not exist in the project).
  • VS6 exclusion necessary in CMake?
    Yes, it is necessary. VC6 doesn't support <stdint.h> and long long required by GameMath. Removing the exclusion will break the build.
  • Sqrt(double) intentional in BaseType.h?
    Yes, intentional. Coord3D::length() is used in game logic and participates in CRC — it must be strictly deterministic.

Okladnoj added a commit to OKJID/GameClient that referenced this pull request May 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep it simple and consolidate code. No math function duplicates.

static WWINLINE float ASinTrig(float x) { return asinf(x); }
#endif

// Origin wrappers: replace bare CRT math calls in GameLogic.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not good naming. They should just be called "Sqrt", "Cos", "Sin", etc.

static WWINLINE double PowOrigin(double x, double y) { return pow(x, y); }
static WWINLINE float PowfOrigin(float x, float y) { return powf(x, y); }
static WWINLINE double CeilOrigin(double x) { return ceil(x); }
static WWINLINE float CeilfOrigin(float x) { return ceilf(x); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Then remove these duplicates and simply call ceil or std::ceil & Co at the non logical critical call sites. This way these extra functions can be removed here.

static WWINLINE float Atan2fOrigin(float y, float x) { return atan2f(y, x); }
static WWINLINE double AtanOrigin(double x) { return atan(x); }
static WWINLINE float AtanfOrigin(float x) { return atanf(x); }
static WWINLINE double ACosOrigin(double x) { return acos(x); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then use Atan, Asin, Acos, like the original. No inconsistent casing styles.

image

static WWINLINE double AtanOrigin(double x) { return (double)gm_atanf((float)x); }
static WWINLINE float AtanfOrigin(float x) { return gm_atanf(x); }
static WWINLINE double ACosOrigin(double x) { return (double)gm_acosf((float)x); }
static WWINLINE float ACosfOrigin(float x) { return gm_acosf(x); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason f suffix math functions exist is for C. C does not support function overloading.

I do not agree with your arguments for dangerous overloads. Overloading is very common in C++ and is desired to call the right function for the right type. Programmer does not need to remember to call f version for floats.

auto f1 = getValue();
auto f2 = acos(f1); // function overload picks the right version for the supported float type

Comment thread Core/Libraries/Include/Lib/BaseType.h Outdated
Real x, y, z;

Real length() const { return (Real)sqrt( x*x + y*y + z*z ); }
Real length() const { return (Real)Sqrt( x*x + y*y + z*z ); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And (Real)sqrt( x*x + y*y + z*z ); was calling double sqrt(double) ?

Real Sin(Real x)
{
return sinf(x);
return WWMath::Sin_Trig(x);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the point of moving the function body to WWMath, when it is just meant to be called through this trig file? Better keep it simple and just do it in here. No trampoline to WWMath.

@Okladnoj

Copy link
Copy Markdown
Author

Hi @xezon! Thanks for the detailed review. I agree with some of your points regarding code cleanliness (I will remove the Ceil/Floor wrappers for the renderer).

However, there are a couple of critical architectural points concerning the preservation of old replays (suffixes) and determinism (Trig.cpp) that I want to clarify before pushing changes.

1. C++ Overloads vs Explicit types (why suffixes are needed)

I want to explain why I had to come to an explicit separation of functions via suffixes instead of using C++ overloads. This is tied to the necessity of preserving 100% backwards compatibility for old builds (VC6 Retail Compatibility).

I introduced 3 types of functions because they reflect 3 completely different mathematical paths (math paths) in the original EA engine. Our codebase serves three build modes at once (VC6, Win32, and Deterministic), and if we don't strictly fix the paths, we will lose Retail compatibility on old compilers:

  1. Without suffix (WWMath::Cos): This is the original Westwood Math implementation. In the original game on VC6/Win32, it compiles into inline x87 asm (fcos).
  2. With _Trig suffix (WWMath::Cos_Trig): This is a replacement for the global Cos() function from Trig.cpp. In the original game on VC6, it called the CRT function cosf() (not fcos!). The difference in the lowest bits between fcos and cosf() is critical: if I merge them into a single function without a suffix, the retail build will start calling fcos instead of cosf(), and the original logic will break.
  3. With _Origin and f_Origin suffixes (ACos_Origin vs ACosf_Origin): These replace direct system calls to acos(double) and acosf(float) in GameLogic. The deterministic library GameMath provides only float versions. My double version is forced to do a narrowing cast: (double)gm_acosf((float)x).
    The original EA code often passed variables of type float into system functions expecting double (e.g., acos()), relying on automatic type promotion by the compiler.
    If I switch to C++ overloads (just ACos), then when passing a float, the compiler will automatically pick the float overload. This will change the original math path (instead of calling the double version with narrowing, it will call the pure float version).

Explicit suffixes strictly lock the original execution path. They guarantee that the exact function intended in the original game is called, avoiding unpredictable compiler behavior during overload resolution.

Examples (The mechanics of overload conflicts)

Here is, with examples, how the overload mechanism breaks the original branches when compiling under VC6:

Example A: Conflicting identical signatures (_Trig)
In the original game, we had two different math paths that took the exact same type (float), but executed different instructions:

  1. The original WWMath::Cos(float) → compiled into fcos (inline asm).
  2. The original Trig::Cos(float) → compiled into cosf() (CRT).

C++ overloads only work with different argument types. How is the compiler supposed to know which of the two Cos(1.0f) calls should go to assembler, and which should go to the system CRT, if their signatures are absolutely identical? It can't.
If we remove _Trig and leave only WWMath::Cos(float), then in the VC6 build, all code from the former Trig.cpp will start invoking the fcos assembler instead of the original cosf(). The math is broken.

Example B: Path substitution via typing (_Origin)
On the calling code side in GameLogic, EA often wrote like this:

float myVal = 0.5f;
float result = acos(myVal); // In the original, this is a call to <math.h> double acos(double)

Since acos in C accepted a double, the compiler did an implicit cast: float -> double -> acos(double) -> float.

What happens if we introduce the overloads WWMath::ACos(float) and WWMath::ACos(double)?
The call to WWMath::ACos(myVal) will see the float type. The C++ overload mechanism will directly call the float overload, completely ignoring the original path with promotion to double. The VC6 logic is broken! The explicit suffix ACos_Origin(double) takes away the compiler's right to choose and strictly forces the original math path.

2. Sqrt(double) in BaseType.h:391

And (Real)sqrt( x*x + y*y + z*z ); was calling double sqrt(double) ?

Yes, in the original game it fell back to the system CRT double sqrt(double). But the problem is that Coord3D::length() is actively used in game logic (it participates in physics and CRC calculations). If I leave the system double, we will have discrepancies between Mac, Win32, and VC6. I have to forcibly cast it to deterministic float (at the cost of precision loss) to guarantee cross-platform sync.

3. "Trampolines" in Trig.cpp

What is the point of moving the function body to WWMath... No trampoline to WWMath.

The fact is that I was acting exactly according to your original task from the previous PR (#2602).
You wrote then: "Generally it is a bad sign if simplifying code would break something. If so, it needs to be fixed", and asked me to physically delete the old Trig.cpp files, migrating everything to WWMath.

I did exactly that. But stephanmeesters discovered that completely deleting trig.h breaks the VC6 / Win32 compilation (over 120 files are affected due to implicit includes).
To save the VC6 build, I had to restore the old Trig.h interface.

But I moved the implementation itself to wwmath.h to fulfill your requirement for math consolidation. If I write #if USE_DETERMINISTIC_MATH directly inside Trig.cpp, I will have to do it twice (since there are two Trig.cpp files in the engine — in Generals and GeneralsMD).
The trampoline is a transitional compromise that allowed us to not break the VC6 build and to gather the deterministic logic strictly in one place, as we planned. In the second phase, when there is already a working system with deterministic math in the main branch, we can start looking for the best way to delete trig.h and fully rely on wwmath.

4. Duplicates (Ceil / Floor)

Regarding Ceil and Floor — here I completely agree with you.
Since these functions (along with their original EA versions) are used exclusively in rendering (e.g., in visrasterizer.cpp) and do not participate in CRC calculations for network play, wrapping them in WWMath makes no sense.
I will completely remove these wrappers from wwmath.h and write direct calls to std::ceil / std::floor right at their call sites in the render code.

Comment thread cmake/gamemath.cmake
@@ -0,0 +1,16 @@
# FORCE is required to guarantee cross-platform bit-exact determinism.
# Intrinsics would use platform-specific SIMD, breaking CRC parity between architectures.
set(GM_ENABLE_INTRINSICS OFF CACHE BOOL "Disable intrinsics for cross-arch determinism" FORCE)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be needed, only intrinsics that match behaviour with the C functions are used and there are test cases that ensure this holds true.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we verify that?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GameMath ships with a test for this that compares the intrinsic and none intrinsic versions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GameMath has tests that check both code paths.

// GameMath only provides float-precision functions. All call sites pass float-width
// values, so the narrowing is lossless in practice.
#if USE_DETERMINISTIC_MATH
static WWINLINE double Sqrt_Origin(double x) { return (double)gm_sqrtf((float)x); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Game math provides double versions of all math functions unlike the original math lib you were using so this needs updating.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed that on my branch.

@xezon

xezon commented May 18, 2026

Copy link
Copy Markdown

Hi @xezon! Thanks for the detailed review. I agree with some of your points regarding code cleanliness (I will remove the Ceil/Floor wrappers for the renderer).

However, there are a couple of critical architectural points concerning the preservation of old replays (suffixes) and determinism (Trig.cpp) that I want to clarify before pushing changes.

It is a bit tough to fight through this much AI generated text. Please push the last state of the code and then I can take a look at it in Visual Studio and try to polish it up if it needs polishing. I expect this is faster than chatting about where to go with this. Generally, try to not trust the AI generated code too much. It generates code that is for machines, not humans.

@xezon xezon added Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Platform Work towards platform support, such as Linux, MacOS labels May 18, 2026
@Okladnoj

Copy link
Copy Markdown
Author

It is a bit tough to fight through this much AI generated text. Please push the last state of the code and then I can take a look at it in Visual Studio and try to polish it up if it needs polishing. I expect this is faster than chatting about where to go with this. Generally, try to not trust the AI generated code too much. It generates code that is for machines, not humans.

I wrote every point personally — I only asked AI to format it properly, fix spelling, and translate it into English, exactly like I’m asking now, because my English is not very strong.

I personally worked through every point of that long text, so it would be better to read it carefully and understand the reasoning behind it — there is nothing unnecessary there.

The main point is that suffixes like _Trig and _Origin are physically necessary for us, because overloading cannot handle this task properly.

In the original project, before deterministic math was introduced, there were places with mixed math inside the game logic that affects the CRC. When USE_DETERMINISTIC_MATH is disabled, we need to support the old CRC calculation system, which means we need simultaneous _Trig and _Origin implementations.

If we could simply remove USE_DETERMINISTIC_MATH from the project, there would not be such a large-scale transformation and interweaving of math functions. But in the old mode, we support not only Win32, but also VC6 with its own assembly functions.

@Okladnoj

Copy link
Copy Markdown
Author

Hi @xezon! Thanks for the detailed review. I agree with some of your points regarding code cleanliness (I will remove the Ceil/Floor wrappers for the renderer).

@xezon
In short, I don’t think it can be explained much shorter or simpler than in that message.

The project’s math was not always written with a clean and transparent architecture — or at least not all parts of it were. Maybe this was even done intentionally to make it harder to reverse-engineer the CRC logic.

At the moment, all workflows build successfully, and all replays also play successfully both with deterministic math enabled and disabled.

Above, I sent a screenshot of your job, plus one additional replay run that I configured specifically to verify Win32.

@xezon

xezon commented May 18, 2026

Copy link
Copy Markdown

Ok fair comments. I was under the impression I was chatting with AI generated text because of all the polished formatting. Can you push the latest state to the branch that you have now? I would like to take a look at it in Visual Studio next.

Btw, Replay Check is currently broken. We need to wait until after that is fixed.

@Okladnoj

Copy link
Copy Markdown
Author

Ok fair comments. I was under the impression I was chatting with AI generated text because of all the polished formatting. Can you push the latest state to the branch that you have now? I would like to take a look at it in Visual Studio next.

Btw, Replay Check is currently broken. We need to wait until after that is fixed.

The branch is already up to date — I haven't made any changes since the last push, I was waiting for your feedback. Feel free to take the current branch and work on it in VS. If you need my help — push your changes and I'll pick up from there.

Regarding the broken Replay Check — the CI runner has no way to obtain the game data. I solved this by extracting a minimal set of files from the Steam distribution (no textures, audio, or GUI — just enough for replay verification), uploaded them as a release to a private repository (Okladnoj/generals-gamedata), and connected it to the workflow via a PAT secret (GAMEDATA_PAT). The CI downloads the data using gh release download, verifies SHA256, and uses it for replay check. You can see the configuration on the test branch: okji/test/deterministic-math-v2 — file .github/workflows/check-replays.yml. Feel free to adopt this approach — or give me access to your organization, and I'll create a similar private repo with the data and wire it up to your CI.

@xezon

xezon commented May 23, 2026

Copy link
Copy Markdown

The branch is already up to date

The last push in from 08 May

@xezon

This comment was marked as resolved.

@Okladnoj

Copy link
Copy Markdown
Author

Hi @bobtista and @xezon,

I've implemented all the changes Bob suggested, but it turned out they weren't enough. Over the past week I've been playing and debugging 2v2v2v2 matches (2 players win<->mac + 6 hard bots), and during that process I found and fixed a fairly extensive list of determinism issues.

I've organized all of those fixes into these PRs in my repository:

Could you please take a look at them first? It should make the review easier.

I haven't merged these changes into #2670 yet. I'll wait for your feedback before doing that.

Comment out per-file NO_DEBUG_CRC in ObjectCreationList and
PhysicsUpdate so Windows DebugFrame dumps match the macOS build,
which defines DEBUG_CRC globally and always emits them. This removes
the instrumentation asymmetry that forced stripping Mac-only dump
blocks before every cross-platform DebugFrame comparison.

Also trim stale patterns from ArchiveCRCLogs.ps1, purge the CRCLogs
folder in one bulk delete instead of per-file globbing, and add a
-Clean switch to Rebuild.ps1 (cmake --clean-first).
@xezon

xezon commented Jul 16, 2026

Copy link
Copy Markdown

So many problems. I suggest to make individual pulls for similar types of fixes and we get them reviewed and merged one by one.

@Okladnoj

Copy link
Copy Markdown
Author

Guys, @bobtista @xezon @OmniBlade — huge thanks for stepping in and doing the review!)
While working through the fixes and thinking it over, some critical determinism issues surfaced.
Please take a look at the situation:
Okladnoj#4 (comment)

Okladnoj and others added 7 commits July 17, 2026 17:26
…d RETAIL_COMPATIBLE_CRC

The single-precision (Real) radius math in calcMinRadius/calcRadiusVec is now
compiled only when RETAIL_COMPATIBLE_CRC is off. Retail-compatible builds keep
the original double-precision math, preserving VC6/retail CRC behavior.

Addresses review feedback from @bobtista and @xezon on PR #4.
…overload at full precision

Div_FixNaN is a div-by-zero guard, not a NaN fix (x/0 -> Inf, only 0/0 -> NaN),
so rename it to Div_Safe across both engines.

The double overload no longer demotes the division to float; parity testing
(Win 32-bit x86 vs macOS ARM64) confirmed double division is bit-identical on
both platforms (SSE2), so the downcast was unnecessary.

Addresses review feedback from @bobtista on PR #4.
… gm_pow x87 divergence

WWMath::Pow(x, 2) routes squaring through gm_pow (fdlibm), which runs on x87
under _PC_24 on the 32-bit Windows build and diverges from macOS ARM64. Parity
testing confirmed Pow at double is not cross-platform bit-identical.

Add WWMath::Sqr(float): under USE_DETERMINISTIC_MATH it squares with a plain
multiply (deterministic and far cheaper); otherwise it keeps the original
Pow(x, 2.0) path. Replaces all Pow(expr, 2) call-sites in PartitionManager
(threat/shroud fill), POWTruckAIUpdate and BuildAssistant across both engines.
…-clean

feat: Clean deterministic math fixes for review (v2.2-clean)
….1-clean

feat(determinism): Cross-platform deterministic simulation math and lockstep desync fixes
Comment thread Core/Libraries/Include/Lib/BaseDefines.h
Comment thread Core/Libraries/Include/Lib/BaseDefines.h

@OmniBlade OmniBlade left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some reconsideration or at least explanation is needed for the combinatorial explosion of functions we seem to have, several of which look like they will call the same underlying functions in both code paths. I've only commented on the Acos and Asin functions, but the comments there apply to all the transcendental functions in my opinion.

Comment thread cmake/gamemath.cmake
@@ -0,0 +1,16 @@
# FORCE is required to guarantee cross-platform bit-exact determinism.
# Intrinsics would use platform-specific SIMD, breaking CRC parity between architectures.
set(GM_ENABLE_INTRINSICS OFF CACHE BOOL "Disable intrinsics for cross-arch determinism" FORCE)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GameMath has tests that check both code paths.

offset.normalize();
Real theta = atan2(-offset.y, offset.x);
theta -= (Real)M_PI/2;
theta -= (Real)WWMATH_HALF_PI;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this being refactored to use WWMath, but the calls to transcendental functions not being routed to the WWMath wrappers? If its not involved in deterministic math then the refactor IMO should do all or nothing with regards to using WWMath.

static WWINLINE float Fabs(float x);
static WWINLINE double Fabs(double x);
static WWINLINE float Fabsf(float x);
static WWINLINE float Fabsf_Legacy(float val);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the CRT fabsf really return a different value compared to the bit twiddling legacy function? A lot of the content of this refactor is renaming WWMath::Fabs to WWMath::Fabsf_Legacy from what I can see which could be avoided with 1. using overloaded functions and 2. not creating a legacy version if the results compared to the CRT and gm_math functions are the same anyhow.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If _Legacy variants can be safely removed without breaking VC6 replays then that is fine.

#endif
}

WWINLINE double WWMath::Sqr(float x)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a new function, I'm not entirely convinced there should be a specialised function for taking a float and returning its square as a double.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks odd indeed.

#endif
}

WWINLINE float WWMath::Sqrt_Legacy(float val)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just return WWMath::Sqrtf if retail CRC matching isn't needed to reduce code duplication.

The same goes for any of the other inline ASM math functions.

It would be interesting to test if the ASM is needed at all or if the CRT functions could be used and still get the same result as I'm sure the windows CRT falls back to the CPU instructions for functions where the CPU has dedicated support.

#if USE_DETERMINISTIC_MATH
return gm_sqrtf(x);
#else
return (float)Sqrt((double)x);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this not call sqrtf when not using GameMath?

#endif
}

WWINLINE float WWMath::Sqrt(int x)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a function to handle promotion to float, this should be handled at the call site IMO to clearly show the intention to perform a floating point operation on an int and allow proper warnings to flag if it wasn't.

return (1.0f - frac) * _FastAsinTable[idx0] + frac * _FastAsinTable[idx1];
}

WWINLINE float WWMath::Acos_Legacy(float val)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed when the current Acos float version will perform exactly the same operation as it is currently implemented. Either scrap this or call acosf in the none deterministic path for Acos?

#endif
}

WWINLINE float WWMath::Asin_Legacy(float val)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A recurring theme, again 4 functions when 2 or perhaps 3 would be sufficient. Really you just need the overloads for float and double here with the none deterministic path returning the output of the double functions properly cast to match VC6 when compiled with retail compatibility in mind and assume only the GameMath paths matter for none VC6. The only gotcha will be where VC6 called the actual float functions but I'm not sure they existed in that version of the CRT.

@bobtista

bobtista commented Jul 21, 2026

Copy link
Copy Markdown

I ran a bunch of tests to add as much conclusive "do this vs that" as I could - results:

Proven — should change

  • Remove Acos_Legacy and use Acos — 11 external calls.
  • Remove Asin_Legacy and use Asin — 1 external call.
  • Remove Atan_Legacy and use Atan — 2 external calls.
  • Remove Atan2_Legacy and use Atan2 — 25 external calls.

These pairs were compared branch-by-branch and have identical implementations in every current math mode.

  • Consolidate Sqrt_Legacy with the appropriate float Sqrt implementation.

    • sqrtf(x) and (float)sqrt((double)x) matched for all (2^{31}) nonnegative float encodings on VC6, modern MSVC x86 and MSVC x64.
    • x87 fsqrt also produced zero differences.
    • VC6 x86 implements sqrtf through promoted sqrt anyway.
  • Remove Sqrt(int) and make the integer-to-float conversion explicit at its call sites.

  • Either route all of W3DMouse’s related operations through WWMath or revert its constant-only conversion.

Proven — should not change

  • Keep Fabsf_Legacy.

    • All non-NaN encodings matched sign-bit clearing.
    • NaN behavior differs on x86: signaling NaNs can be quieted, raise FE_INVALID, or have their sign altered by promotion.
    • Do not redirect these calls to plain Fabs(float).
  • Keep Sinf_Legacy and Cosf_Legacy.

    • x87 fsin/fcos differed from the CRT for roughly 25% of a ten-million-input corpus.
    • Results were unchanged between _PC_24 and _PC_53.
  • Do not merge genuine float transcendental calls with promoted-double calls indiscriminately.

    • Independent ARM64 testing found ordinary bitwise differences for acos, asin, sin and cos.
  • Keep pow((double)x, 2.0) in the VC6 retail/native compatibility branch.

    • VC6 differed from double multiplication for 178,512,248 of 4,278,190,080 finite float inputs: approximately 4.17%.
    • Modern MSVC produced zero differences because the UCRT special-cases exponent 2.
  • Keep float multiplication in the deterministic Sqr(float) branch.

    • Exhaustive ARM64 testing showed float and double squaring differ for 4,277,665,792 finite float encodings.
    • Changing it to double multiplication would alter deterministic results.
  • Keep GM_ENABLE_INTRINSICS=OFF, at least on x86.

    • The pinned GameMath gm_llrintf and gm_llrint x86 intrinsic paths use 32-bit conversions despite returning long long.
    • Every tested |x| >= 2^31 collapsed to -2147483648, across all rounding modes.
    • lrint paths and x64 long-long paths tested correctly.

Still requiring integration validation

The standalone numerical questions are resolved. Remaining validation is integration-level:

  • Historical replay CRC compatibility.
  • Cross-platform replay CRC agreement.
  • Multiplayer lockstep synchronization.

Overall, the independent macOS and Windows results agree: the redundant inverse-trig and square-root wrappers can be consolidated, while the x87 sine/cosine paths, legacy absolute-value behavior, VC6 pow(x,2) behavior and GameMath x86 intrinsic safeguard must remain.

Tested by

  • macOS ARM64 using Clang:

    • Exhaustive IEEE-754 float sweeps where practical.
    • Ten-million-input deterministic differential tests for transcendental functions.
    • Source-level comparison of every preprocessor branch.
    • GameMath builds with intrinsics both enabled and disabled.
  • Windows using:

    • Visual C++ 6 Win32/x86.
    • Modern MSVC x86 and x64.
    • Both _PC_24 and _PC_53 x87 control words.
    • Genuine CRT calls forced with #pragma function(...) and volatile function pointers.
    • No /fp:fast.
    • The actual pinned GameMath sources compiled with intrinsics ON and OFF.

(These conclusions were independently tested on two machines against PR head 306e43d1955090fc15a96651f112af0387d64526 and pinned GameMath revision 59f7ccd494f7e7c916a784ac26ef266f9f09d78d.)

@fbraz3

fbraz3 commented Jul 24, 2026

Copy link
Copy Markdown

Just my 2 cents.

I achieved great results on GeneralsX cross-play (Linux x Mac) with a very similar approach to this one.

So I'm sharing some commits I made over there, hoping they might be useful for TSH as well:

@xezon

xezon commented Jul 25, 2026

Copy link
Copy Markdown

@Okladnoj Please work on last comments when you can.

@Okladnoj

Copy link
Copy Markdown
Author

Hi! @xezon Sounds good. I’ll continue on Monday.

I also ran my own individual tests. In most cases, the advice from @bobtista that overlaps with @OmniBlade’s recommendations was confirmed. However, I still have concerns about whether we might affect the RETAIL_COMPATIBLE_CRC (1) mode.

My brain has kind of overheated, so I decided to take a short break from deterministic math until Monday. I’ll probably revisit the changes with a fresh perspective—not all at once, but function by function. I’ll start with the legacy code.

@OmniBlade

Copy link
Copy Markdown

I ran a bunch of tests to add as much conclusive "do this vs that" as I could - results:

Proven — should not change

* Keep `pow((double)x, 2.0)` in the VC6 retail/native compatibility branch.
  
  * VC6 differed from double multiplication for 178,512,248 of 4,278,190,080 finite float inputs: approximately 4.17%.
  * Modern MSVC produced zero differences because the UCRT special-cases exponent 2.

* Keep float multiplication in the deterministic `Sqr(float)` branch.
  
  * Exhaustive ARM64 testing showed float and double squaring differ for 4,277,665,792 finite float encodings.
  * Changing it to double multiplication would alter deterministic results.

My point about the Sqr function is why it has been introduced at all as it doesn't exist in the original code base so there is no original function to retain the behaviour of? If anything multiplications should be left where they are in the code and pow should be replaced with a WWMath::Pow function that wraps either pow(f) or gm_pow(f).

@bobtista

Copy link
Copy Markdown

Member
Ok sounds good to me - so remove WWMath::Sqr and route the existing pow/powf calls through WWMath::Pow/Powf and leave x*x and sqr() alone.

Also needs a rebase.

@Okladnoj

Okladnoj commented Aug 3, 2026

Copy link
Copy Markdown
Author

Deterministic math cleanup — decisions and validation

Refactor of wwmath.h: removed the duplication where the code paths are numerically identical, kept it where they diverge. Every "kept" is backed by a measurement. Decisions and validation below.

Removed

  • Acos/Asin/Atan/Atan2_Legacy — the retail path was (float)crt((double)x), identical to X(float). There was no fast path.
  • Sqrt_Legacy — folded into Sqrt(float). sqrtf(x) == (float)sqrt((double)x) == x87 fsqrt across all 2^31 non-negative encodings (VC6, MSVC x86/x64), 0 mismatches.
  • Sqrt(int) — removed, explicit (float) at the call sites. The only int call sites are cell deltas in AIPathfind (≪ 2^24), so the result is unchanged.
  • Sqrt(float), non-deterministic branch — now calls sqrtf(x) directly, dropping the float→double→float round-trip.
  • W3DMouse — cursor angle routed through WWMath::Atan2 (cursor rendering, outside the sim CRC).

Kept

  • Fabsf_Legacy — on x86 NaN diverges from CRT fabsf (sNaN quieting, sign flip on promotion).
  • Sinf/Cosf_Legacy — x87 fsin/fcos differ from the CRT for ~25% of a 10M-input corpus.
  • Inv_Sqrt_Legacy — a speed optimization (code comment: "30% faster … from Intel's math library"). The Newton approximation is not equal to 1/sqrtf → changes the retail CRC. The deterministic branch already uses 1/gm_sqrtf, no ASM.
  • double Sqr(float) — the double return keeps Sqrt(Sqr(a)+Sqr(b)) accumulating in double; narrowing to float would change the VC6 CRC. Branch contents measured: the deterministic branch squares in float (differs from double squaring on 4,277,665,792 encodings), VC6 uses pow(x,2.0) (differs from double multiplication on 4.17%).
  • Sqrtf kept separate from Sqrt(float) — some call sites pass a double on purpose (double minDistSqr; // double, not real) and rely on Sqrtf narrowing to single. The overloaded Sqrt would pick Sqrt(double) → different result → retail CRC.
  • X(float) and Xf not mergedX(float) in the non-deterministic branch is (float)X((double)x), Xf is the CRT single-precision call (acosf). For acos/asin/sin/cos these differ bit-for-bit on ARM64, so the family stays at 3 functions, not 2.
  • GM_ENABLE_INTRINSICS=OFF on x86gm_llrint* on x86 intrinsics do a 32-bit conversion when returning long long: |x| ≥ 2^31 collapses to -2147483648.

Validation

  • Retail (vc6, vc6-releaselog): 10 retail replays (GeneralsZH 1.04) — 0 desyncs.
  • Deterministic (macOS ARM64 ↔ Windows): one replay, ~118,800 frames — 0 desyncs (per-frame CRC, mac↔win).

Review

The changes are split into two stacked PRs on the branch, so they're easier to check separately. Please review:

@xezon @OmniBlade @bobtista

@xezon

xezon commented Aug 3, 2026

Copy link
Copy Markdown

Please rebase on main. There are a bunch of conflicts now.

@Okladnoj

Okladnoj commented Aug 3, 2026

Copy link
Copy Markdown
Author

@xezon
After merging the latest upstream changes from main, the build broke. There were a couple of minor issues: a missing include and particle creation not working with retail mode disabled.

The fixes are in my latest branch. The build succeeds, and replay testing also passes successfully.

All ready to review!) :
Okladnoj#7
Okladnoj#6


RETAIL_COMPATIBLE_CRC=1
image

RETAIL_COMPATIBLE_CRC=0
USE_DETERMINISTIC_MATH=1
image

Okladnoj added a commit to OKJID/GameClient that referenced this pull request Aug 3, 2026
Port the WWMath cleanup landed on GeneralsGameCode (PR TheSuperHackers#2670 line) so the
DET builds match:

- Remove the redundant _Legacy wrappers (Acos/Asin/Atan/Atan2/Sqrt) and the
  Sqrt(int) overload from wwmath.h; route Fast_Acos/Fast_Asin through the
  non-Legacy siblings (Fabsf_Legacy guard kept). All collapse to the same
  gm_*f in deterministic math, so DET output is unchanged.
- Rename every call site to the non-Legacy names (Inv_Sqrt_Legacy /
  Fabsf/Sinf/Cosf_Legacy are intentionally kept and untouched).
- Preserve numeric behaviour at the double-argument sites: shattersystem uses
  Sqrt((float)(...)), camera uses Atan2(x, 2.0f), euler From_Matrix keeps
  sy/cy as float, AIPathfind casts the int cell-delta to float before Sqrt.
- Route the W3DMouse scroll-cursor angles through WWMath::Atan2.

Builds clean on macOS (both GeneralsVanilla and GeneralsOnlineZH link).
@Okladnoj

Okladnoj commented Aug 3, 2026

Copy link
Copy Markdown
Author

Here is the list of separate PRs that the deterministic workflow depends on:

#3055
#3056
#3057

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker Platform Work towards platform support, such as Linux, MacOS ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants