Skip to content

bugfix(object): Preserve max-health upgrade order - #3285

Draft
tintinhamans wants to merge 1 commit into
TheSuperHackers:mainfrom
tintinhamans:arctic/fix/maxhealth-order
Draft

bugfix(object): Preserve max-health upgrade order#3285
tintinhamans wants to merge 1 commit into
TheSuperHackers:mainfrom
tintinhamans:arctic/fix/maxhealth-order

Conversation

@tintinhamans

@tintinhamans tintinhamans commented Sep 13, 2026

Copy link
Copy Markdown

Closes #3279


This fixes max health depending on upgrade order. Max-health upgrades now scale their added health using the unit's current veterancy bonus.

For a Paladin with 500 base health, Composite Armor (+100), and Elite veterancy (1.3×):

Before, applying veterancy first gave (500 × 1.3) + 100 = 750, and applying the armor first gave (500 + 100) × 1.3 = 780.

After the fix, both orders result in 780 health.

Signed-off-by: tintinhamans <5984296+tintinhamans@users.noreply.github.com>
@tintinhamans
tintinhamans force-pushed the arctic/fix/maxhealth-order branch from 8f2e1e3 to fa3cba6 Compare September 13, 2026 07:07
@tintinhamans
tintinhamans marked this pull request as ready for review September 13, 2026 07:11
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review Completed 2026-09-13T07:16:14.180041Z fa3cba6 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Preserve max-health upgrades across veterancy order

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Scale max-health additions by the unit’s current veterancy health bonus.
• Make upgrade and veterancy ordering produce identical maximum health.
• Preserve retail-compatible behavior in both Generals and Zero Hour.
Diagram

graph TD
  U["Max-health upgrade"] -->|"requests scale"| O["Game object"] -->|"reads"| V["Veterancy level"] -->|"indexes"| G["Health bonus"] -->|"scales addition"| S["Scaled health"] -->|"updates"| B["Body module"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Track unscaled health modifiers
  • ➕ Makes modifier composition intrinsically order-independent.
  • ➕ Provides a reusable model for future health modifiers.
  • ➖ Requires broader body-state and serialization changes.
  • ➖ Introduces substantially more regression risk across both engines.

Recommendation: Keep the PR’s targeted scaling approach. It aligns upgrade additions with the existing veterancy multiplier while minimizing compatibility risk; a componentized modifier model would be cleaner long-term but is disproportionate for this isolated ordering bug.

Files changed (6) +24 / -2

Bug fix (6) +24 / -2
Object.hExpose veterancy-aware health upgrade scaling +1/-0

Expose veterancy-aware health upgrade scaling

• Declares an Object method that returns the scale used when applying maximum-health upgrades.

Generals/Code/GameEngine/Include/GameLogic/Object.h

Object.cppResolve health scale from current veterancy +6/-0

Resolve health scale from current veterancy

• Implements the health upgrade scale lookup using the object’s current veterancy level and global health bonus table.

Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp

MaxHealthUpgrade.cppApply veterancy scaling to Generals health upgrades +5/-1

Apply veterancy scaling to Generals health upgrades

• Multiplies added maximum health by the object’s current veterancy bonus, removing upgrade-order differences. The original calculation remains under retail-compatible builds.

Generals/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp

Object.hExpose Zero Hour health upgrade scaling +1/-0

Expose Zero Hour health upgrade scaling

• Declares the veterancy-aware maximum-health upgrade scale on the Zero Hour Object interface.

GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h

Object.cppResolve Zero Hour scale from veterancy +6/-0

Resolve Zero Hour scale from veterancy

• Implements the scale lookup from Zero Hour’s current veterancy level and global health bonus table.

GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp

MaxHealthUpgrade.cppApply veterancy scaling to Zero Hour upgrades +5/-1

Apply veterancy scaling to Zero Hour upgrades

• Scales maximum-health additions by current veterancy so upgrade order no longer changes the result. Retail-compatible builds retain the original behavior.

GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp

@greptile-apps

greptile-apps Bot commented Sep 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a veterancy-aware multiplier to max-health upgrade deltas in both Generals and Zero Hour so upgrades and promotions are intended to commute.

  • Adds Object::getMaxHealthUpgradeScale() backed by the current veterancy health bonus.
  • Applies that multiplier when MaxHealthUpgrade increases maximum health.
  • Preserves the legacy calculation under RETAIL_COMPATIBLE_CRC, which currently leaves the fix inactive in default builds.

Confidence Score: 4/5

The PR is not safe to merge as the stated bug fix remains inactive in the repository’s default build configuration.

The scaling arithmetic is consistent with the existing ratio-based veterancy transitions, but the default compatibility branch still executes the old order-dependent calculation in both game variants.

Files Needing Attention: Generals/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp; GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Include/GameLogic/Object.h Declares the new accessor for the current veterancy health-bonus multiplier.
Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp Implements the accessor by indexing the initialized global health bonuses with the object’s veterancy.
Generals/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp Adds veterancy-scaled health upgrades, but the repository’s default compatibility setting compiles the unchanged calculation instead.
GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h Declares the equivalent accessor for Zero Hour objects.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp Implements the Zero Hour accessor using the current veterancy health bonus.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp Mirrors the scaled calculation and the default-build gating issue in Zero Hour.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Apply MaxHealthUpgrade] --> B{RETAIL_COMPATIBLE_CRC}
  B -->|Default: 1| C[Add raw AddMaxHealth]
  B -->|Explicitly disabled| D[Read current veterancy bonus]
  D --> E[Add AddMaxHealth × bonus]
  C --> F[Upgrade order remains significant]
  E --> G[Upgrade order is preserved]
Loading
Prompt To Fix All With AI
### Issue 1
Generals/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp:89-92
**Fix Disabled by Default**

`RETAIL_COMPATIBLE_CRC` defaults to `1`, so normal builds take this branch and keep adding the raw `AddMaxHealth` value. The new veterancy scaling runs only when that macro is explicitly disabled. As a result, applying veterancy before the armor upgrade still produces 750 health instead of 780 in default builds. The Zero Hour implementation has the same guard and behavior.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "bugfix(object): preserve max-health upgr..." | Re-trigger Greptile

Comment on lines +89 to +92
#if RETAIL_COMPATIBLE_CRC
body->setMaxHealth( body->getMaxHealth() + data->m_addMaxHealth, data->m_maxHealthChangeType );
#else
Real maxHealth = body->getMaxHealth() + data->m_addMaxHealth * obj->getMaxHealthUpgradeScale();

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 Fix Disabled by Default

RETAIL_COMPATIBLE_CRC defaults to 1, so normal builds take this branch and keep adding the raw AddMaxHealth value. The new veterancy scaling runs only when that macro is explicitly disabled. As a result, applying veterancy before the armor upgrade still produces 750 health instead of 780 in default builds. The Zero Hour implementation has the same guard and behavior.

Prompt To Fix With AI
This is a comment left during a code review.
Path: Generals/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp
Line: 89-92

Comment:
**Fix Disabled by Default**

`RETAIL_COMPATIBLE_CRC` defaults to `1`, so normal builds take this branch and keep adding the raw `AddMaxHealth` value. The new veterancy scaling runs only when that macro is explicitly disabled. As a result, applying veterancy before the armor upgrade still produces 750 health instead of 780 in default builds. The Zero Hour implementation has the same guard and behavior.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

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 by design, this fix is not compatible with non-retail builds.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Understood—thanks for clarifying. Since this fix is intentionally limited to non-retail builds, the RETAIL_COMPATIBLE_CRC guard is by design and my finding does not apply. I'll withdraw the comment.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Default builds retain health order bug 📎 Requirement gap ≡ Correctness
Description
upgradeImplementation() places the veterancy-scaled addition behind #if RETAIL_COMPATIBLE_CRC,
while the enabled branch continues to add m_addMaxHealth without scaling. Because that macro
defaults to 1, both games still produce different maximum health when veterancy precedes any
affected armor upgrade, including the three upgrades identified by the checklist.
Code

Generals/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp[R89-92]

+#if RETAIL_COMPATIBLE_CRC
		body->setMaxHealth( body->getMaxHealth() + data->m_addMaxHealth, data->m_maxHealthChangeType );
+#else
+		Real maxHealth = body->getMaxHealth() + data->m_addMaxHealth * obj->getMaxHealthUpgradeScale();
Evidence
Rules 1 and 2 require order-independent health for both games and the three named armor upgrades.
The changed implementations retain the unscaled retail-compatible path, the shared configuration
enables that path by default, and veterancy subsequently scales the entire current maximum health,
preserving the 750-versus-780 discrepancy when the events occur in opposite orders.

Ensure maximum health is independent of veterancy and upgrade order
Apply order-independent health calculation to identified armor upgrades
Generals/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp[89-94]
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp[89-94]
Core/GameEngine/Include/Common/GameDefines.h[99-104]
Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp[1129-1137]
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp[1486-1494]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The order-independent maximum-health calculation is disabled in default builds because `RETAIL_COMPATIBLE_CRC` defaults to enabled in `GameDefines.h`. Both game variants therefore retain the old order-dependent calculation for the identified armor upgrades.

## Fix Focus Areas
- Generals/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp[89-94]
- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp[89-94]

## Recommended Fix
Make the veterancy-scaled `m_addMaxHealth` calculation active in the supported default configuration in both game trees. Remove or revise the compatibility guard so equivalent upgrade and veterancy states always produce the same maximum health.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. New veterans receive too much health 🐞 Bug ≡ Correctness
Description
MaxHealthUpgrade::upgradeImplementation uses the tracker's new veterancy bonus even when
Object::onVeterancyLevelChanged runs upgrade modules before the body applies the old-to-new health
multiplier. If a player health upgrade is already completed when a unit is created with production
veterancy, or the module is triggered by promotion, the addition is multiplied at lines 92-93 and
again by ActiveBody::onVeterancyLevelChanged, so a +100 elite upgrade at 1.3× contributes 169
health instead of 130 in both games.
Code

Generals/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp[R92-93]

+		Real maxHealth = body->getMaxHealth() + data->m_addMaxHealth * obj->getMaxHealthUpgradeScale();
+		body->setMaxHealth( maxHealth, data->m_maxHealthChangeType );
Evidence
The experience tracker stores the new level before invoking the object callback. That callback
processes completed player upgrades and grants the automatic veterancy upgrade before calling the
body, while the changed implementation already scales any health addition using the stored new
level; the body then multiplies the complete maximum health by the same veterancy transition ratio.
Object construction explicitly assigns production veterancy before the normal creation-time upgrade
pass, making an already-completed player upgrade reach this ordering.

Generals/Code/GameEngine/Source/GameLogic/Object/ExperienceTracker.cpp[120-131]
Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp[398-403]
Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp[2206-2224]
Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp[2731-2734]
Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp[2835-2851]
Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp[1129-1137]
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp[3140-3156]
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp[1486-1494]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Max-health upgrades can execute after the experience tracker has entered its new veterancy level but before the body has applied that veterancy transition. The new scaling therefore applies the new bonus to the addition before the body multiplies the same addition again.

## Fix Focus Areas
- Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp[2835-2851]
- Generals/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp[89-94]
- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp[3140-3156]
- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp[89-94]

## Recommended Fix
Apply the body's old-to-new veterancy health transition before any upgrade-module pass can execute against the new veterancy level, then grant and process the corresponding upgrade. Mirror the ordering change in both games and cover a unit created with non-regular production veterancy while its player already owns a max-health upgrade.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: ⚖️ Balanced: This is a behavioral gameplay fix affecting health and veterancy calculations across two engine variants, warranting a careful single-pass review despite its localized logic.

Grey Divider

Tip of the day
💡 Did you know, you can start a comment with 'qodo' or '@qodo' to chat about any finding

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +92 to +93
Real maxHealth = body->getMaxHealth() + data->m_addMaxHealth * obj->getMaxHealthUpgradeScale();
body->setMaxHealth( maxHealth, data->m_maxHealthChangeType );

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. New veterans receive too much health 🐞 Bug ≡ Correctness

MaxHealthUpgrade::upgradeImplementation uses the tracker's new veterancy bonus even when
Object::onVeterancyLevelChanged runs upgrade modules before the body applies the old-to-new health
multiplier. If a player health upgrade is already completed when a unit is created with production
veterancy, or the module is triggered by promotion, the addition is multiplied at lines 92-93 and
again by ActiveBody::onVeterancyLevelChanged, so a +100 elite upgrade at 1.3× contributes 169
health instead of 130 in both games.
Agent Prompt
## Issue description
Max-health upgrades can execute after the experience tracker has entered its new veterancy level but before the body has applied that veterancy transition. The new scaling therefore applies the new bonus to the addition before the body multiplies the same addition again.

## Fix Focus Areas
- Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp[2835-2851]
- Generals/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp[89-94]
- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp[3140-3156]
- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp[89-94]

## Recommended Fix
Apply the body's old-to-new veterancy health transition before any upgrade-module pass can execute against the new veterancy level, then grant and process the corresponding upgrade. Mirror the ordering change in both games and cover a unit created with non-regular production veterancy while its player already owns a max-health upgrade.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@tintinhamans
tintinhamans marked this pull request as draft September 13, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Max health depends on veterancy and upgrade order

1 participant