Skip to content

fix(particlesys): Decouple particle systems from logic crc#2742

Open
xezon wants to merge 3 commits into
TheSuperHackers:mainfrom
xezon:xezon/fix-particlesystem-logic-crc
Open

fix(particlesys): Decouple particle systems from logic crc#2742
xezon wants to merge 3 commits into
TheSuperHackers:mainfrom
xezon:xezon/fix-particlesystem-logic-crc

Conversation

@xezon
Copy link
Copy Markdown

@xezon xezon commented May 22, 2026

This change decouples particle systems from logic crc and is an alternative to #2717.

The implementation adds a tiny runtime cost for virtual table lookups, but it provides an easy to use runtime switch for logic and client random values. Using it requires no refactors, template functions or static functions.

TODO

  • Replicate in Generals

@xezon xezon added Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Fix Is fixing something, but is not user facing labels May 22, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR decouples particle system positioning from the logic CRC by routing those random calls through a new RandomValueClass virtual interface (LogicRandomValueClass / ClientRandomValueClass) and switching particle effect sites to use client-side random. #if RETAIL_COMPATIBLE_CRC blocks preserve the original logic-RNG advancement for replay compatibility.

  • RandomValue.h/cpp introduce a tiny stateless virtual hierarchy and two helper macros (RandomValueInt, RandomValueReal) that replace the previous direct GameLogicRandomValue* macro calls.
  • GeometryInfo::makeRandomOffsetWithinFootprint gains a defaulted const RandomValueClass& parameter, keeping all existing callers unchanged.
  • Three particle-effect sites (TransitionDamageFX, EMPUpdate, SpecialAbilityUpdate) switch to ClientRandomValueClass and add CRC-preservation dummy calls; however all three dummy blocks are placed before createParticleSystem rather than inside the subsequent if (sys/pSystem) guard, which mismatches the original code's conditional RNG advancement.

Confidence Score: 4/5

The core abstraction is correct, but the CRC-preservation blocks in all three call sites run outside the particle-system null-check that guarded the original random calls, potentially diverging replay CRCs when particle creation fails.

The new RandomValueClass hierarchy and the Geometry.cpp port are clean. The issue is in all three RETAIL_COMPATIBLE_CRC sites: the dummy logic-random calls are unconditional, but the originals only fired when createParticleSystem succeeded. Any emitter-creation failure would silently advance the logic RNG differently from the original, breaking the exact CRC compatibility the blocks are meant to guarantee.

EMPUpdate.cpp, TransitionDamageFX.cpp, and SpecialAbilityUpdate.cpp — all three need their RETAIL_COMPATIBLE_CRC dummy blocks moved inside the if(sys/pSystem) guards.

Important Files Changed

Filename Overview
Core/GameEngine/Include/Common/RandomValue.h Introduces RandomValueClass hierarchy (base + Logic/Client impls) and helper macros; clean design with correct const-ref temporary binding for default parameters.
Core/GameEngine/Source/Common/RandomValue.cpp Implements the four virtual dispatch methods; each simply delegates to the appropriate global random function. Straightforward and correct.
GeneralsMD/Code/GameEngine/Include/Common/Geometry.h Adds RandomValue.h include and a const-ref default-parameter overload to makeRandomOffsetWithinFootprint; backward-compatible change.
GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp Replaces all GameLogicRandomValueReal calls with the polymorphic RandomValueReal macro; logic is unchanged for existing callers using the default LogicRandomValueClass.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp Switches particle position to ClientRandomValueClass and adds a RETAIL_COMPATIBLE_CRC dummy call; however the dummy call is placed before the if(pSystem) guard instead of inside it, mismatching the original execution order.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp Three CRC-preservation dummy calls placed unconditionally before createParticleSystem; originals were inside if(sys), so CRC can diverge when sys is null.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp Same RETAIL_COMPATIBLE_CRC scope placement issue as EMPUpdate.cpp and TransitionDamageFX.cpp; dummy call runs before the if(sys) null check.

Class Diagram

%%{init: {'theme': 'neutral'}}%%
classDiagram
    class RandomValueClass {
        <<abstract>>
        +GetRandomValueInt(lo, hi, file, line) Int
        +GetRandomValueReal(lo, hi, file, line) Real
    }
    class LogicRandomValueClass {
        +GetRandomValueInt(lo, hi, file, line) Int
        +GetRandomValueReal(lo, hi, file, line) Real
    }
    class ClientRandomValueClass {
        +GetRandomValueInt(lo, hi, file, line) Int
        +GetRandomValueReal(lo, hi, file, line) Real
    }
    RandomValueClass <|-- LogicRandomValueClass
    RandomValueClass <|-- ClientRandomValueClass

    class GeometryInfo {
        +makeRandomOffsetWithinFootprint(pt, random) void
    }
    GeometryInfo ..> RandomValueClass : uses

    note for LogicRandomValueClass "delegates to GetGameLogicRandomValue()"
    note for ClientRandomValueClass "delegates to GetGameClientRandomValue()"
Loading
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
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp:307-315
**CRC dummy calls placed outside the `if (sys)` null-check**

In the original code all three logic-random calls (`makeRandomOffsetWithinFootprint`, `GameLogicRandomValue(3, victimHeight)`, `GameLogicRandomValue(1, 100)`) were inside `if (sys)`. The new `RETAIL_COMPATIBLE_CRC` block runs them unconditionally, before `createParticleSystem` is even called. If `createParticleSystem` returns null the original code would not have advanced the logic RNG at all, but the CRC block advances it anyway, producing a divergent RNG state and breaking the replay CRC that the block is supposed to preserve.

The same structural mistake is present in `TransitionDamageFX.cpp` (the `getLocalEffectPos` dummy call before `if (pSystem)`) and `SpecialAbilityUpdate.cpp` (the `makeRandomOffsetWithinFootprint` dummy call before `if (sys)`). All three blocks should be moved inside their respective `if (sys/pSystem)` guards to exactly mirror the original code path.

Reviews (2): Last reviewed commit: "Fix macro arguments" | Re-trigger Greptile

Comment thread Core/GameEngine/Include/Common/RandomValue.h Outdated
Comment on lines +307 to +315
#if RETAIL_COMPATIBLE_CRC
// TheSuperHackers @fix The particle system is now decoupled from the logic crc
// and the legacy logic random values are preserved here.
{
Coord3D offs = {0,0,0};
curVictim->getGeometryInfo().makeRandomOffsetWithinFootprint( offs, LogicRandomValueClass() );
GameLogicRandomValue(3, victimHeight);
GameLogicRandomValue(1, 100);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 CRC dummy calls placed outside the if (sys) null-check

In the original code all three logic-random calls (makeRandomOffsetWithinFootprint, GameLogicRandomValue(3, victimHeight), GameLogicRandomValue(1, 100)) were inside if (sys). The new RETAIL_COMPATIBLE_CRC block runs them unconditionally, before createParticleSystem is even called. If createParticleSystem returns null the original code would not have advanced the logic RNG at all, but the CRC block advances it anyway, producing a divergent RNG state and breaking the replay CRC that the block is supposed to preserve.

The same structural mistake is present in TransitionDamageFX.cpp (the getLocalEffectPos dummy call before if (pSystem)) and SpecialAbilityUpdate.cpp (the makeRandomOffsetWithinFootprint dummy call before if (sys)). All three blocks should be moved inside their respective if (sys/pSystem) guards to exactly mirror the original code path.

Prompt To Fix With AI
This is a comment left during a code review.
Path: GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp
Line: 307-315

Comment:
**CRC dummy calls placed outside the `if (sys)` null-check**

In the original code all three logic-random calls (`makeRandomOffsetWithinFootprint`, `GameLogicRandomValue(3, victimHeight)`, `GameLogicRandomValue(1, 100)`) were inside `if (sys)`. The new `RETAIL_COMPATIBLE_CRC` block runs them unconditionally, before `createParticleSystem` is even called. If `createParticleSystem` returns null the original code would not have advanced the logic RNG at all, but the CRC block advances it anyway, producing a divergent RNG state and breaking the replay CRC that the block is supposed to preserve.

The same structural mistake is present in `TransitionDamageFX.cpp` (the `getLocalEffectPos` dummy call before `if (pSystem)`) and `SpecialAbilityUpdate.cpp` (the `makeRandomOffsetWithinFootprint` dummy call before `if (sys)`). All three blocks should be moved inside their respective `if (sys/pSystem)` guards to exactly mirror the original code path.

How can I resolve this? If you propose a fix, please make it concise.

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.

This is fine for as long as it is inside the if (tmp) branch.

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

Labels

Fix Is fixing something, but is not user facing Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Particle systems cause game logic / CRC changes

1 participant