Skip to content

bugfix(ai): Fix isSupplySourceAttacked SCAN_RATE using frames instead of seconds - #3380

Open
WebbontheWeb wants to merge 5 commits into
TheSuperHackers:mainfrom
WebbontheWeb:bugfix/ai-supply-attack-timing
Open

WebbontheWeb wants to merge 5 commits into
TheSuperHackers:mainfrom
WebbontheWeb:bugfix/ai-supply-attack-timing

Conversation

@WebbontheWeb

@WebbontheWeb WebbontheWeb commented Sep 27, 2026 •

Copy link
Copy Markdown

Updates AIPlayer::isSupplySourceAttacked() to check within the last 10 seconds, instead of the last 10 frames, by multiplying the SCAN_RATE by LOGICFRAMES_PER_SECOND.

This changes AI behavior, so it may break retail compatibility. It's removed with RETAIL_COMPATIBLE_CRC enabled.

Verified using a custom map where a tank attacks a supply truck. After the first hit, the tank is deleted to prevent any more attacks.

After 4 seconds, a script checks whether the supply source is under attack. If true, victory. If false, defeat.

With change:

withsupplyfix.mp4

Without change:

withoutsupplyfix.mp4

SupplyAttack2386.zip

Included replays ran successfully.

This is a small commit, but I'm trying to get the hang of working with this codebase and the testing process

@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 4612069c-d695-40f5-b134-ec1ba11e69e4

📥 Commits

Reviewing files that changed from the base of the PR and between 7d340d2 and 63f40b9.

📒 Files selected for processing (2)
  • Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


Walkthrough

Both game variants set the supply-attack scan interval to 10 frames in RETAIL_COMPATIBLE_CRC builds and to 10 seconds in other builds. The interval also controls the associated attack-recency and damage-timestamp checks.

Changes

Supply attack scan interval

Layer / File(s) Summary
Set the build-dependent scan interval
Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp, GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp
Both implementations use a 10-frame interval in RETAIL_COMPATIBLE_CRC builds and a 10-second interval otherwise. The selected interval applies to the surrounding attack-recency and damage-timestamp checks.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: caball009

Merge Risk: ⚪ Minimal · up to 63f40

Non-CRC builds now retain supply attacks for 10 seconds, while CRC-compatible builds preserve their prior behavior. No actionable merge-blocking risk remains; the change is ready for normal checks.

Architecture Summary

Architecture risk: 🔵 Low · up to 63f40

The change affects 2 systems.

Changed systems: Generals, GeneralsMD

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — Generals (service) was modified; 1 changed file maps to changed impact.
  • observed — GeneralsMD (service) was modified; 1 changed file maps to changed impact.

Before / after behavior

  • observed — Modified behavior in Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp: AIPlayer::isSupplySourceAttacked now uses a 10-frame scan interval under RETAIL_COMPATIBLE_CRC and a 10-second interval otherwise, replacing the previous unconditional 10-frame interval. The selected interval also governs the surrounding attack-recency and damage-timestamp checks.
  • observed — Modified behavior in GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp: isSupplySourceAttacked now uses a 10-frame scan interval when RETAIL_COMPATIBLE_CRC is enabled; otherwise it uses 10 logic-seconds. This replaces the previous unconditional 10-second interval.
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #2386 requires AIPlayer::isSupplySourceAttacked() to retain attacks for 10 seconds, or 300 logic frames, in Generals and Zero Hour. Both changed files use 10*LOGICFRAMES_PER_SECOND only when… Use 10*LOGICFRAMES_PER_SECOND for SCAN_RATE in the RETAIL_COMPATIBLE_CRC configuration, or explicitly update the linked issue to exclude that configuration.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the change to the attack-recency window, the retail compatibility condition, the related issue, and the reported test procedure.
Title check ✅ Passed The title clearly identifies the AI bug and the incorrect use of frames instead of seconds in isSupplySourceAttacked.
Out of Scope Changes check ✅ Passed The pull request changes only AIPlayer::isSupplySourceAttacked() in the Generals and Zero Hour source files. The build conditional relates to the stated retail-compatibility constraint. No unrelated…
Full details: Linked Issues check

Explanation

Issue #2386 requires AIPlayer::isSupplySourceAttacked() to retain attacks for 10 seconds, or 300 logic frames, in Generals and Zero Hour. Both changed files use 10*LOGICFRAMES_PER_SECOND only when RETAIL_COMPATIBLE_CRC is disabled. They still use 10 frames when the flag is enabled. Issue #2386 does not exclude that configuration. The issue states no separate automated-test requirement.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Fixes AI supply-source attack detection logic and damage timestamp handling.

The PR appears safe to merge; no outstanding or new actionable finding remains.

Summary

The PR extends supply-attack detection from ten frames to ten seconds in non-retail builds while retaining the retail scan window. It also makes never-recorded damage timestamps nullable and updates their callers in both game variants.

  • The two previously reported supply-attack findings are not outstanding: the refresh interval remains ten frames, and non-retail scans skip units with no recorded damage.

Reviews (11) · Last reviewed commit: "bugfix(ai): Update getLastDamageTimestam..."

Comment thread Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
@Caball009 Caball009 added Bug Something is not working right, typically is user facing Minor Severity: Minor < Major < Critical < Blocker Unit AI Is related to unit behavior Gen Relates to Generals ZH Relates to Zero Hour AI Is AI related and removed Unit AI Is related to unit behavior labels Sep 27, 2026
@WebbontheWeb
WebbontheWeb force-pushed the bugfix/ai-supply-attack-timing branch from 7d340d2 to 63f40b9 Compare September 28, 2026 05:45

@Skyaero42 Skyaero42 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.

This change is fine pending a few nits.

Comment thread Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
Comment thread Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
@WebbontheWeb
WebbontheWeb force-pushed the bugfix/ai-supply-attack-timing branch from 63f40b9 to 9558198 Compare September 28, 2026 14:05
xezon
xezon previously approved these changes Sep 28, 2026

@xezon xezon 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.

Comments can be restyled, otherwise makes sense.

Comment thread Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
Comment thread Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
@WebbontheWeb
WebbontheWeb force-pushed the bugfix/ai-supply-attack-timing branch from 9558198 to 3e53105 Compare September 28, 2026 18:39
Comment thread Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
@WebbontheWeb
WebbontheWeb force-pushed the bugfix/ai-supply-attack-timing branch from 3e53105 to c288acb Compare September 28, 2026 19:41
@WebbontheWeb
WebbontheWeb force-pushed the bugfix/ai-supply-attack-timing branch from c288acb to e1e9c0e Compare September 28, 2026 19:44
@Caball009

Copy link
Copy Markdown

Greptile still has an open review with a concern that looks valid.

@WebbontheWeb

WebbontheWeb commented Sep 28, 2026 •

Copy link
Copy Markdown
Author

Considering the original code had a comment indicating that 10 seconds was intended for SCAN_RATE, I don't believe that's an issue.

If we think it is, I could update to separate the "refresh rate" from SCAN_RATE.

m_supplySourceAttackCheckFrame could be incremented using the previous 10 frame value, while the rest could use the corrected SCAN_RATE.

@xezon

xezon commented Sep 29, 2026

Copy link
Copy Markdown

I sent an inquiry to kabuse about this.

@xezon

xezon commented Sep 29, 2026

Copy link
Copy Markdown

-TanSo-

yes, the condition works fine, however, exactly like presented in the PR, the 10 second delay makes the use of this script very inconsistent. The proposed solution would be a good implementation. I have set the refresh time to 2 seconds on my own build and run with it just fine

@WebbontheWeb

WebbontheWeb commented Sep 29, 2026 •

Copy link
Copy Markdown
Author

Thanks for the feedback, it's been updated. I split SCAN_RATE into REFRESH_RATE (how often isSupplySourceAttacked can be checked) and SCAN_WINDOW (how far back it's checking). I wasn't sure how to keep the EA comment, since it doesn't accurately describe either of the variables, so I moved it above with a disclaimer.

I also updated my map script to verify that the refresh is tracked separately from the scan. Before the tank attacks it now checks whether the supply truck has been attacked. It checks again 4 seconds after the attack.

The previous version doesn't register that it has been attacked (defeat):

previoussupplyfix.mp4

While the updated version does (victory):

newsupplyfix.mp4

Map:

SupplyAttack2386.zip

Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
@WebbontheWeb
WebbontheWeb force-pushed the bugfix/ai-supply-attack-timing branch from 6ec4e6a to 9f1a75a Compare September 29, 2026 22:30
if (body->getLastDamageTimestamp() + SCAN_RATE > curFrame) {
#if !RETAIL_COMPATIBLE_CRC
// Ignore undamaged units.
if (body->getLastDamageTimestamp() == 0xffffffff) {

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, because it compares against a magic value. Better add a new function to the interface that tells whether this has damage. Judging by other code, it looks like the common way to check for damage is to test the DamageInfo.in.m_sourceID, but needs verifying whether this is really reliable.

@WebbontheWeb WebbontheWeb Sep 30, 2026 •

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.

I don't think I could use DamageInfo.in.m_sourceId, since it can be invalid even after damage has been taken.

For example, ScriptActions::doNamedDamage sets damageInfo.in.m_sourceID = INVALID_ID;. It also looks like it can be set to healers, so I'd have to filter those out as well.

Is having the magic value within the interface function alright, with something like this in ActiveBody.h?
virtual Bool hasLastDamageTimestamp() const override { return m_lastDamageTimestamp != 0xffffffff; }

If not, I could also create a constant for the value within ActiveBody.h:
constexpr const UnsignedInt InvalidBodyTimestamp = ~0u;

Additionally, while investigating I noticed that StealthUpdate::allowedToStealth has basically the same check as the one I added. Would it be a good idea to update that as well?
if( self->getBodyModule()->getLastDamageTimestamp() != 0xffffffff )

Sorry for all the questions

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes the virtual Bool hasLastDamageTimestamp() makes sense and internally you can have named constant for it as well. You can update the other place(s) as well.

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.

Thanks! Should be updated

Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
@xezon
xezon dismissed their stale review September 30, 2026 17:07

Changes made.

Comment thread Generals/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp Outdated
Comment thread Generals/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h Outdated

virtual const DamageInfo *getLastDamageInfo() const override { return &m_lastDamageInfo; } ///< return info on last damage dealt to this object
virtual UnsignedInt getLastDamageTimestamp() const override { return m_lastDamageTimestamp; } ///< return frame of last damage dealt
virtual Bool hasLastDamageTimestamp() const override { return m_lastDamageTimestamp != InvalidBodyTimestamp; } ///< return whether a frame of last damage has been recorded

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Or is there any reliable marker inside DamageInfo that we can use to determine that there was a damage event?

	DamageInfoInput()
	{
		m_sourceID = INVALID_ID;
		m_sourceTemplate = nullptr;
		m_sourcePlayerMask = 0;
		m_damageType = DAMAGE_EXPLOSION;
		m_damageStatusType = OBJECT_STATUS_NONE;
		m_damageFXOverride = DAMAGE_UNRESISTABLE;
		m_deathType = DEATH_NORMAL;
		m_amount = 0;
		m_kill = FALSE;

For example, m_amount > 0 or m_damageStatusType != OBJECT_STATUS_NONE or m_sourcePlayerMask != 0 ?

We would just need to find one bit that is always reliably different.

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.

I did my best to find something, I also sent AI to see if it could find anything.

Healing seems to overwrite the entire DamageInfo record, making it unreliable. A unit that has only been healed can have an identical DamageInfo to one that was just attacked and then healed.

There doesn't really seem to be a great solution that's not the timestamp.

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

Labels

AI Is AI related Bug Something is not working right, typically is 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.

SCAN_RATE in AIPlayer::isSupplySourceAttacked() uses frames instead of seconds, causing AI to "forget" attacks after 0.33 seconds

4 participants