Skip to content

bugfix: Modules now cease updating when disabled by non-whitelisted disabled types#2458

Open
Stubbjax wants to merge 1 commit intoTheSuperHackers:mainfrom
Stubbjax:fix-module-disable-conditions
Open

bugfix: Modules now cease updating when disabled by non-whitelisted disabled types#2458
Stubbjax wants to merge 1 commit intoTheSuperHackers:mainfrom
Stubbjax:fix-module-disable-conditions

Conversation

@Stubbjax
Copy link

Closes #147

This change fixes an issue where modules continue updating when disabled by a type that is not whitelisted.

For example, the stealth detector module has DISABLED_HELD whitelisted. This allows stealth detection to still run if an object is held, which in most cases is the result of containment (though CanDetectWhileContained typically blocks this). Any other disabling type is expected to shut the stealth detector down, such as DISABLED_EMP or DISABLED_SUBDUED. The problem is that the module only looks for whether the DISABLED_HELD bit is set and, if so, any other disabled type such as DISABLED_EMP is not considered.

This can also be seen elsewhere, such as with powered USA base defences. BaseRegenerateUpdate whitelists DISABLED_UNDERPOWERED. This means a Patriot Missile Battery will still repair itself when out of power. Conversely, if a Patriot Missile Battery is disabled by a Microwave Tank (DISABLED_SUBDUED), it will not repair itself. But if an underpowered Patriot Missile Battery is out of power and disabled by a Microwave Tank, then it will repair itself.

Before

The EMP Patriot will not repair while subdued, but will while the power is also out

REPAIR.mp4

After

A disabled (including unmanned) Overlord's Gattling Cannon no longer detects stealth units

DISABLE_DETECTION.mp4

@Stubbjax Stubbjax self-assigned this Mar 15, 2026
@Stubbjax Stubbjax added Bug Something is not working right, typically is user facing Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour NoRetail This fix or change is not applicable with Retail game compatibility labels Mar 15, 2026
@greptile-apps
Copy link

greptile-apps bot commented Mar 15, 2026

Greptile Summary

This PR fixes a logic bug in the module update-gating condition inside GameLogic::update() for both the base game (Generals/) and Zero Hour (GeneralsMD/) variants. The old condition (dis.anyIntersectionWith(whitelist)) would allow a module to keep running as long as at least one disabled flag was whitelisted, ignoring any additional non-whitelisted flags (e.g., DISABLED_EMP). The new condition (whitelist.testForAll(dis)) instead requires that every disabled flag on the object be covered by the whitelist — i.e., dis must be a strict subset of the module's whitelisted types — which correctly shuts the module down if any non-whitelisted disabling condition is also active. The change is gated behind #if RETAIL_COMPATIBLE_CRC to preserve CRC-compatible replays against the original game binary while fixing the behaviour in non-retail builds.

Key points:

  • The new testForAll semantics are correct: this->testForAll(that) returns true iff all bits of that are set in this, so whitelist.testForAll(dis) is true iff dis ⊆ whitelist.
  • The existing !dis.any() short-circuit ensures dis is non-empty before testForAll is called, satisfying the DEBUG_ASSERTCRASH inside that method.
  • The identical fix is applied to both the ALLOW_NONSLEEPY_UPDATES normal-update loop and the sleepy-update loop in both files.
  • Comment dates use 15/03/2026, which is within the current year (2026).

Confidence Score: 5/5

  • This PR is safe to merge — the change is minimal, correctly gated behind a compatibility flag, and logically sound.
  • The two-file change is small and self-contained. The new testForAll semantics (dis ⊆ whitelist) precisely implement the described intent, the DEBUG_ASSERTCRASH pre-condition is always satisfied by the existing !dis.any() guard, and the RETAIL_COMPATIBLE_CRC branch preserves original game compatibility. No custom rules are violated, comment dates are current, and the fix is applied consistently across both update loops and both game directories.
  • No files require special attention.

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Adds #if RETAIL_COMPATIBLE_CRC guards replacing anyIntersectionWith with testForAll for the disabled-types check; the new condition correctly makes the whitelist exclusive (dis must be a subset of whitelisted types), matching the described bugfix intent.
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Identical fix applied to the Zero Hour (GeneralsMD) variant of GameLogic.cpp; the same RETAIL_COMPATIBLE_CRC-guarded testForAll change is made in both the normal-update and sleepy-update loops.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[GameLogic::update - iterate update modules] --> B[dis = getDisabledFlags]
    B --> C{dis has any bits set?}
    C -- No disabled flags --> D[Run module update]
    C -- Has disabled flags --> E{RETAIL_COMPATIBLE_CRC enabled?}
    E -- Yes --> F[Old: dis.anyIntersectionWith whitelist]
    E -- No --> G[New: whitelist.testForAll dis]
    F -- Any bit in common --> D
    F -- No overlap --> H[Skip update]
    G -- dis is subset of whitelist --> D
    G -- Any non-whitelisted bit found --> H
    D --> I[u->update called]
    H --> J[sleepLen = UPDATE_SLEEP_NONE]
Loading

Last reviewed commit: 5330787

@Stubbjax Stubbjax force-pushed the fix-module-disable-conditions branch from 43e4df1 to 5330787 Compare March 15, 2026 10:22
Copy link

@Skyaero42 Skyaero42 left a comment

Choose a reason for hiding this comment

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

Looks good to me.

Copy link

@Caball009 Caball009 left a comment

Choose a reason for hiding this comment

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

Before merging this I'd like to test if this code overlaps / conflicts with possible fixes for the gattling cannon barrels rotating despite insufficient energy.

@Caball009 Caball009 dismissed their stale review March 16, 2026 16:12

No conflict issue that I can see.

@xezon
Copy link

xezon commented Mar 17, 2026

Do we need to label this is Controversial?

// Previously, if the disabled mask had any bits in common with the disabled-types-to-process mask,
// the update would be processed. Now, if any *other* bits are set in the disabled mask, the update
// is no longer processed.
if (!dis.any() || u->getDisabledTypesToProcess().testForAll(dis))
Copy link

Choose a reason for hiding this comment

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

Is this certainly correct?

I would have expected:

const Bool isDisabled = dis.any() && dis.testForAny(u->getDisabledTypesToProcess());
if (!isDisabled)
...

Copy link
Author

Choose a reason for hiding this comment

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

The getDisabledTypesToProcess function returns the disabled statuses that do not block the module's update.

For example, BaseRegenerateUpdate::getDisabledTypesToProcess returns DISABLED_UNDERPOWERED. This means the module is allowed to continue performing its repair update even if the player has low power.

Similarly, PoisonedBehaviour::getDisabledTypesToProcess returns DISABLEDMASK_ALL because nothing should disable the poisoned update. This is highlighted by the accompanying comment "we should still poison disabled things".

The function serves as a whitelist of disabled types that do not affect the update of the respective module. Perhaps a better name would be getAllowedDisabledTypes or getIgnoredDisabledTypes.

Copy link

Choose a reason for hiding this comment

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

Ok got it. But why is this condition still correct then?

So what we need to test is: (dis & ~allowedDisabledTypes) == 0

Is u->getDisabledTypesToProcess().testForAll(dis) equivalent to that? It is so complicated to read. Can we write that easier?

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

Labels

Bug Something is not working right, typically is user facing Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker NoRetail This fix or change is not applicable with Retail game compatibility ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Group force fire attack is gated by lowest ID group object weapon

4 participants