bugfix(savegame): Rebuild shroud grid on load so structures stay visible#3005
bugfix(savegame): Rebuild shroud grid on load so structures stay visible#3005coolswood wants to merge 1 commit into
Conversation
Loading a save left the partition/shroud state inconsistent. The per-cell shroud counters and the pending undo-queue were restored from the file, but nothing re-applied the object looks those counters represent. When the restored undo-queue drained (~m_unlookPersistDuration frames after load) removeLooker fired at every queued position. Static structures never re-look, so their cells dropped to FOGGED and the player's own base became gray, unselectable ghosts within a few seconds of loading a save. PartitionManager::loadPostProcess was an empty stub, so the grid was never rebuilt after load. Fill it by mirroring the established TerrainLogic::setActiveBoundary rebuild: drain the queue, snapshot the explored (FOGGED) and permanently-revealed (CLEAR) cells, release ghost partition data, invalidate every object's sighting state so the later unlook()/unshroud() are no-ops, tear the cell grid down to the all-shrouded default, then re-look every object so each owns exactly one live looker. Finally restore the explored cells and ghost objects and refresh the display/radar. After this the undo-queue is empty and static structures stay CLEAR permanently. Applied to Zero Hour first, then replicated for Generals.
|
| Filename | Overview |
|---|---|
| Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp | Fills in the empty loadPostProcess() stub by implementing the canonical shroud-grid rebuild sequence, closely mirroring TerrainLogic::setActiveBoundary. Logic and ordering match the reference implementation; no issues found. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp | Byte-identical replica of the Generals fix; same correct sequence. No issues found. |
Sequence Diagram
sequenceDiagram
participant L as loadPostProcess
participant PM as PartitionManager
participant GOM as GhostObjectManager
participant OBJ as GameObjects
L->>PM: processEntirePendingUndoShroudRevealQueue()
L->>PM: storeFoggedCells(store TRUE)
L->>GOM: releasePartitionData()
L->>OBJ: friend_prepareForMapBoundaryAdjust all objects
L->>PM: storeFoggedCells(store FALSE)
L->>PM: reset then init
L->>PM: restoreFoggedCells(store FALSE)
L->>GOM: lockGhostObjects TRUE
L->>OBJ: friend_notifyOfNewMapBoundary all objects
L->>PM: restoreFoggedCells(store TRUE)
L->>GOM: restorePartitionData
L->>GOM: lockGhostObjects FALSE
L->>PM: refreshShroudForLocalPlayer
Reviews (1): Last reviewed commit: "bugfix(savegame): Rebuild shroud grid on..." | Re-trigger Greptile
|
What does the issue look like? Could you share a video or screenshot? |
|
@Caball009 Here's a recording of the issue. One upfront note on the setup: this capture was taken with the Contra 009 mod installed, because it gave us a ready-made save with a developed base to reproduce against. I want to be transparent that it's a modded capture, but I'll explain below why the mod is not the cause here. Why the mod can't be responsible for this bug The fix targets PartitionManager::loadPostProcess(), which in the current tree is an empty stub: This is compiled engine code in GameLogic/Source/GameLogic/Object/PartitionManager.cpp. A .big mod archive can only ship data — INI files, textures, audio, maps. It physically cannot modify a compiled C++ function. So the empty stub executes byte-for-byte identically on vanilla retail, on our cross-platform port, and with any mod layered on top. A mod can neither introduce nor remove this behavior. What Contra does change are per-object VisionRange / ShroudClearingRange values in its INIs. Those only affect the radius each unit/structure reveals — i.e. how large the grayed-out region is — not whether the player's own base fogs over after a load. The fogging itself comes from the restored undo-queue draining (~m_unlookPersistDuration frames after load): removeLooker fires at positions no live object owns, static structures never re-look, and their cells drop to FOGGED. That's engine logic the mod never touches. What to watch in the video Load the save. For completeness, I checked main directly — the empty loadPostProcess() is present there too (it's not something our port or the mod introduced). The regression therefore reproduces on the retail Windows build the same way; the bug is platform- and mod-independent. proof_small.mp4 |
|
Thank you for your effort, but this is a duplicate of #2892. |
|
This interesting insofar that it is an attempt to tackle the issue in a generic way that sets up PartitionManager for success after saveload. Just looks a bit complicated. Maybe this can be done in simpler ways? |
Summary
After loading a save game, fog of war creeps over the player's own base within a few seconds: static structures become gray "previously seen" ghosts that can no longer be selected or controlled.
Root cause
PartitionManager::loadPostProcess()was an empty stub. After a save load, the per-cell shroud counters (m_currentShroud) and the pending undo-queue are restored from the file (bothPartitionManager::xferversion 2 andPartitionCell::xfer), but nothing re-applies the object looks those counters represent.When the restored undo-queue drains (~
m_unlookPersistDurationframes after load),removeLookerfires at every queued position. Static structures never re-look (nothing callsObject::look()/handleShroud()after load), so their cells drop toFOGGED. The restoredm_partitionLastLookand the cell counters also disagree, so any laterhandleShroud()would double-count.The same empty stub is present in both the Zero Hour and Generals trees.
Fix
Fill in
PartitionManager::loadPostProcess()by mirroring the establishedTerrainLogic::setActiveBoundaryrebuild (the canonical full-shroud-rebuild pattern, already used for map boundary resize):processEntirePendingUndoShroudRevealQueue).FOGGED) cells viastoreFoggedCells.friend_prepareForMapBoundaryAdjust) so the laterunlook()/unshroud()insidehandleShroud()are no-ops instead of double-undoes/double-counts.CLEAR) cells, then tear the cell grid down to the all-shrouded default (reset()+init()).friend_notifyOfNewMapBoundary→ each object owns exactly one live looker).refreshShroudForLocalPlayer().After this the undo-queue is empty, every live object owns its looker, and static structures stay
CLEARpermanently.All APIs used (
storeFoggedCells,restoreFoggedCells,ShroudStatusStoreRestore,processEntirePendingUndoShroudRevealQueue,Object::friend_prepareForMapBoundaryAdjust,Object::friend_notifyOfNewMapBoundary,GhostObjectManagermethods) already exist and are unchanged.Replication
Applied to Zero Hour first, then replicated identically for Generals, per the contribution precedence guideline. The Generals replica is byte-identical.
Verification
FOGGED) rather than black (SHROUDED), confirmingrestoreFoggedCellspreserves explored state.xfer,update, serialization, or the runtime look/unlook logic — only the previously-emptyloadPostProcessbody.