Skip to content

bugfix(tunnel): Fix crash when a transferred tunnel dies with occupants - #3308

Draft
bobtista wants to merge 1 commit into
TheSuperHackers:mainfrom
bobtista:bobtista/bugfix/tunnel-transfer-dangling-container
Draft

bobtista wants to merge 1 commit into
TheSuperHackers:mainfrom
bobtista:bobtista/bugfix/tunnel-transfer-dangling-container

Conversation

@bobtista

@bobtista bobtista commented Sep 17, 2026

Copy link
Copy Markdown

When a player surrenders in a team game, GameLogic::onSelfDestruct hands everything to a living ally through Player::transferAssetsFromThat, which re-teams the Tunnel Networks together with the units inside them. Every player has its own TunnelTracker, and in Generals TunnelContain::onCapture is compiled out for retail compatibility (#3242), so the tunnels stay listed in the old owner's tracker while TunnelContain::onDie, onDelete and onSelling report the death to the new owner's tracker. That tracker never registered them, so its count underflows (the underflow noted in #3242), and nothing re-points the occupants in the old tracker. Every unit still inside keeps a dangling m_containedBy, and the next Object::onDestroy on that unit, at game end in GameLogic::reset or when the unit's own death completes, dereferences the freed tunnel and crashes in OpenContain::removeFromContain. Debug builds hit it every time because freed pool memory is filled; release builds hit it when the memory was reused.

Now a dying, sold or deleted tunnel notifies the tracker of its controlling player as before, and additionally every other tracker that still lists the tunnel by ID. Without an owner change no other tracker lists it, so nothing changes in normal games and the retail sim before the transfer is untouched. After a transfer the old tracker drops the tunnel, re-points its occupants to another of its tunnels, or caves them in when it was the last one, so no occupant is left pointing at a destroyed tunnel. Zero Hour keeps TunnelContain::onCapture, which moves the tunnel between trackers at transfer time, so it does not need this.

Reproduction:

  1. Start a 2v1 skirmish as GLA with an AI ally.
  2. Build a Tunnel Network and move a vehicle into it.
  3. Press Esc and choose Surrender, so the assets go to the ally.
  4. Let the ally lose, so the transferred tunnel is destroyed.
  5. The game crashes at the end of the match, or earlier when the transferred unit's own death completes.

Todo:

  • Verified with an instrumented build on a game that reproduces the surrender transfer: the old owner's tracker handles the tunnel deaths, the occupant is caved in with the last tunnel, and the crash at game end is gone

@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Walkthrough

Tunnel destruction handling now scans all players’ tunnel trackers. Non-owner trackers that still list the tunnel receive destruction notifications during selling, death, and deletion.

Changes

Tunnel tracker notifications

Layer / File(s) Summary
Tracker scan helper
Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp
The new helper accesses all players, skips null and owner trackers, and notifies trackers that list the destroyed tunnel.
Destruction path wiring
Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp
Selling, death, and deletion handling now invoke the helper for tunnels that remain in other trackers.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: caball009

Merge Risk: 🔵 Low · up to e0138

Destroying a transferred tunnel can corrupt the new owner’s tunnel count in retail-compatible builds. Add the membership guard before merging.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the tunnel crash bug and the transfer-with-occupants scenario addressed by the changes.
Description check ✅ Passed The description directly explains the crash cause, affected tunnel trackers, implemented fix, reproduction steps, and verification result.

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

@coderabbitai
coderabbitai Bot requested a review from Caball009 September 17, 2026 09:22

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 05462f18-5541-4952-bf16-ef691cac9291

📥 Commits

Reviewing files that changed from the base of the PR and between 2632833 and e013859.

📒 Files selected for processing (1)
  • Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp

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

for( Int i = 0; i < ThePlayerList->getPlayerCount(); ++i )
{
TunnelTracker *tracker = ThePlayerList->getNthPlayer( i )->getTunnelSystem();
if( tracker == nullptr || tracker == ownerTracker )

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '40,85p;205,240p;355,410p;425,465p' Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp
sed -n '220,325p' Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp
sed -n '1390,1430p' Generals/Code/GameEngine/Source/Common/RTS/Team.cpp
rg -n -C 3 'onCapture\(|onTunnelCreated\(|setOwner|SetOwner|changeOwner|ChangeOwner|transfer.*owner|Transfer.*Owner' Generals/Code/GameEngine/Source/GameLogic Generals/Code/GameEngine/Source/Common/RTS

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 28344


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- all tunnel registration and transfer references ---'
rg -n -C 4 'onTunnelCreated\(|onTunnelDestroyed\(|handlePartitionCellMaintenance|setControllingPlayer\(' \
  Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp \
  Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp \
  Generals/Code/GameEngine/Source/Common/RTS/Team.cpp \
  Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp
printf '%s\n' '--- retail transfer implementation ---'
sed -n '820,865p' Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp
sed -n '1395,1425p' Generals/Code/GameEngine/Source/Common/RTS/Team.cpp
printf '%s\n' '--- tracker registration and destruction call sites ---'
rg -n 'onTunnelCreated\(|onTunnelDestroyed\(' Generals/Code/GameEngine/Source

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 23695


Guard the owner tracker by membership before destruction.

In RETAIL_COMPATIBLE_CRC, Team::setControllingPlayer uses handlePartitionCellMaintenance() instead of onCapture, and TunnelContain::onCapture is excluded. The new owner's TunnelTracker therefore can lack this tunnel ID. The direct calls in onSelling, onDie, and onDelete still call that tracker’s onTunnelDestroyed unconditionally. TunnelTracker::onTunnelDestroyed decrements m_tunnelCount even when removal finds no ID, which can corrupt the count.

Before each owner-tracker call, check that its tunnel list contains tunnelID. Continue notifying other trackers that contain the ID.

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.

1 participant