Skip to content

bugfix(save): Fix in-flight DumbProjectile detonating instantly on save game load#2591

Open
afc-afc0 wants to merge 2 commits intoTheSuperHackers:mainfrom
afc-afc0:bugfix/dumb-projectile-detonates-on-load
Open

bugfix(save): Fix in-flight DumbProjectile detonating instantly on save game load#2591
afc-afc0 wants to merge 2 commits intoTheSuperHackers:mainfrom
afc-afc0:bugfix/dumb-projectile-detonates-on-load

Conversation

@afc-afc0
Copy link
Copy Markdown

  • Fixes Tank/Artillery Shells instantly Detonates Mid-Air after Loading #2346

    DumbProjectileBehavior does not serialize m_flightPath (the Bezier curve points) or m_currentFlightPathStep (the current
    position along the path). On load, m_flightPath is empty and m_currentFlightPathStep is 0, so the first update() call
    evaluates 0 >= 0 as true and immediately calls detonate(). This is most visible with slow artillery like Inferno Cannons and
    Nuke Cannons.

    The fix adds two things:

    1. Serializes m_currentFlightPathStep in xfer() (version bumped from 1 to 2) so the projectile knows where it was along its
      flight path.
    2. Rebuilds the flight path Bezier curve in loadPostProcess() using the already-saved start, end, segments and speed parameters.

    Old saves (version 1) remain compatible — the step defaults to 0 and the path is rebuilt, so projectiles restart from the
    beginning of their arc rather than detonating instantly.

    Test

    • Built and launched Zero Hour
    • Started a skirmish and built artillery units
    • Fired artillery and immediately saved the game while shells were mid-flight
    • Loaded the save — projectiles continued their flight path and landed at their target without instant detonation
    • Verified loading old saves works correctly — projectiles restart from the beginning of their arc instead of detonating

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 12, 2026

Greptile Summary

This PR fixes a long-standing save/load bug in DumbProjectileBehavior where in-flight projectiles would immediately detonate on game load. The root cause was that m_flightPath (the Bezier curve data) was never serialized, so on load the path was empty and the first update() call evaluated 0 >= 0 and called detonate().

The fix serializes m_currentFlightPathStep in version 2 of xfer() and rebuilds the Bezier curve in loadPostProcess(). When RETAIL_COMPATIBLE_XFER_SAVE is enabled (the default), the version stays at 1 and only the path rebuild applies — projectiles restart from the beginning of their arc rather than detonating, which is the documented compatibility trade-off. Both Generals/ and GeneralsMD/ files are updated identically.

Confidence Score: 5/5

Safe to merge — the fix is correct, backward-compatible, and the only remaining finding is a minor defensive-programming suggestion.

All remaining findings are P2. The core logic is sound: m_flightPath is correctly rebuilt in loadPostProcess(), the versioned xfer() is backward-compatible with old saves, and the RETAIL_COMPATIBLE_XFER_SAVE guard follows the established codebase pattern. The unchecked calcFlightPath return value is an extremely unlikely edge case (off-map coordinates on load) whose worst-case outcome is no worse than the original bug.

No files require special attention.

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp Adds versioned serialization of m_currentFlightPathStep (version 2, guarded by RETAIL_COMPATIBLE_XFER_SAVE) and rebuilds the flight path in loadPostProcess(); return value of calcFlightPath is unchecked.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp Identical changes to the Zero Hour variant — same version bump, xferInt for step, and unchecked calcFlightPath call in loadPostProcess().

Sequence Diagram

sequenceDiagram
    participant Save as Save/Load System
    participant Xfer as DumbProjectileBehavior::xfer()
    participant LPP as loadPostProcess()
    participant Update as update()

    Note over Save,Update: Saving (version 2 / non-retail)
    Save->>Xfer: xfer(XFER_SAVE)
    Xfer-->>Save: writes m_flightPathSegments, m_flightPathSpeed, m_flightPathStart, m_flightPathEnd, m_currentFlightPathStep (v2 only)

    Note over Save,Update: Loading
    Save->>Xfer: xfer(XFER_LOAD)
    Xfer-->>Save: reads m_flightPathSegments, m_flightPathSpeed, m_flightPathStart, m_flightPathEnd, m_currentFlightPathStep (if version >= 2)

    Save->>LPP: loadPostProcess()
    alt m_flightPathSegments > 0
        LPP->>LPP: calcFlightPath(false) - rebuilds m_flightPath Bezier curve
    end

    Note over Save,Update: First update() after load
    Save->>Update: update()
    alt m_currentFlightPathStep >= m_flightPath.size()
        Update-->>Save: detonate() [old bug / off-map edge case]
    else
        Update-->>Save: move to m_flightPath[m_currentFlightPathStep], ++step
    end
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp
Line: 779-782

Comment:
**Unchecked return value from `calcFlightPath`**

`calcFlightPath(false)` can return `false` when the start/end positions are off-map (the internal call to `estimateTerrainExtremesAlongLine` returns `false`). If that happens here, `m_flightPath` is left empty and the next `update()` frame evaluates `0 >= 0`, triggering `detonate()` — exactly the original bug. Every other call site checks the return value: the fire path calls `TheGameLogic->destroyObject`, and the `update()` tracking path calls `detonate()`. A defensive check and `DEBUG_CRASH` would be consistent with the rest of the codebase.

```suggestion
	if( m_flightPathSegments > 0 )
	{
		if( !calcFlightPath( false ) )
		{
			DEBUG_CRASH(( "DumbProjectileBehavior::loadPostProcess - Failed to rebuild flight path; projectile will detonate immediately." ));
		}
	}
```

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

---

This is a comment left during a code review.
Path: GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp
Line: 830-833

Comment:
**Unchecked return value from `calcFlightPath`**

Same as `Generals/``calcFlightPath(false)` can return `false` for off-map coordinates, leaving `m_flightPath` empty and causing instant detonation on the next frame. A `DEBUG_CRASH` on failure would be consistent with the existing call sites in `update()`.

```suggestion
	if( m_flightPathSegments > 0 )
	{
		if( !calcFlightPath( false ) )
		{
			DEBUG_CRASH(( "DumbProjectileBehavior::loadPostProcess - Failed to rebuild flight path; projectile will detonate immediately." ));
		}
	}
```

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

Reviews (2): Last reviewed commit: "bugfix(save): Add RETAIL_COMPATIBLE_XFER..." | Re-trigger Greptile

Copy link
Copy Markdown

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

FWIW it's best to do the Generals changes last (after all feedback), unless there are meaningful changes in the Generals code. That's easier for the PR author and reviewers. This PR is small enough that it doesn't really matter, though.

@afc-afc0 afc-afc0 requested a review from Caball009 April 12, 2026 20:16
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.

Tank/Artillery Shells instantly Detonates Mid-Air after Loading

2 participants