Skip to content

feat: Implement math CRC calculation utility for logic mismatch detection purposes#2100

Open
stephanmeesters wants to merge 12 commits intoTheSuperHackers:mainfrom
stephanmeesters:math-crc
Open

feat: Implement math CRC calculation utility for logic mismatch detection purposes#2100
stephanmeesters wants to merge 12 commits intoTheSuperHackers:mainfrom
stephanmeesters:math-crc

Conversation

@stephanmeesters
Copy link

@stephanmeesters stephanmeesters commented Jan 13, 2026

This PR adds a diagnostic utility to compute a CRC of simulation-relevant math operations which may be useful for predicting and preventing mismatches in multiplayer games.

It's basically a cocktail of math functions that is tweaked to be able to differentiate compilers, known good/bad Linux config, processor architecture. In the case of a non-matching CRC then there would be a high chance of seeing a mismatch at some point.

This provides a similar function to calculating CRC's of a set of replays, with the benefit that it runs very quickly (sub 0.1 ms) and is more stable to changes in the codebase.

Testing results

System Compiler CRC value
windows x86 vc6 🟩 89B53879
linux x86 good¹ vc6 🟩 89B53879
linux x86 bad¹ vc6 🟥 DCB83820
windows arm vc6 🟨 2EB834D2
windows arm (strict mode)⁶ vc6 ⬛ 2BB83459
mac arm (parallels)⁴ vc6 🟨 2EB834D2
mac arm (rosetta)⁵ vc6 ⬜ TBD
windows x86 win32 🟦 B5B838E0
linux x86 good² win32 🟦 B5B838E0
linux x86 bad³ win32 ⬜ TBD
windows arm win32 🟪 D4B838D4
mac arm (parallels)⁴ win32 🟪 D4B838D4
mac arm (rosetta)⁵ win32 ⬜ TBD

¹ Good/bad is determined by replay CRC's see #1940
² Known good here based on extensive testing using replays from GeneralsOnline (not the same binary though)
³ I don't have a known bad Linux config for win32
⁴ Apple M1 Mac Mini, Parallels Desktop 26, Windows 11 ARM
⁵ Using Whisky or Crossover
⁶ Using ARM emulation setting "Very strict execution"

Implementation

A command line argument is added that prints this simulation math CRC: -printSimMathCrc.

It's out of scope but a possible use case for this is to check CRC's between a lobby host and someone who joins so see if their systems are compatible, and perhaps warn or block if the CRC's don't match.

Help with testing

If you have an ARM machine and would like to help test and fill in the TBD items it would be much appreciated.

Instructions

Download and install this version with the CRC print flag (must be logged into GitHub):
vc6: https://github.com/TheSuperHackers/GeneralsGameCode/actions/runs/20965225959/artifacts/5115389009
win32: https://github.com/TheSuperHackers/GeneralsGameCode/suites/54311017349/artifacts/5120052850

Run this command using PowerShell, first dir into the directory with the game files:

$out = Join-Path $env:TEMP ("stdout_{0}.log" -f ([guid]::NewGuid())); $p = Start-Process -FilePath "generalszh.exe" -ArgumentList "-printSimMathCrc" -RedirectStandardOutput $out -PassThru; $p.WaitForExit() | Out-Null; Get-Content $out; Remove-Item $out -ErrorAction SilentlyContinue

If everything went well this should print the simulation CRC value.

@stephanmeesters stephanmeesters changed the title feat: Add simulation math CRC utility for mismatch prevention feat: Simulation math CRC utility for mismatch prevention Jan 13, 2026
@stephanmeesters stephanmeesters marked this pull request as ready for review January 13, 2026 23:52
@greptile-apps
Copy link

greptile-apps bot commented Jan 13, 2026

Greptile Summary

This PR introduces SimulationMathCrc, a lightweight diagnostic utility that computes a deterministic CRC by seeding a Matrix3D with a set of trig/log/exp results, multiplying two matrices together, inverting the result, and hashing it via XferCRC. The intent is to fingerprint the floating-point behaviour of a given compiler + platform + FPU configuration so that mismatch-prone setups can be identified before entering a multiplayer game.

Key observations:

  • The math pipeline (Sin, Cos, tanf, asinf, acosf, atanf, atan2f, sinhf, coshf, tanhf, sqrtf, expf, log10f, logf, powf) is a well-chosen cross-section of functions known to differ between implementations, making it a solid fingerprint signal.
  • Matrix3D::Multiply already handles the in-place aliasing case correctly.
  • Get_Inverse (static form) is safe for in-place inversion (matrix.Get_Inverse(matrix)).
  • One issue requires attention: the return value of Get_Inverse is discarded, so a near-singular matrix silently produces a wrong CRC without error indication.

Confidence Score: 4/5

  • Safe for the current startup-only diagnostic use case, but has a silent failure path if matrix inversion fails that should be handled.
  • The PR implements a solid diagnostic fingerprinting utility with a well-chosen math pipeline. The core logic is sound and matrix aliasing is handled correctly. However, the unchecked return value from Get_Inverse() means a singular matrix (however unlikely with fixed constants) would silently produce an incorrect CRC. For a diagnostic utility meant to produce reliable results, this correctness issue warrants attention. The startup-only context limits practical risk, but the code should defend against edge cases.
  • Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp — specifically line 54 where Get_Inverse() return value is discarded.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant SimulationMathCrc
    participant Matrix3D
    participant XferCRC

    Caller->>SimulationMathCrc: calculate()
    SimulationMathCrc->>XferCRC: open("SimulationMathCrc")
    SimulationMathCrc->>Matrix3D: matrix.Set(constants...)
    SimulationMathCrc->>Matrix3D: factorsMatrix.Set(trig/log/exp results...)
    SimulationMathCrc->>Matrix3D: Multiply(matrix, factorsMatrix, &matrix)
    SimulationMathCrc->>Matrix3D: matrix.Get_Inverse(matrix)
    Note over Matrix3D: Returns nullptr if singular<br/>— return value NOT checked!
    SimulationMathCrc->>XferCRC: xferMatrix3D(&matrix)
    SimulationMathCrc->>XferCRC: close()
    SimulationMathCrc-->>Caller: UnsignedInt CRC
Loading

Last reviewed commit: 572caec

@greptile-apps
Copy link

greptile-apps bot commented Jan 13, 2026

Greptile found no issues!

From now on, if a review finishes and we haven't found any issues, we will not post anything, but you can confirm that we reviewed your changes in the status check section.

This feature can be toggled off in your Code Review Settings by deselecting "Create a status check for each PR".

@Skyaero42
Copy link

While I think the concept is great, I don't think we should pollute the user-facing command-line options even more.
I think there are other ways to perform these tests rather than embedding it in the main application.

@stephanmeesters
Copy link
Author

I can agree with that, the command line was useful for testing but maybe not so user friendly, and sharing a CRC with users is not necessarily a goal as the meaning can be misunderstood (a matching CRC does not guarantee no mismatch).

We could hold off merging until there is some kind of plan to use it

@helmutbuhler
Copy link

I like this! I don't mind the commandline option, there are so many already anyway.
But we cannot expect users to run this manually, maybe we check this once the player enters the network lobby automatically? If the value doesn't match the common value, we can warn the user.

@bobtista
Copy link

I like it :) Agreed maybe we remove the command line option, and use this in the game lobby to warn people of potential compat issues. Also, looking forward, when we eventually go 64 bit + cross platform, this will presumably be removable, just something to plan for.

@Caball009
Copy link

I'm not sure whether I see the value of the command line option, but perhaps I could use the CRC value for #1404

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

@xezon
Copy link

xezon commented Jan 28, 2026

I'm not sure whether I see the value of the command line option, but perhaps I could use the CRC value for #1404

I agree. The idea for the CRC precheck is great. It would combine well for joining rooms and is another layer of safety to detect mismatch early.

@xezon xezon added Experimental Wear safety goggles in lab Major Severity: Minor < Major < Critical < Blocker Network Anything related to network, servers Stability Concerns stability of the runtime labels Jan 28, 2026
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@Caball009
Copy link

SimulationMathCrc::calculate returns 0x97B538BF for me with VS22 debug / release with debug info.

@stephanmeesters
Copy link
Author

SimulationMathCrc::calculate returns 0x97B538BF for me with VS22 debug / release with debug info.

That's odd, from where in the game did you run calculate? If it produces a different value right after the app starts vs somewhere later in the game that'd be pretty bad, I'll have a look.

@Caball009
Copy link

Caball009 commented Feb 2, 2026

I tried in GameEngine::GameEngine and also mid-game in GameLogic::update, and it's the same result.

Matrix values

FWIW these are my values of matrix at the end of appendMatrixCrc:

X	-8.38940525	float
Y	12.5274582	float
Z	-3.00327826	float
W	31.0372162	float

X	0.00523006357	float
Y	-0.0105796689	float
Z	0.00482341275	float
W	-0.0223645028	float

X	7.13817120	float
Y	-9.68629169	float
Z	2.22494340	float
W	-27.6725254	float

and here the values of factors_matrix:

X	0.233031467	float
Y	0.548851252	float
Z	0.309336275	float
W	1.31436896	float

X	0.256427377	float
Y	0.930222750	float
Z	0.298498958	float
W	0.201336011	float

X	0.499582082	float
Y	236.196625	float
Z	0.399771094	float
W	0.336472213	float

@Caball009
Copy link

Caball009 commented Feb 3, 2026

I was thinking we can extract more information from a bit flag than a CRC value. What if we create a bunch of math calculations and find the correct results once, and use that to do comparisons? That way we can signal exactly which math operation did not give the desired result.

Pseudo code, could probably be rewritten with a loop:

UnsignedInt comparison = (std::bit_cast<UnsignedInt>(WWMath::Sin(0.7f)) == std::bit_cast<UnsignedInt>(0.644217670f));
UnsignedInt flags = (comparison << 0);

comparison = (std::bit_cast<UnsignedInt>(log10f(2.3f)) == std::bit_cast<UnsignedInt>(0.361727834f));
flags |= (comparison << 1);

Even just a 32 bit integer would give use space for 32 separate comparisons which I think will be plenty do the input separately and the matrix values separately.

@stephanmeesters
Copy link
Author

stephanmeesters commented Feb 3, 2026

Knowing which operation made the change would be great.

There would be a possible loss in sensitivity, for example if we have our (win32+x86?) reference value A and two different systems produce B and C then we would not be able to tell those systems apart and see if they were actually compatible. For example if the CPU architecture is different AND somehow the math library used is different.

Would be interesting to see how many of these bits win32 <--> vc6 and x86 <--> arm would flip on it's own

If can you spare the bytes maybe use two integers, the CRC and the bitflag?

As written we would have 16 comparisons (15 factors individually, one with matrix mult + invert and then components summed or something)

@Caball009
Copy link

Caball009 commented Feb 3, 2026

for example if we have our (win32+x86?) reference value A and two different systems produce B and C then we would not be able to tell those systems apart and see if they were actually compatible.

I don't think I follow what you're saying. What additional information does the CRC provide that the bit flag doesn't? (assuming the number of comparisons does not exceed the number of bits of the CRC).

Perhaps we could anticipate that we want to expand this in the future beyond 31 bits and always check against a known CRC value, and flip a bit based on the result of the comparison. We could dedicate more than 32 bits to this, but I don't think it's needed.

@stephanmeesters
Copy link
Author

stephanmeesters commented Feb 4, 2026

I don't think I follow what you're saying. What additional information does the CRC provide that the bit flag doesn't? (assuming the number of comparisons does not exceed the number of bits of the CRC).

For example

On system A: log10f(2.3f) = 0.361727834f (<-- the hardcoded reference value)
On system B: log10f(2.3f) = 0.361727999f
On system C: log10f(2.3f) = 0.361727111f

This would produce CRCs like:

A = 0xAABBCC
B = 0xFABACA
C = 0xDADADA

And a bitflag like:

A = 1
B = 0
C = 0

Now we might (incorrectly) infer that B and C are compatible to play without mismatches because they have the same bitflag.

(unless I misunderstood something in your suggestion)

@Caball009
Copy link

I think I see what you mean. Where would we use the information that B and C aren't compatible with each other, though?

@stephanmeesters
Copy link
Author

I think I see what you mean. Where would we use the information that B and C aren't compatible with each other, though?

It would depend on the implementation but I think it would be nice if we compare the CRC of the lobby host with all participants, and for example use a color if a player does not have a matching CRC with the host.

So given that example with a lobby of A and B and C, if A is the host, then B and C would have a color, but we wouldn't directly compare B and C (unless one of them is the host).

@Caball009
Copy link

Yeah, that's fair. I'm not sure if we're going to use colors for this, but having the information available could be useful in some scenarios, and I think we can spare 4 bytes for it.

@xezon xezon changed the title feat: Simulation math CRC utility for mismatch prevention feat: Implement math CRC calculation utility for logic mismatch detection purposes Mar 2, 2026
Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

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

We can do it like this if you are happy with it.


setFPMode();

appendMatrixCrc(xfer);
Copy link

Choose a reason for hiding this comment

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

Name is perhaps a bit misleading, because it does more than just CRC from Matrix. You could split it into more distinct parts for Trigonometry, Exponential. Could also include Quaternion, Vector. I do not know if that is useful for any additional investigations later on.

Copy link
Author

@stephanmeesters stephanmeesters Mar 2, 2026

Choose a reason for hiding this comment

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

I've changed it appendSimulationMathCrc. It covers right now just generally the floating-point operations + standard library math functions and the Matrix was convenient for that and for doing the CRC. Perhaps different kinds of SIMD would also be interesting? (but not right now I guess). I think I'll keep it like this for now as all the testing was based on the current form and Caball's feature is only expected to use the single value, but happy to change later.

stephanmeesters and others added 2 commits March 2, 2026 21:37
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Include/Common/XferDeepCRC.h
Include/Common/XferLoad.h
Include/Common/XferSave.h
Include/Common/Diagnostic/SimulationMathCrc.h
Copy link

Choose a reason for hiding this comment

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

Better sort this alphabetically

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

Labels

Experimental Wear safety goggles in lab Major Severity: Minor < Major < Critical < Blocker Network Anything related to network, servers Stability Concerns stability of the runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants