From f0d5a30a62715445dc11955eccf58a859291c06b Mon Sep 17 00:00:00 2001 From: torradmin Date: Sat, 5 Sep 2026 11:30:42 +0300 Subject: [PATCH 1/3] * Fixed: Legacy game StackOverflow exception when large amounts of fog were shrouded/unshrouded at once. (cherry picked from commit 069340db8b39e3075cc872fab3fee58ffefe142a) (cherry picked from commit 33b7e034837fe2ea4feed2f933e822d2a7b2de94) --- code/display.cpp | 51 +++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/code/display.cpp b/code/display.cpp index cc1fc98c5..46380c00a 100644 --- a/code/display.cpp +++ b/code/display.cpp @@ -2986,41 +2986,44 @@ void DisplayClass::Encroach_Fog(void) /// The cell that the fog is to be regrown upon. void DisplayClass::Fog_Cell(Cell const & cell) { - if (!In_Radar(cell)) return; + // An explicit queue avoids running the stack out on a cascade that spans most of the map. + std::vector pending; + pending.push_back(cell); - CellClass * cellptr = &(*this)[cell]; - bool fog = false; + while (!pending.empty()) { + Cell current = pending.back(); + pending.pop_back(); - if (cellptr->IsFogMapped || cellptr->IsFogVisible) { - fog = true; - } + if (!In_Radar(current)) continue; - cellptr->IsFogMapped = false; - cellptr->IsFogVisible = false; - cellptr->FogFrame = -2; + CellClass * cellptr = &(*this)[current]; + bool fog = cellptr->IsFogMapped || cellptr->IsFogVisible; - if (cellptr->IsMapped) { - TacticalMap->Flag_Cell(*cellptr); - } + cellptr->IsFogMapped = false; + cellptr->IsFogVisible = false; + cellptr->FogFrame = -2; - for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir++) { - Cell c = Adjacent_Cell(cell, dir); - CellClass * cptr = &(*this)[c]; - int fog = TacticalMap->Cell_Shadow(cptr->Fetch_CellID(), true); + if (cellptr->IsMapped) { + TacticalMap->Flag_Cell(*cellptr); + } - if (fog == -2 && cptr->FogFrame != -2) { - Fog_Cell(c); - } else { - if ((cptr->IsFogVisible || fog != cptr->FogFrame) && fog >= 0 && cptr->FogFrame >= -1) { - cptr->FogFrame = fog; + for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir++) { + Cell c = Adjacent_Cell(current, dir); + CellClass * cptr = &(*this)[c]; + int fogshape = TacticalMap->Cell_Shadow(cptr->Fetch_CellID(), true); + + if (fogshape == -2 && cptr->FogFrame != -2) { + pending.push_back(c); + } else if ((cptr->IsFogVisible || fogshape != cptr->FogFrame) && fogshape >= 0 && cptr->FogFrame >= -1) { + cptr->FogFrame = fogshape; cptr->IsFogMapped = true; cptr->IsFogVisible = false; TacticalMap->Flag_Cell(*cptr); } } - } - if (fog) { - cellptr->Fog_Cell(); + if (fog) { + cellptr->Fog_Cell(); + } } } From b66576f4c36aaf1f443c6bfdaa641906987ecd61 Mon Sep 17 00:00:00 2001 From: torradmin Date: Sat, 5 Sep 2026 11:32:38 +0300 Subject: [PATCH 2/3] * Fixed: PLAYTEST F4 button for shrouding/unshrouding the map didn't work. (cherry picked from commit 96eec03c36fcaa6ad04b8dbe9002231cd611040f) (cherry picked from commit 641ab05fbb6c7e82ed543c5892db61987ff2cc7e) --- code/mainloop.cpp | 10 +++++++++- code/map.cpp | 41 ++++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/code/mainloop.cpp b/code/mainloop.cpp index 16f292b44..28d086dec 100644 --- a/code/mainloop.cpp +++ b/code/mainloop.cpp @@ -40,6 +40,7 @@ #include "ipxmgr.h" #include "language/language.h" #include "logic.h" +#include "map.h" #include "misc.h" #include "mpscore.h" #include "msgbox.h" @@ -540,7 +541,14 @@ void Keyboard_Process(KeyNumType & input) if ((Debug_Flag || Debug_Playtest) && plain == KN_F4) { if (Session.Type == GAME_NORMAL) { Debug_Unshroud = (Debug_Unshroud == false); - Map.Flag_To_Redraw(GS_REDRAW_ALL); + + // The same pair the reveal and blackout crates use, so turning this off + // leaves normal vision to re-explore the map rather than leaving it dark. + if (Debug_Unshroud) { + Map.Reveal_The_Map(true); + } else { + Map.Shroud_The_Map(); + } } } diff --git a/code/map.cpp b/code/map.cpp index 9429804c6..4f8aa9ce7 100644 --- a/code/map.cpp +++ b/code/map.cpp @@ -11640,29 +11640,32 @@ bool MapClass::Is_Something_Nearby(Cell const & cell, int radius) /// /// Determines if a coordinate is still under the shroud. /// Which cell a coordinate appears over depends on how high it is, so the height is folded -/// into the lookup before the shroud is consulted. +/// into the lookup before the shroud is consulted. The map debugger's unshroud toggle sees +/// through it, and nothing is reported as hidden while that is active. /// /// bool; Is the coordinate still shrouded? bool MapClass::Is_Shrouded(Coord const & coord) { - int level_height = coord.Z / LEVEL_LEPTON_H; - if ((level_height & 1) != 0) { - int offset = level_height / 2 + 1; - Cell cell = coord.As_Cell(); - CellClass * cptr = &Map[Cell(cell.X - offset, cell.Y - offset)]; - if (cptr->IsMapped) { - return(false); - } - cptr = &cptr->Adjacent_Cell(FACING_SE); - if (!cptr->IsMapped) { - return(true); - } - } else { - int offset = level_height / 2; - Cell cell = coord.As_Cell(); - CellClass * cptr = &Map[Cell(cell.X - offset, cell.Y - offset)]; - if (!cptr->IsMapped) { - return(true); + if (!Debug_Unshroud) { + int level_height = coord.Z / LEVEL_LEPTON_H; + if ((level_height & 1) != 0) { + int offset = level_height / 2 + 1; + Cell cell = coord.As_Cell(); + CellClass * cptr = &Map[Cell(cell.X - offset, cell.Y - offset)]; + if (cptr->IsMapped) { + return(false); + } + cptr = &cptr->Adjacent_Cell(FACING_SE); + if (!cptr->IsMapped) { + return(true); + } + } else { + int offset = level_height / 2; + Cell cell = coord.As_Cell(); + CellClass * cptr = &Map[Cell(cell.X - offset, cell.Y - offset)]; + if (!cptr->IsMapped) { + return(true); + } } } return(false); From 92e694b6905b6d35c54cc3449cefb070765c1530 Mon Sep 17 00:00:00 2001 From: torradmin Date: Sat, 5 Sep 2026 22:27:30 +0300 Subject: [PATCH 3/3] * Added manual changes docs. --- manual/changes/f4-debug-unshroud-toggle.md | 17 +++++++++++++++++ .../stack-overflow-on-mass-shroud-unshroud.md | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 manual/changes/f4-debug-unshroud-toggle.md create mode 100644 manual/changes/stack-overflow-on-mass-shroud-unshroud.md diff --git a/manual/changes/f4-debug-unshroud-toggle.md b/manual/changes/f4-debug-unshroud-toggle.md new file mode 100644 index 000000000..b4e25d634 --- /dev/null +++ b/manual/changes/f4-debug-unshroud-toggle.md @@ -0,0 +1,17 @@ +--- +title: Fix the F4 playtest shroud toggle doing nothing +category: fix +release: 0.2.0 +targets: +- type: command + id: fixed:debug-unshroud + effect: changed +credit: +- torradmin +--- + +Pressing F4 flagged the whole tactical view for redraw but never touched the shroud data +itself, so the map stayed exactly as shrouded as before. F4 now reveals the map when +turning the toggle on and reshrouds it when turning the toggle off, the same pair the +reveal-the-map and blackout crates use, and coordinate shroud checks now see through an +active toggle instead of only the redraw seeing it. diff --git a/manual/changes/stack-overflow-on-mass-shroud-unshroud.md b/manual/changes/stack-overflow-on-mass-shroud-unshroud.md new file mode 100644 index 000000000..abc78dcd1 --- /dev/null +++ b/manual/changes/stack-overflow-on-mass-shroud-unshroud.md @@ -0,0 +1,17 @@ +--- +title: Fix crash when large fog changes unwound recursively +category: fix +release: 0.2.0 +targets: +- type: system + id: map-visibility + effect: changed +credit: +- torradmin +--- + +Regrowing fog over a cell recursed into every neighbor that also needed to regrow, so +shrouding or unshrouding a large connected area — a big map, a reveal-the-map crate, or the +[map shroud toggle](/commands/fixed-debug-unshroud/) — could recurse deeply enough to +overflow the stack and crash. Fog regrowth now walks an explicit queue instead, so the +depth of a shroud change no longer bounds how much fog can change at once.