diff --git a/engine/MUNGA_L4/L4CTRL.cpp b/engine/MUNGA_L4/L4CTRL.cpp index 49a31b4..02ea2eb 100644 --- a/engine/MUNGA_L4/L4CTRL.cpp +++ b/engine/MUNGA_L4/L4CTRL.cpp @@ -1118,11 +1118,22 @@ LBE4ControlsManager::~LBE4ControlsManager() #ifdef BT_GLASS // - // Symmetric with the Create above (2026-08-06). ~PadRIO tears the panels - // down itself, so this is a no-op there -- but the HARDWARE RIO's dtor - // knows nothing about them, and windows that outlive the surfaces they - // blit are a crash waiting for the next mission cycle. Destroy is safe - // if they were never created. + // Symmetric with the Create above (2026-08-06). The HARDWARE RIO's dtor + // knows nothing about the panels, and windows that outlive the surfaces + // they blit are a crash waiting for the next mission cycle. + // + // CORRECTION (2026-08-07, #140): the original comment here claimed this was + // "a no-op" on the PadRIO path because ~PadRIO tears the panels down itself. + // It was not. ~PadRIO runs first (delete rioPointer, above), zeroing the + // window list -- and BTGlassPanels_Destroy unconditionally ran SaveLayout + // BEFORE looking at whether anything was left, so this second call rewrote + // glass_layout.cfg with every MFD and radar line missing. Only the plasma + // window survived, because external windows cached a last-known rect and the + // per-display windows did not. That is the regression testers hit on the + // desktop the same day the pod panels were wired up; the pod itself was + // unaffected (no PadRIO, so only ONE destroy, and it runs BT_GLASS_LAYOUT= + // load anyway). Fixed on both sides in L4GLASSWIN: the geometry is now + // remembered across teardown, and the teardown save is guarded. // BTGlassPanels_Destroy(); #endif diff --git a/engine/MUNGA_L4/L4GLASSWIN.cpp b/engine/MUNGA_L4/L4GLASSWIN.cpp index ded2c53..2da5202 100644 --- a/engine/MUNGA_L4/L4GLASSWIN.cpp +++ b/engine/MUNGA_L4/L4GLASSWIN.cpp @@ -916,9 +916,50 @@ static void << layoutFileName << "\n" << std::flush; } -// Write every window's current on-screen frame rect. Whole-file rewrite (it is -// tiny), so partial/hard kills never leave a half-written file for long. Called -// on finished-drag and on teardown in save mode. +// LAST-KNOWN GEOMETRY, kept ACROSS teardown (2026-08-07, #140). +// +// SaveLayout rewrites the whole file, so it can only be as complete as what it +// can see -- and it could only see LIVE windows. Destroy() nulls every hwnd and +// zeroes gWinCount, so any save that ran after a teardown wrote a file with +// every MFD and radar line MISSING. The plasma window survived that because +// external windows already cached a last-known rect (gExtern[].haveLast); the +// per-display windows had no such cache, which is exactly the reported +// signature: "all the MFDs and secondary lines missing, but plasma was still +// there". Give the glass windows the same guarantee, so the file is monotonic +// -- a save can update a line or add one, never drop one. +struct SavedGeom +{ + char title[64]; + RECT r; + int noFrame; +}; +static SavedGeom gLastGeom[16]; +static int gLastGeomCount = 0; + +static void + RememberGeom(const char *title, const RECT &r, int noFrame) +{ + if (title == NULL || title[0] == '\0') + return; + for (int i = 0; i < gLastGeomCount; ++i) + if (strcmp(gLastGeom[i].title, title) == 0) + { + gLastGeom[i].r = r; gLastGeom[i].noFrame = noFrame; + return; + } + if (gLastGeomCount >= (int)(sizeof(gLastGeom) / sizeof(gLastGeom[0]))) + return; + SavedGeom &g = gLastGeom[gLastGeomCount++]; + strncpy(g.title, title, sizeof(g.title) - 1); + g.title[sizeof(g.title) - 1] = '\0'; + g.r = r; g.noFrame = noFrame; +} + +// Write every window's frame rect. Whole-file rewrite (it is tiny), so +// partial/hard kills never leave a half-written file for long. Called on +// finished-drag and on teardown in save mode. Live windows refresh the +// remembered geometry first; the FILE is then written from the remembered set, +// so a window that has already been torn down keeps its line. static void SaveLayout() { @@ -945,6 +986,7 @@ static void "# this list too -- it can be dragged, remembered and set ,noframe.\n", f); int wrote = 0; + // 1. refresh the remembered geometry from whatever is currently alive for (int i = 0; i < gWinCount; ++i) { GWin &gw = gWins[i]; @@ -953,10 +995,16 @@ static void RECT r; if (!GetWindowRect(gw.hwnd, &r)) continue; - fprintf(f, "%s=%ld,%ld,%ld,%ld%s\n", gw.title, - (long)r.left, (long)r.top, - (long)(r.right - r.left), (long)(r.bottom - r.top), - gw.noFrame ? ",noframe" : ""); // keep the hand-added option + RememberGeom(gw.title, r, gw.noFrame); // keep the hand-added option + } + // 2. write the remembered set -- including windows already torn down + for (int i = 0; i < gLastGeomCount; ++i) + { + const SavedGeom &g = gLastGeom[i]; + fprintf(f, "%s=%ld,%ld,%ld,%ld%s\n", g.title, + (long)g.r.left, (long)g.r.top, + (long)(g.r.right - g.r.left), (long)(g.r.bottom - g.r.top), + g.noFrame ? ",noframe" : ""); ++wrote; } // External windows (the plasma window) ride the same file. Cache the last @@ -1741,8 +1789,14 @@ void void BTGlassPanels_Destroy() { - SaveLayout(); // backstop for a clean teardown (WM_EXITSIZEMOVE already - // caught every finished drag); no-op unless mode==save + // Backstop for a clean teardown (WM_EXITSIZEMOVE already caught every + // finished drag); no-op unless mode==save. GUARDED on there being windows: + // this function has TWO callers (~PadRIO and ~LBE4ControlsManager), so on + // the desktop path it runs twice, and the second pass has nothing live to + // report. The remembered-geometry cache above already makes that harmless, + // but there is no reason to rewrite the file to say the same thing. + if (gWinCount > 0) + SaveLayout(); for (int i = 0; i < gWinCount; ++i) { diff --git a/scratchpad/night13/layoutsave.sh b/scratchpad/night13/layoutsave.sh new file mode 100644 index 0000000..12353c7 --- /dev/null +++ b/scratchpad/night13/layoutsave.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# ========================================================================= +# #140 glass_layout.cfg save regression -- the FIELD composition. +# +# SAURON's report: "set my glass panels, saved and borders off ... relaunched +# and it reset again ... the glass_layout.cfg had all the MFDs and secondary +# lines missing, but plasma was still there". +# +# So the acceptance test is the full ROUND TRIP a tester does, not just a +# single launch: +# run 1 -- BT_GLASS_LAYOUT=save, panels come up, quit cleanly +# check -- the cfg must list every MFD + the radar, not just plasma +# run 2 -- relaunch; the panels must come back where they were +# +# The teardown is what mattered: BTGlassPanels_Destroy has TWO callers on the +# desktop path (~PadRIO, then ~LBE4ControlsManager), and it ran SaveLayout +# BEFORE checking whether any windows were left -- so the second pass rewrote +# the file from an empty list. +# ========================================================================= +set -x +. /c/git/bt411/scratchpad/night6/bench_common.sh +cd /c/git/bt411/content || exit 1 + +taskkill //F //IM btl4.exe > /dev/null 2>&1 +sleep 2 +rm -f glass_layout.cfg ls_run1.log ls_run2.log + +# ---- run 1: create the layout ------------------------------------------ +( export BT_GLASS_PANELS=1 BT_GLASS_LAYOUT=save + bt_launch ls_run1.log ARENA1.EGG 0x03 ) +sleep 55 +bt_kill_ours +sleep 2 +taskkill //F //IM btl4.exe > /dev/null 2>&1 +sleep 3 + +echo "############ RUN 1 SAVE RESULT ############" +echo "--- every 'saved N window position(s)' line (the double-save shows here) ---" +grep -a "window position" ls_run1.log +echo +echo "--- glass_layout.cfg AFTER a clean quit ---" +if [ -f glass_layout.cfg ]; then cat glass_layout.cfg; else echo "!!! NO FILE WRITTEN"; fi +echo +echo "--- line census (comments excluded) ---" +echo -n "total entries : "; grep -acE "^[^#]+=" glass_layout.cfg 2>/dev/null +echo -n "MFD lines : "; grep -acE "^(Heat|Comm|Mfd|Eng|Weap|Sec)" glass_layout.cfg 2>/dev/null +echo -n "plasma line : "; grep -ac "Plasma" glass_layout.cfg 2>/dev/null +cp glass_layout.cfg /tmp/ls_after_run1.cfg 2>/dev/null + +# ---- run 2: does it RESTORE? ------------------------------------------- +( export BT_GLASS_PANELS=1 BT_GLASS_LAYOUT=save + bt_launch ls_run2.log ARENA1.EGG 0x03 ) +sleep 55 +bt_kill_ours +sleep 2 +taskkill //F //IM btl4.exe > /dev/null 2>&1 +sleep 3 + +echo "############ RUN 2 (RELAUNCH) ############" +echo "--- restore receipts ---" +grep -aE "restored|glass_layout|window position" ls_run2.log | head -12 +echo +echo "--- cfg after run 2 -- must still hold every line ---" +cat glass_layout.cfg 2>/dev/null +echo +echo "--- DIFF run1 -> run2 (empty = layout survived the round trip) ---" +diff /tmp/ls_after_run1.cfg glass_layout.cfg && echo "IDENTICAL"