Conversation
WalkthroughTunnel destruction handling now scans all players’ tunnel trackers. Non-owner trackers that still list the tunnel receive destruction notifications during selling, death, and deletion. ChangesTunnel tracker notifications
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix Suggested reviewers: Merge Risk: 🔵 Low · up to 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)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: 05462f18-5541-4952-bf16-ef691cac9291
📒 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 ) |
There was a problem hiding this comment.
🎯 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/RTSRepository: 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/SourceRepository: 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.
When a player surrenders in a team game,
GameLogic::onSelfDestructhands everything to a living ally throughPlayer::transferAssetsFromThat, which re-teams the Tunnel Networks together with the units inside them. Every player has its ownTunnelTracker, and in GeneralsTunnelContain::onCaptureis compiled out for retail compatibility (#3242), so the tunnels stay listed in the old owner's tracker whileTunnelContain::onDie,onDeleteandonSellingreport 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 danglingm_containedBy, and the nextObject::onDestroyon that unit, at game end inGameLogic::resetor when the unit's own death completes, dereferences the freed tunnel and crashes inOpenContain::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:
Todo: