# BattleTech: FireStorm — Project Notes (CLAUDE.md)

Working notes for Claude Code. Updated as work progresses.

## ⚠️ Repository location, git & auth (relocated 2026-06-26)
**The whole repo moved from `C:\VWE` → `C:\VWE\firestorm`** so `C:\VWE` can host other
projects. `firestorm\` is now a **self-contained git repo** (its own `.git`); `C:\VWE` is just
a parent container (NOT a git repo). All paths in this file and in build/deploy scripts are the
new `C:\VWE\firestorm\...` paths.
- **What the move touched:** physical move of all top-level entries (incl. `.git`); no junctions/
  symlinks existed to break; single volume so it was an instant metadata rename. `git status` was
  clean afterward (repo-relative paths unchanged) — the only diff was the path rewrites below.
- **Hardcoded paths rewritten** `c:\VWE\... → c:\VWE\firestorm\...` (encoding-preserved) in:
  `Gameleap\code\mw4\Libraries\stlport\stl_inc_find.hpp` (stlnative include — build-critical),
  the `build-env\*.ps1` deploy/build scripts, `build-env\play-mr.*`, the `build-env\*.reg` files
  (incl. UTF-16 `vc6-hklm-registration.reg`), and the docs (`CLAUDE.md`/`README.md`/`RECOVERY.md`).
  Stale `build-env\*.log`/`.plg`/`.map` build artifacts still contain old paths (historical, unused).
- **System registry was repointed to the new paths** (so build + run still work):
  - HKCU: VC6 IDE directories (`...DevStudio\6.0\...\Directories`) + AppCompat `Layers` shims for
    `MW4.exe`, `MW4pro.exe`, `MW4Ed2.exe`; dropped two stale `C:\VWE\MW4Editor\*` entries
    (that dir was removed in STEP 9).
  - HKLM (elevated): VC6 install registration (`Wow6432Node\...\VisualStudio\6.0`, ProductDir) +
    all-users AppCompat shims for `MW4.exe`/`MW4pro.exe`.
  - **Reminder:** AppCompat (`DWM8And16BitMitigation`) is keyed on the exe **PATH** — relocating an
    exe requires re-applying the shim (the `.reg` files + `deploy-*.ps1` now use firestorm paths).
- **Git remote (new):** `origin = https://gitea.mysticmachines.com/VWE/firestorm.git`
  (was `http://192.168.1.167:3000/cyd/firestorm.git`; host + owner `cyd→VWE` changed; same backend,
  so LFS objects already present — pushes only send new deltas). LFS access hint set to `basic`.
- **Auth = Personal Access Token over basic auth**, stored in Windows Credential Manager. The Git
  Credential Manager **browser-OAuth flow for Gitea FAILS here** (loopback redirect to
  `127.0.0.1:<port>` does not complete). To (re)seed creds, run in Git Bash:
  `printf 'protocol=https\nhost=gitea.mysticmachines.com\nusername=<USER>\npassword=<PAT>\n' | git credential approve`
  (PAT needs `repository: Read and Write`). Then `git push` uses it with no browser prompt.
- Uses **Git LFS**; `.gitattributes` forces `* -text` for byte-exact restores regardless of
  `core.autocrlf`.

## What this project is
A total-conversion / expansion ("FireStorm") built on the **MechWarrior 4** game
engine (the *Gameleap* engine, v5.03), using Microsoft's **GameOS / GOS**
technology (same lineage as MechWarrior 4 and MechCommander 2). ~60k files mixing
C++ engine source, an asset/content pipeline, and game-design spreadsheets.
Toolchain is late-90s/early-2000s **Visual C++ 6.0** — no git, no modern build system.

## Repo layout (top level)
> **2026-06-23 cleanup:** unused/duplicate trees archived to **`_UNUSED\`** to reduce newcomer
> confusion (Gameleap\{Archive,Drivers,EditorDocs,Notes,batch,utilities}; GameleapCode5_03\{Content,hsh};
> resource_fullbak). Build paths untouched. See **`README.md`** for the orienting layout map.
> Build/deploy uses only: `Gameleap\mw4` (data), `Gameleap\code` (code+binaries), `build-env` (toolchain).

- **`Gameleap\code/`** — THE BUILD TARGET. Engine + game source + content. *(confirmed by user)*
  (Its `Content\` + `hsh\` were stale 2005 duplicates → moved to `_UNUSED\`; live data is `Gameleap\mw4`.)
- **`Gameleap/`** — only **`mw4/`** is used (game-data source for resource packing). Former siblings
  (editor docs, drivers, batch tools, runtime env) were historical/utility → moved to `_UNUSED\`.
- **`BTFrstrm/`** — FireStorm design data: mech stat workbooks (`MechInfo_5.04.xls`, `scriptaddmech.xls`).
- **`Finished HUDS from J&J/`** — J&J external MFD/Radar source art, measurements, generated
  comparison maps, and audit summary for 13 chassis. The usable mappings were installed on
  2026-08-07; see `MFD-RADAR-MAPPINGS.md` and the STEP 13 note below.

### Inside `Gameleap\code/`
- `CoreTech/` — reusable engine layer (GameOS, gosFX, GOSScript, MLR renderer, Network, Stuff, Language, blade) + Tools.
- `mw4/Code/` — the game itself. `mw4/Code/MW4/` = ~590 files; AI subsystem dominates (~65 `AI_*` files).
- `mw4/Libraries/` — mw4-local libs (Adept, Compost, ElementRenderer, gosfx, ImageLib, MLR, server, stuff, **stlport**).
- `Content/` — game content: `ABLScripts/` (ABL scripting language), Defines, Subsystems, Tables, `.build` files.
- `rel.bin/` `dbg.bin/` `pro.bin/` — **build OUTPUT folders** (libs/exe/dll land here). Generated, not source.

---

## ✅ STEP 1: Build process reconstruction — COMPLETE

### Toolchain
- **Compiler/IDE:** Visual C++ 6.0 / MSDev98 (all `.dsw`/`.dsp` are "Format Version 6.00").
- **STL:** bundled **STLport** at `mw4/Libraries/stlport/` (custom, build it first).
- **External SDKs (NOT bundled)** — exact reqs from `CoreTech/Libraries/GameOS/pch.hpp`:
  - **DirectX 7.0 SDK** — headers must be **FIRST** in include order
    (`#if DIRECTDRAW_VERSION != 0x0700 → #error`; uses ddraw7, dinput7, d3d.h immediate mode, dplay).
  - **DirectX Media 6.0 SDK** — DirectShow video (`amstream.h`, `__IDDrawExclModeVideo`).
  - **EAX SDK** (Creative `eax.h`) — audio.
  - **DirectX 8 link libs** (`d3d8.lib d3dx8.lib dinput8.lib dxguid.lib`) — used by standalone tools
    (Gos2Light/Text, HighTide, etc.).
  - NOTE: SDK paths are NOT in the `.dsp` files — they come from the IDE's global
    **Tools → Options → Directories**, which is why DX7-first ordering matters.
- Compiler flags of note: `/G6 /Zp4` (struct packing 4 — must be consistent across all libs),
  `/MD` runtime (Release), `/MDd` (Debug). Mismatched packing/runtime = link/crash.

### Locating VC6 + SDKs on the ORIGINAL DISK IMAGE
- **VC6 / MSDev98:** `C:\Program Files\Microsoft Visual Studio\`
  - `VC98\Bin\` (cl.exe, link.exe, `VCVARS32.BAT`), `VC98\Include`, `VC98\Lib`, `VC98\MFC`
  - `Common\MSDev98\Bin\MSDEV.EXE` (IDE + command-line builder).
- **DirectX/Media/EAX SDKs** — check these era-typical roots:
  - DX7 SDK: `C:\DXSDK\`, `C:\mssdk\`, or `C:\DX7SDK\`
  - DX8 SDK: `C:\DXSDK\` or `C:\Program Files\Microsoft DirectX 8*SDK\`
  - DX Media 6: `C:\DXMedia\` or `C:\DXMEDIASDK\`
  - EAX SDK: under `C:\` or the Creative/SB SDK folder.
- **⭐ Most valuable artifact — the IDE directory ordering** (reproduces the env exactly).
  Load the build user's `NTUSER.DAT` and read:
  `HKCU\Software\Microsoft\DevStudio\6.0\Build System\Components\Platforms\Win32 (x86)\Directories`
  → values **"Include Dirs", "Library Dirs", "Path Dirs"** (ordered; copy verbatim).
  Also: `HKLM\SOFTWARE\Microsoft\VisualStudio\6.0`, `HKLM\SOFTWARE\Microsoft\DirectX` (records DX version).
- **Signature-file search on a mounted image:** `MSDEV.EXE`/`VCVARS32.BAT` → VC6;
  `ddraw.h`/`d3d.h`/`dinput.h` → DX7 includes (verify `DIRECTDRAW_VERSION 0x0700`);
  `amstream.h` → DX Media 6; `d3dx8.lib`/`eax.h` → DX8 / EAX SDK.

### Master workspace & final target
- **Workspace:** `Gameleap\code/mw4/Code/MechWarrior4.dsw` (the modern/current one).
  - Self-contained: all 24 sub-projects verified present on disk, spanning both
    `mw4/Libraries` and `CoreTech/Libraries`.
- **`MechWarrior4Build.dsw`** = LEGACY/alternate (uses old `MW4Gos`, `Network`, `GamePlatform`,
  `MW4GameEd` v1). Do not use; superseded by `MechWarrior4.dsw`.
- **Final game target:** project **`MW4Application`** → `MW4.exe`.
- **Editor target:** project **`MW4GameEd2`** → editor exe (separate).

### Build configurations → output paths (from MW4Application.dsp)
| Config  | Defines                | Output            |
|---------|------------------------|-------------------|
| Debug   | `_DEBUG _ARMOR` `/MDd` | `dbg.bin/MW4.exe` |
| Armor   | `NDEBUG _ARMOR` `/Zi`  | `arm.bin/MW4.exe` |
| Profile | `NDEBUG` profiling     | `rel.bin/MW4pro.exe` |
| Release | `RELEASE NDEBUG`       | `rel.bin/MW4.exe` |
| icecap  | `_ICECAP`              | `ice.bin/`        |

A prior successful **Release build exists** in `rel.bin/` (MW4.exe, MW4pro.exe, all `.lib`s,
Launcher.exe, ctcls.dll, MissionLang.dll, ScriptStrings.dll, autoconfig.exe, mw4print.exe).
`arm.bin/` and `ice.bin/` are absent (those configs were never built here).

### Build dependency order (topological, from MechWarrior4.dsw)
1. **`stlport`** — foundation, no deps. Build first.
2. **Leaf libs** (depend only on stlport / nothing): `GameOS`, `Stuff`, `MLR`, `gosFX`,
   `ImageLib`, `Compost`, `GOSScript`, `ElementRenderer`, `Adept`, `server`,
   `DLLPlatform`, `MFCPlatform`, `GamePlatformNoMain`, `ctcls`.
3. **`MW4`** — the game library.
4. **Helper exes/dlls:** `Launcher`, `autoconfig`, `mw4print`, `MissionLang`,
   `ScriptStrings`, `MW4DedicatedUI`.
5. **`MW4Application`** — links everything into `MW4.exe`. *(Set as Active Project.)*

VC6 honors these dependencies automatically via the `.dsw` "Project Dependency" records,
so a single "Build" of MW4Application pulls the whole chain in order.

### How to build
- **IDE:** Open `mw4/Code/MechWarrior4.dsw` in VC6 → set **MW4Application** as Active Project
  → pick config (Win32 Debug / Release) → Build.
- **Command line (VC6):**
  `msdev MechWarrior4.dsw /MAKE "MW4Application - Win32 Debug"`
  (or `"... - Win32 Release"`). Add `/REBUILD` for a clean build.

### How to RUN (the `mw4/*.bat` scripts are LAUNCH, not compile)
- `game_dbg.bat` etc. just run the exe: `binaries\mw4\MW4_dbg -armorlevel 3 -nobuild`.
- `-build` = (re)build game resource `.erf`s from source assets at startup; `-nobuild` = skip.
- `bldresources_game_dbg.bat` = run with `-build` to regenerate resources.
- `editor_dbg.bat` = launch the editor (`MW4Ed2_d`).

### ⚠️ Open discrepancies to resolve before a real build
1. **Output path vs. launch path mismatch:** `.dsp` files emit to `dbg.bin/MW4.exe` /
   `rel.bin/MW4.exe`, but the `game_*.bat` launchers point at `binaries\mw4\MW4_dbg.exe`.
   There is a missing copy/deploy step (or the bats target a different deployed layout).
   `mw4/Binaries/` currently only holds `3DSPlug-ins/`. **TODO: find/recreate the deploy step.**
2. `FullExport.batch` references dead studio network paths (`\\aas1\...`, `d:\ContentExport\`).
   That's the *art* export pipeline, irrelevant to the code build.

---

## ✅ STEP 2: Toolchain extracted from original disk image (E:\) — COMPLETE
Original build machine image mounted at `E:\`. Build user = **`jeff`**.
Everything needed is now copied into **`build-env/`** (self-contained):
- **`build-env/VisualStudio6/`** ← `E:\Program Files\Microsoft Visual Studio` (VC6: CL, LINK, NMAKE, MSDEV).
- **`build-env/dx7asdk/`** ← `E:\Code\dx7asdk` (DirectX **7.0a** SDK; `ddraw.h` = `DIRECTDRAW_VERSION 0x0700` ✓).
- **`build-env/DXMedia/`** ← `E:\Code\DXMedia` (DirectX **Media 6.0** SDK; amstream/control/strmif + libs).
- **`build-env/setup-mw4-build.bat`** + **`README.md`** (generated; build instructions).

Resolutions to prior open items:
- **DirectX version pinned:** DX **7.0a** SDK (headers) + DX **Media 6.0** SDK. NOT DX8 for the game.
- **VC6 located & copied:** VC++ 6.0 at `build-env/VisualStudio6/`.
- **EAX:** bundled in repo (`GameOS/Eax.h`); no static `eax.lib` needed (runtime-resolved).
- **DX8 SDK:** absent from image; only blocks 3 standalone tools (Gos2Light/Text, HighTide), not the game/editor.
- **Critical ordering:** DX7 includes MUST precede VC98\INCLUDE (engine `#error` guard); enforced in setup .bat + README.

### ✅ Exact registry directory order RECOVERED (from jeff's NTUSER.DAT)
Key: `HKCU\...\DevStudio\6.0\Build System\Components\Platforms\Win32 (x86)\Directories`
- **Include:** `DXMedia\include → dx7asdk\include → VC98\INCLUDE → VC98\MFC\INCLUDE → VC98\ATL\INCLUDE`
- **Library:** `VC98\LIB → VC98\MFC\LIB` only.
Two verified facts that make this work:
1. `DXMedia\include` has NO core DX headers (DirectShow only) → `ddraw.h` still resolves
   from `dx7asdk` before VC98's old one (satisfies the `pch.hpp` 0x0700 guard).
2. The DX/DShow `.lib`s were **copied into `VC98\Lib`** on the original machine (verified
   present in our copy: ddraw/dinput/dsound/dxguid/strmiids/quartz/strmbase/amstrmid);
   pulled in via `#pragma comment(lib,...)` in `GameOS\guids.cpp`, not the link line.
Captured as **`build-env/vc6-directories.reg`** (rebased to local copies; HKCU import, no admin)
and reflected exactly in **`build-env/setup-mw4-build.bat`**.

### ✅ HKLM VC6 install registration extracted & rebased
Exported jeff's offline `software` hive trees (`Microsoft\VisualStudio\6.0`,
`Microsoft\DevStudio\6.0`) and transformed into **`build-env/vc6-hklm-registration.reg`**:
- Key root redirected `oldsw` → `HKLM\SOFTWARE\Wow6432Node\...` (32-bit `msdev` on 64-bit Windows
  reads HKLM\SOFTWARE redirected to Wow6432Node; HKCU\Software is NOT redirected).
- All paths rebased: `C:\Program Files\Microsoft Visual Studio` → `build-env\VisualStudio6`;
  `C:\Program Files\Common Files\Microsoft Shared` → `build-env\CommonFiles-MSShared`
  (copied `VS98` + `MSDesigners98` so those refs resolve).
- Verified: 0 leftover `oldsw`, 0 un-rebased paths, 202 keys, ProductDir/InstallDir correct.
- Raw exports kept as `build-env/_raw_*.reg` (provenance).
- Import order (ELEVATED): `vc6-hklm-registration.reg` then `vc6-directories.reg`.

---

## ✅ STEP 3: Clean build from source — SUCCESS (game target)
Registry imported (HKLM `vc6-hklm-registration.reg` + HKCU `vc6-directories.reg`), msdev
runs from the copy, and a **full clean rebuild of `MW4Application - Win32 Release` succeeds**:
- 22 projects compiled in dependency order (stlport → GameOS/Stuff/MLR/gosFX/… → MW4 →
  helper exes/dlls → MW4Application). **`MW4.exe` — 0 errors**, 384 warnings (normal for VC6).
- Output: `rel.bin\MW4.exe` (~3.64 MB), plus fresh `*.lib`, `Launcher.exe`, `autoconfig.exe`,
  `mw4print.exe`, `MissionLang.dll`, `ScriptStrings.dll`, `ctcls.dll`.
- Build is reproducible head-lessly via:
  `msdev MechWarrior4.dsw /MAKE "MW4Application - Win32 Release" /REBUILD /OUT <log>`
  (run via `Start-Process -Wait`; msdev is a GUI-subsystem app so the shell won't block otherwise).
- Build logs kept in `build-env\build_*.log`.

### ✅ `stlnative` dependency relocated INTO the working tree
`mw4/Libraries/stlport/stl_inc_find.hpp` hardcoded a native-headers path
`<c:\stlnative/##x>` (3 macro defs, the only references in the codebase). STLport wraps a
27 MB snapshot of the VC98 + Platform SDK headers (974 files) restored from the image.
Originally placed at `C:\stlnative`; now **moved into `build-env\stlnative\`** and the 3
macros repointed to `<c:\VWE\firestorm\build-env\stlnative/##x>`. Verified: with `C:\stlnative`
removed, a full clean rebuild still succeeds (MW4.exe, 0 errors). The build is now
self-contained under `c:\VWE\firestorm` — no external `C:\stlnative` needed.

---

## ✅ STEP 4: Runnable deployment assembled at `C:\VWE\firestorm\MW4` — DONE
Rules-based deploy (no image/E: dependency) via **`build-env\deploy-mw4.ps1`**:
- **Result: `C:\VWE\firestorm\MW4` — 845 files, 895 MB** (target was ~900 MB), with our freshly
  built `MW4.exe` (3,637,248 bytes) + all runtime DLLs + prebuilt `resource\*.mw4`.
- Sources (all from working dir): runtime base = `Gameleap\mw4`; binaries = `rel.bin`.
- The old `game_*.bat` assumed a `binaries\mw4\MW4_dbg.exe` layout that the real game
  never used — the runnable game has `MW4.exe` at the deployment root. Launch via
  **`C:\VWE\firestorm\MW4\run-mw4.bat`** (resources prebuilt, no `-build` needed).

### Deployment selection rules (the dev tree `Gameleap\mw4` is a ~6 GB bloated superset)
- KEEP whole: `Resource\` (767 MB .mw4 packages), `hsh\` (102 MB), `Assets\`, `Stats\`.
- KEEP subset: `Content\shellscripts\` MINUS `Graphics\` (119 MB source .tga) and
  `Original\` (57 MB backups) -> ~9 MB of compiled scripts/.gaf/.d3f.
- KEEP root: runtime `*.dll/*.ini/*.options/*.txt/*.rtf/*.ico/*.016/*.256/...` + `clokspl.exe`.
- DROP: `Content\*` (4.6 GB source except shellscripts), `Movies\` (352 MB), `Notes\`,
  `fonts\`, all tool exes (ERF Deluxe, EffectEdit, MapCreator, ResourceBrowser, MW4Ed2, ...).
- OVERLAY: fresh `rel.bin` binaries (MW4.exe, MW4pro.exe, Launcher, autoconfig, mw4print,
  MissionLang.dll, ScriptStrings.dll, ctcls.dll).

---

## ✅ STEP 5: Runtime brought up at `C:\VWE\firestorm\MW4` — RUNS (fullscreen + windowed)
Reference for "working" = **`C:\MW4knowngood`** (NOT `E:\gameleap`, NOT `D:\5.0.8.2_console`
— those are unrelated). Two independent issues had to be fixed; both are now in
`deploy-mw4.ps1` so a fresh deploy is runnable out of the box:

1. **DirectDraw 16-bit compat shim (Win11).** MW4 runs `bitdepth=16`; modern Windows fails
   its DirectDraw hardware-surface creation with `DDERR_NODIRECTDRAWHW` unless the
   **`DWM8And16BitMitigation`** AppCompat layer is applied to the exe *path*. Our path had a
   stray `HIGHDPIAWARE`-only layer that suppressed the auto-shim. Fix: set HKCU layer
   `DWM8And16BitMitigation HIGHDPIAWARE` for `C:\VWE\firestorm\MW4\MW4.exe` (deploy script does this;
   matching all-users HKLM `$ DWM8And16BitMitigation` is in `build-env\mw4-compat-hklm.reg`).
   Note: AppCompat is keyed on exe PATH, so a copy at a new path needs the layer re-applied.
2. **Dev-only DLLs shadowing system DLLs.** Our rules copied every root `*.dll`, which pulled
   in `dbghelp.dll`/`imagehlp.dll` (2003 copies that shadow Windows' system DLLs and break
   DirectDraw init) plus tool DLLs `ijl10.dll`, `ctcl.dll`, `Language.oeg.dll`, `Resources.dll`.
   known-good has none of these. Fix: deploy script now excludes those 6 by name. Removing
   them was what made it run in BOTH fullscreen and windowed.

Diagnostic note: swapping known-good binaries into our deploy still failed *before* the shim
was applied — that proved our rebuilt binaries are NOT the cause (the shim + DLL excludes were).
Final deployment: **`C:\VWE\firestorm\MW4` = 894 MB, 839 files, DLL set identical to known-good**, running
our freshly built `MW4.exe`.

---

## Resource (`.mw4`) build process — documented & ✅ VERIFIED
Full writeup in **`build-env\RESOURCE-BUILD.md`**. Tested end-to-end 2026-06-11 with our
freshly built `MW4pro.exe` (Profile/LAB_ONLY): minimal `Content\MechWarrior4.build` ->
`resource\restest.mw4` + `.dep` (exit 0); `#VBD` magic + payload confirmed; `.dep` tracks
source; incremental skip/rebuild both verified; Release exe confirmed to ignore `-build`.
Key facts:
- The resource compiler is **inside the game exe**, triggered by the **`-build`** flag,
  which is `#ifdef LAB_ONLY` — so **only Debug/Armor/Profile (`MW4_dbg`/`MW4_arm`/`MW4pro.exe`)
  can build resources; Release `MW4.exe` cannot**. (That's why `bldresources_game_*.bat`
  use the LAB_ONLY exes.)
- Entry point: `MW4Application.cpp` -> `Tool::Instance->BuildResources("Content\MechWarrior4.build",
  VER_CONTENTVERSION=63, deps)`; recursive driver `Adept::Tool::BuildResources` /
  `ParseBuildFile`; per-type packers in `Adept\Tool.cpp` + `MW4\MWTool.cpp`.
- Input: `Content\MechWarrior4.build` (master tree) -> per-package `.build` manifests
  (`[resource\X.mw4]` + `data=`/`instance=`/`file=`/`notation=`/`campaign=`/... entries) ->
  source assets under `Content\`. Output: `resource\*.mw4` + `*.dep` (incremental via `.dep`
  timestamps + content version). Needs the full ~4.6 GB `Content\` source tree.

### ⚠️ Resource packer quirks (discovered 2026-07-02, ConLobby promotion)
1. **Incremental rebuilds carry forward stale entries.** A package rebuild re-packs only
   entries the `.dep` flags as changed and copies the rest — including entries whose SOURCE
   FILES WERE DELETED (they are never dropped) — from the previous `.mw4`. Per-entry FILETIME
   in the directory = when that entry was last (re)packed, not the source mtime. **To pick up
   deletions/renames, force a full repack: delete `resource\<pkg>.mw4` + `<pkg>.dep`, then run
   `build-env\build-resources.ps1`.** Also: `Copy-Item` preserves the source mtime, so a
   promoted/copied file can look OLDER than the package and be skipped — touch it (or full-repack).
2. **Script entry names are alphabetically skewed vs their contents** (in `directory=` sweeps,
   e.g. props' `content\shellscripts`): e.g. entry `ConLobby.script` holds CreatePilotModal-like
   text, entry `ConLobbyMission.script` holds `GUNStatus.script` (byte-identical), and our 140 KB
   console (source `ConLobby.script`) lands under entry `ComputerPlayer.script` — IDENTICAL skew
   in the 2016 release props.mw4, which shipped and worked. The runtime evidently resolves names
   through the same pairing, so packages are self-consistent in-game; only extraction tools see
   "swapped" names. Don't "fix" packed name mismatches by editing sources — verify in-game.

## ✅ STEP 6: Editor (`MW4GameEd2`) builds — DONE (with a 1-line source fix)
- Target `MW4GameEd2 - Win32 Release` -> **`rel.bin\MW4Ed2.exe`** (3,973,120 bytes, 0 errors).
  MFC app (`Use_MFC 6`); needs `mfc42.dll` (present in deployment) at runtime. Profile config
  also exists (-> `pro.bin\MW4Ed2.exe`).
- **Source fix required** (editor source had drifted; shipped `MW4Ed2.exe` is from 2003 and
  was never rebuilt against the 2009 code): enum `MPGameTypes` in
  `mw4\Code\MW4GameEd2\ObjectManager.h` was **missing `GT_StockCampaign`**, which
  `ObjectManager.cpp` references (string map @1013, int map `case 14`). Added the enum member
  between `GT_StockSiegeAssault` and `GT_CustomDestruction`. (This is the only repo source edit
  beyond the stlnative relocation; both are marked with `[...]` comments in-place.)
- Built incrementally but it was a *full* compile of the editor's own sources (none existed
  prior); shared libs reused from the Release game build.

---

## ⚠️ STEP 7: Editor builds + launches + draws UI, then HANGS (viewport spin)
Set up via **`build-env\deploy-editor.ps1`**; launches to window
**"MechWarrior 4 Mission Editor - 05.07.00.00"** and creates its MDI children (Game View,
Object Manager, Overview Window) — but then **spins one CPU core at ~100% with zero memory
growth and a stalled message pump** ("not responding"; 282s CPU / 283s uptime). It's a
busy-loop hang in the editor's real-time 3D viewport/idle loop — a **runtime** compat issue
on modern hardware, NOT a build issue (it compiled, launched, and drew its UI). The game runs
fine (different render path: fullscreen exclusive vs the editor's windowed live viewport).
### ROOT CAUSE FOUND: incomplete dev content + modal-STOP-over-windowed-DDraw
The editor loads/builds the full content tree at startup and **STOPs (fatal) on missing/
inconsistent dev content**, one item at a time:
- `skies\reduex_sky\reduex_night.erf` — misnamed (`Reduex_sky_night.erf`); FIXED by copy.
- `Texture skyfbb1` (referenced by `Content\Skies\fact01_sky\fact01_sky.hint`) — **genuinely
  absent** from the dev tree (no file of that name anywhere). The FireStorm dev `Content\`
  is an incomplete WIP with real asset gaps.
GameOS's `STOP` pops a **modal dialog**. Build-specific behavior on that dialog:
- **Profile** (LAB_ONLY, lighter opt): shows the STOP dialog cleanly; dismissing it exits.
- **Release** (`/Ox /Og`, no LAB_ONLY): trying to show the modal STOP over the **windowed
  DirectDraw** viewport **hangs (100% spin) or crashes (AV)** — a dialog/DDraw interaction
  fragility under full optimization. So the earlier "hang/crash" was this same content STOP,
  handled badly by the Release build (NOT a separate memory bug as first guessed).
So: **editor compiles + launches correctly; the blocker is incomplete dev content** (a
content-curation task on the project's own unfinished assets, distinct from build/toolchain).

### Option 2 attempted (make content STOPs non-fatal) -> uncovered the REAL bug: heap corruption
- Patched `mw4\Libraries\Adept\ResourceImagePool.cpp:385` (texture-not-found): first tried
  `continue` (skip) -> downstream **access violation** (missing texture deref'd later); then a
  **placeholder-substitute** (`file_name="content\\textures\\01AACA1.tga"`) -> got PAST the
  content STOPs. Both are tagged `[editor degraded-mode]` in-source.
- With content handled, the editor returns to the original symptom: **main UI thread spins at
  100% CPU, not responding**. EIP sampling (`Wow64GetThreadContext`) located the spin in a tight
  loop inside **`SysWOW64\ntdll.dll`** heap code (~`0x76F73E04`) = **walking a corrupted heap
  free-list**. ROOT CAUSE = **heap corruption** (buffer overrun / double-free / use-after-free)
  in the editor; it manifests non-deterministically as the ntdll heap-loop HANG or an AV, and
  shifts with optimization (Release vs Profile) -> exactly the observed behavior.
- To actually fix: enable **PageHeap** for `MW4Ed2.exe` (registry `Image File Execution
  Options\MW4Ed2.exe` GlobalFlag=0x02000000; needs admin) to trap the corrupting write at its
  source with the Profile build's symbols, then fix the overrun. This is a deep memory-bug hunt
  in old code (the 2003 editor never hit it on XP / with complete content).
- NOTE: `ResourceImagePool.cpp` placeholder patch is a degraded-mode hack; revert if not wanted.

### PageHeap hunt (in progress) — corruption confirmed, but a Heisenbug to pin down
- Enabled full PageHeap via `build-env\mw4ed2-pageheap.reg` (IFEO GlobalFlag=0x02000000,
  PageHeapFlags=0x3; off-switch in `mw4ed2-pageheap-off.reg`). Confirmed active (editor mem
  45->94 MB). Built `pro.bin\MW4Ed2.map` (added `/map` to the Profile link in MW4GameEd2.dsp,
  preferred base 0x00400000) for address->function resolution.
- **With PageHeap + NO debugger: editor reliably AVs (0xC0000005) at ~1 s** = the overrun caught
  at the corrupting write (the goal). **But under my PowerShell debug loop it will NOT reproduce**
  (many attempts, even with `_NO_DEBUG_HEAP=1`): attaching perturbs heap/timing so the editor
  takes a different path and exits without faulting -> classic heap-corruption Heisenbug.
- Conclusion: pinning the exact corrupting instruction needs a REAL debugger that holds heap/
  timing steady. Cleanest: install **Debugging Tools for Windows** (`cdb`) and run
  `cdb -g -G -c "kb;q" -o MW4Ed2.exe` with PageHeap on -> one-shot symbolicated faulting stack.
  Alt: WER LocalDumps (HKLM) -> parse the minidump's exception stream for the faulting EIP ->
  map via `MW4Ed2.map` (uncertain: GameOS's UEF may pre-empt WER). The corruption is in the
  content-load path (~1 s, texture/sky loading).
- Working dir = proven runtime DLLs from the game deploy (NO `imagehlp`/`dbghelp`) + editor
  tool DLLs (`ctcl`/`ijl10`/`Resources`/`Language.oeg`) + MFC + our `MW4Ed2.exe`; the bulk
  (`Content\`, `resource\`, `hsh\`) are **directory junctions** to the dev tree
  `Gameleap\mw4` (no 5.5 GB copy). Same `DWM8And16BitMitigation` compat shim as the game.
- **Content data fix needed to launch:** `Content\skies.build:109` declares
  `File=Skies\reduex_sky\reduex_night.erf`, but the night-sky erf was misnamed
  `Reduex_sky_night.erf`. The editor (LAB_ONLY, asserts on missing files) STOPped on it.
  Fixed by adding `reduex_night.erf` (copy of `Reduex_sky_night.erf`) in
  `Gameleap\mw4\Content\Skies\Reduex_sky\`. (A dev-content naming inconsistency, not a
  build/toolchain problem — the editor itself builds+runs correctly.)

---

## ✅ STEP 8: Editor RUNS — root cause was DirectInput, NOT heap corruption (2026-06-22)
Installed **Debugging Tools for Windows** (`cdb` at `C:\Program Files (x86)\Windows Kits\10\
Debuggers\x86\cdb.exe`) and got the first fully-symbolicated faulting stack. **This overturned
the STEP 7 hypothesis:** the crash is NOT content-load heap corruption — it is a **modern-Windows
DirectInput init cascade** during `CMInstall` (GameOS), reproducible and deterministic:

1. **Joystick enumeration heap-corrupts in modern `dinput.dll`.** `CMInstall` ->
   `CMCreateJoysticks` -> `wEnumDevices(DIEDFL_ATTACHEDONLY)` -> `CMCreateJoystick7`
   (`ControlManager.cpp`). On Win11 the legacy **IDirectInput7** path, when it `Release()`s a
   phantom/virtual HID "joystick" (0-axis "misreported device" at line ~602), corrupts the heap
   inside `dinput.dll`. Surfaces as `RtlFreeHeap` AV (normal heap) or, under full PageHeap, as a
   guard-page over-read in `dinput!DIWdm_InitJoyId` (`[esi+0x44]`). **The PageHeap "AV at ~1s"
   from STEP 7 was THIS, mis-attributed to content loading.** The game survives by heap-layout luck.
   - **FIX (source):** `CoreTech\Libraries\mfcplatform\MFCPlatform.cpp` `InitGameOS` now sets
     `gDisableJoystick=1` (editor/`Platform_MFC` only; the game uses a different platform path and
     is unaffected). Chosen over the `/gosnojoystick` switch because in **LAB_ONLY** builds
     `WinMain.cpp:~398` shows a help dialog and **exits** if any leftover `/gos` token remains on
     the command line — fragile (e.g. cdb perturbs the cmdline -> trips it). Tagged `[editor modern-Windows fix]`.

2. **Mouse `SetCooperativeLevel(NULL, FOREGROUND|NONEXCLUSIVE)` fails 0x80070006.**
   `CMCreateMouse` (`Mouse.cpp`) resolves the coop window from `hWindow`, which is **always NULL**
   in the editor at `CMInstall` time: the editor passes `pFrame->GameWnd->m_wndView.m_hWnd`, but
   `InitSecondaryWindows` (`MainFrm.cpp:173`) only `new`s `CGameFrame` and never `Create()`s it, so
   `CGameFrame::OnCreate` (which builds `m_wndView`) hasn't run. XP tolerated a NULL coop window;
   modern DirectInput rejects it -> fatal PAUSE. (Keyboard has no coop-level call, so it survived.)
   - **FIX (source):** `Mouse.cpp` `CMCreateMouse` falls back to `GetForegroundWindow()`/
     `GetDesktopWindow()` with `BACKGROUND|NONEXCLUSIVE` when the resolved window is NULL; the game
     path (valid `hWindow`) is unchanged. Tagged `[editor modern-Windows fix]`.

Rebuilt **`MW4GameEd2 - Win32 Profile`** (recompiles GameOS + MFCPlatform, relinks; 0 errors) ->
deployed `pro.bin\MW4Ed2.exe`+`.pdb` to `C:\VWE\firestorm\MW4Editor`. With both fixes + the content fixes
below, the editor **launches to a live, responsive, idle (0% CPU) main window** ("MechWarrior 4
Mission Editor - 05.07.00.00", ~70 MB) — the old "100% spin / not responding" is GONE. Launch with
just `-armorlevel 3` (no `/gos` flag needed).

### Content-curation fixes (FireStorm WIP dev tree, in `Gameleap\mw4\Content`)
The editor enumerates+loads EVERY mission/map at startup (`ObjMan->BuildResources()`) and STOPs
fatally on the first defect. Fixed (all additive/non-destructive unless noted):
- **`_backup` dev-copy dirs** (files named after the original, not the dir): created the missing
  `<dir>.build`/`<dir>.instance` (page `[<dir>]`, unique resource targets):
  `Maps\Colsm01_backup`, `Maps\StormCanyonSiege_Backup`, `Missions\Coliseum_backup`.
  (Editor compiled these into `resource\maps\colsm01_backup.mw4` etc. — confirms they're valid.)
- **Dangling map refs:** `Missions\EditorTemplate\EditorTemplate.instance` `Map=` arctic01 (exists
  nowhere) -> `arctic04`; `Missions\s1s2\s1s2.instance` `Map=` lunar02 -> `lunar01`.
- **Double-nested user mission:** `Missions\PhoenixPalaceSTB\PhoenixPalaceSTB\*` moved up one level
  (the files already referenced the top-level path); then its `.instance` `Model=` pointed at
  `dropzones\hill.contents` (page `[droppoints]`) -> fixed to `phoenixpalacestb.data` (page `[gamedata]`).
- Scans confirmed scope is small: 0 remaining mission page-name mismatches; 1 dangling map ref
  (fixed); arctic01/lunar02 are genuine content gaps absent from ALL installs (dev Resource\Maps
  has 46 .mw4 — MORE complete than game-deploy/knowngood's 26 — so launch folder/junctions are correct).

### ✅ 3D viewport FIXED via DDrawCompat (2026-06-22)
Root cause pinned with temporary diagnostics in `SetupMode`/`DirectDrawCreateAllBuffers`
(`DXRasterizer.cpp`, since reverted): every surface (front/back/Z), the clipper, and the
`IDirect3D7` object create fine with a valid `hWindow`, but **`IDirect3D7::CreateDevice` fails
with `0x88760082 = DDERR_INVALIDOBJECT`** for BOTH the hardware HAL (`IID_IDirect3DTnLHalDevice`
-> `IID_IDirect3DHALDevice`) and the Blade software device — i.e. **Win11's legacy DirectDraw does
not support creating a windowed Direct3D7 device** on an offscreen backbuffer (the game avoids
this by using a fullscreen flip-chain). Not a code-flag issue.
**Fix:** dropped **DDrawCompat** (narzoul, v0.7.1, `ddraw.dll`) into `C:\VWE\firestorm\MW4Editor\` — a
drop-in ddraw/d3d7 reimplementation. With it, `CreateDevice` succeeds and the **Game View/Overview
render** (user-confirmed). Source kept at `build-env\ddrawcompat\ddraw.dll`; `deploy-editor.ps1`
now copies it. The standalone game (`C:\VWE\firestorm\MW4`, fullscreen) is unaffected and does NOT use it.
**The editor is now fully working: launches, edits, and renders the 3D viewport.**

### ✅ Open-Mission dialog now lists ALL source missions (2026-06-22)
Symptom: editor's File->Open showed only **5** missions. Cause: `COpenMissionDlg::OnInitDialog`
(`OpenMissionDlg.cpp`) listed `Resource\UserMissions\*.mw4` (compiled/published user-mission
packages) — only 5 exist in the dev tree (and **0** in the game deploy `C:\VWE\firestorm\MW4`, so co-locating
the editor with the game would have shown ZERO, not more). But `CMainFrame::LoadMission` loads from
`Content\Missions\<name>\<name>.Instance`. **Fix:** changed the dialog to enumerate
`Content\Missions\*` subdirectories that contain a matching `<name>.instance` -> now lists all **51**
source missions. Tagged `[editor data fix]`. (Note: `INVALID_FILE_ATTRIBUTES` isn't defined in the
VC6 SDK; used the literal `0xFFFFFFFF`.) This made the requested "move editor into the game-binaries
folder" unnecessary — the editor already junctions the dev tree (`Gameleap\mw4`), the most complete
data (51 missions, 46 maps); the game deploy `C:\VWE\firestorm\MW4` actually has **0** user missions, so it
would have shown FEWER.

**Second half of the fix — actually LOADING the listed missions (`EditorApplication.cpp`
`LoadMissionResources`):** listing wasn't enough. Opening any non-user mission STOPped with
`Can't open <garbage>`. Cause: `LoadMissionResources` hardcoded the resource path as
`"Resource\\User"` + (name after first `\`), i.e. only `Resource\UserMissions\<name>.mw4` (the 5
user missions), and produced a garbage path (bad `strchr+1` pointer) for other name forms. But
**stock** missions compile to `Resource\Missions\<short>.mw4` with abbreviated lowercase names
(`CentralPark`->`cpark.mw4`, `Frostbite`->`fbite.mw4`) that aren't derivable by transform — the
short name is declared in each mission's `.build` (`[resource\missions\X.mw4]`). Fix: extract the
bare mission name, prefer `Resource\UserMissions\<name>.mw4`, else read the resource path straight
out of `Content\Missions\<name>\<name>.build`. Verified headlessly via `-report` (which also calls
`LoadMissionResources`): Coliseum -> 248 entities, CentralPark (cpark.mw4) -> 1709 entities, no STOP.
All 51 missions are now openable; the 5 user missions still work. Tagged `[editor data fix]`.

### (historical) Editor INTERACTIVE; the (now-fixed) windowed 3D viewport blocker
Verified via window enumeration: at startup the editor creates ALL its MDI children
(**Game View, Object Manager, Overview Window**) and presents a modal **"Open Mission"** dialog
(normal startup; main frame correctly disabled behind it) — the user opens a map from there and
the UI is usable. (Headless test runs "exit code 1" simply because nothing clicks that modal
"Open Mission" dialog — NOT a crash. Earlier "idle/Responding" readings were the process sitting
on that dialog; `Responding=True`+0% CPU cannot distinguish a working UI from a modal dialog —
enumerate windows to tell them apart.) The only real failure is **live 3D rendering**: selecting
**Game View** (and the Overview render once a mission draws) STOPs:
**`No software rasterizer available`**
(`DXRasterizer.cpp:2488`). Path: `DirectDrawCreateAllBuffers` -> `SetupMode` tries windowed
**hardware D3D7** (primary surface + clipper on `hWindow` + VRAM 3D backbuffer) which fails on
Win11's legacy DirectDraw, then falls back to **Blade software** (555 system-mem surface,
`SetupMode` line ~1816/1835) which also fails -> STOP. This is the **windowed legacy DirectDraw/
D3D7-on-Win11** problem — the SAME core incompatibility the game avoids by running **fullscreen-
exclusive** (+ `DWM8And16BitMitigation` shim). It is a distinct, deeper rendering-compat task from
everything above (which was input + content). To pursue: capture the exact failing `HRESULT` by
reproducing interactively (open map -> Game View) under cdb; candidate directions = a DDraw
compat layer (e.g. DDrawCompat), forcing a supported windowed surface format, or hosting the
viewport via a different present path. NOT yet solved.

### ✅ Deployment finalized (2026-06-22)
- **PageHeap REMOVED** (debug aid only): ran `reg import C:\VWE\firestorm\build-env\mw4ed2-pageheap-off.reg`
  ELEVATED (UAC) -> HKLM IFEO `MW4Ed2.exe` key gone. The real-name `MW4Ed2.exe` now runs directly
  (no longer AVs at ~2s). The `MW4Ed2_nph.exe` copy remains as a page-heap-immune fallback.
- **`deploy-editor.ps1` updated:** sources the **Profile** build (`pro.bin`, the working config) +
  `.pdb`; writes `run-editor.bat` (`MW4Ed2.exe -armorlevel 3`, no `/gos`); sets the DDraw shim;
  warns if PageHeap IFEO is present. A fresh deploy is runnable to the editor UI.
- **Launch:** `C:\VWE\firestorm\MW4Editor\run-editor.bat` (or `MW4Ed2.exe -armorlevel 3`). Pick a mission in
  the startup "Open Mission" dialog. Everything except the live 3D render works.
- Expect more WIP content STOPs only if exercising missions/maps not loaded at startup.
- (Optional) Build Debug/Armor configs (need `dbg.bin`/`arm.bin` output dirs).

## ✅ STEP 9: Editor runs IN PLACE in the data tree — no separate `MW4Editor` dir (2026-06-23)
The editor's only job is editing the source content/resources in `Gameleap\mw4`, so the separate
`C:\VWE\firestorm\MW4Editor` deploy (which just junctioned back to `Gameleap\mw4`) was removed. `deploy-editor.ps1`
now **installs the editor into `Gameleap\mw4`**: copies `MW4Ed2.exe`(+pdb) + DDrawCompat `ddraw.dll`,
renames the two DDraw-breaking DLLs (`dbghelp.dll`/`imagehlp.dll` → `.disabled`), sets the DWM shim on
`Gameleap\mw4\MW4Ed2.exe`, and writes `Gameleap\mw4\run-editor.bat`. Verified: editor launches in place
with the full UI + DDrawCompat viewport (Game View/Overview/Object Manager/Resources).
- **Conflict handled:** DDrawCompat `ddraw.dll` is **fatal** to `MW4pro.exe`'s fullscreen build init
  (`[DDrawCompat] Fatal Error`). Since the resource builder runs from the same `Gameleap\mw4`,
  `build-resources.ps1` now moves `ddraw.dll` aside (`.buildaside`) for the build and restores it in a
  `finally` (verified: build exit 0, ddraw restored).
- **Game deploy unaffected:** `deploy-mw4.ps1` adds `ddraw.dll` to its skip list, so DDrawCompat + the
  editor exe/pdb/launcher/`.disabled` DLLs never leak into `C:\VWE\firestorm\MW4` (verified: 682 files / 868 MB,
  58 packages, fullscreen native DDraw). Old launch path `C:\VWE\firestorm\MW4Editor\...` is superseded by
  `C:\VWE\firestorm\Gameleap\mw4\run-editor.bat`.

## 📋 Reference: adding a new 'Mech chassis (documented 2026-07-01)
Full workflow in **`ADDING-A-MECH.md`** (repo root). Key facts: every registration point is
marked `// MSL ADD MECH` (~41 files); Mech IDs are positional/alphabetical parallel-array
indices spanning code + tables + shell scripts; `BTFrstrm\scriptaddmech.xls` is a generator
workbook for the per-mech rows; mechlab hardpoints live in the mech's `.damage` (`OmniSlots=`
etc.), default loadout in `.subsystems`; 2D HUD/MFD bmps load loose from `hsh\`; a new chassis
needs MW4.exe rebuild + resource repack + redeploy. `Finished HUDS from J&J\` = pending MFD/
radar art for ~13 chassis.

## 📋 Reference: FS507D_20161015 release drop analyzed (2026-07-01)
`FS507D_20161015\` (repo root, **gitignored** — not part of the mirror) = a later shipped
LAN-center release of this game (postinstall.bat maps per-machine 10.0.0.x IPs; Mumble refs).
**2026-07-02: release contents DELETED (user holds the zip archive); only
`FS507D_20161015\art-review\` remains** — 800 side-by-side pairs (`*.RELEASE.*` vs `*.OURS.*`,
flattened paths, `manifest.tsv`) of art whose CONTENT differs from our sources, awaiting the
user's review: `DIMS-DIFFER\` 451 (structurally different, e.g. 3state button strips grew
110×105→110×180, btfconsole.tga 800×600 vs 112×112), `PIXELS-DIFFER\` 280 (same format,
repainted), `HSH\` 69 (loose hsh bmps beyond pure depth-reduction; 91 8bpp-reductions of our
24bpp sources excluded). ALL packaged art diffs are shell-UI art (`shellscripts\graphics\**`,
`mechbay\graphics\**`, lobby skins/decals/mechicons) — zero world/mech texture diffs; the
release = a 5.07D UI-art refresh + the ComputerPlayer console script. (Extraction note:
6,469 map/mission-package texture entries are 4-byte cross-package reference stubs, not art.)
Findings vs our tree:
- **Binaries are a 2016 rebuild of (essentially) our source.** Release `MW4.exe` linked
  2016-10-04, Launcher/MissionLang/ScriptStrings/ctcls 2014, but `mw4pro.exe` is the ORIGINAL
  2009-06-14 build, byte-size-identical to our rebuild. `__TIMESTAMP__` banner in MW4.exe:
  ours `Sun Jun 14 01:45:57 2009` vs release `02:45:57` = same MWApplication.cpp within a DST
  hour. Full strings-diff of the exes: all meaningful strings present on BOTH sides (incl.
  `BiggieSizeIt`/`RuleBook`, which our `VehicleInterface.cpp` has). **No evidence of missing
  source.**
- **✅ Degraded-mode placeholder patch made LAB-only (2026-07-02).** Our rebuilt game exe used
  to contain the `01AACA1.tga` placeholder (Adept links into the game); release was clean.
  `ResourceImagePool.cpp` now: `#ifdef LAB_ONLY` placeholder (editor keeps degraded mode),
  `#else` original `STOP(("Texture %s could not be found!"))`. Release+Profile rebuilt (0 err),
  verified placeholder gone from `rel.bin\MW4.exe` / present in `MW4pro.exe`; both deployed
  to `C:\VWE\firestorm\MW4`.
- **✅ `hsh\` mech art synced from release (2026-07-02).** Release hsh was NEWER than our dev
  tree and matches OUR SOURCE'S names (`huddamage.cpp` wants `hud\assassin2/behemoth/
  behemothii`...). Copied the 13 missing files into `Gameleap\mw4\hsh` + deploy (hud/radar
  assassin2+behemoth+behemothii, Mechs behemoth/black hawk/longbow/solitare/victor, decals
  46/47); `Mechs\black hawk.bmp`+`solitare.bmp` upgraded to our 24bpp twins (blackhawk/
  solitaire renames). Did NOT overwrite the ~158 existing bmps that "differ": release ships
  8bpp palette reductions of our 24bpp sources (deploy-mw4.ps1 reduces at deploy anyway).
- **resource .mw4 detail (via `.dep`/package name-set parsing, 2026-07-02):** entry SETS are
  identical for core/textures/all 26 maps/all 29 missions (maps byte-identical ±2B; mission
  payload drift of ±0.1-33KB = ABL/notation re-serialization, not content). All 29 `.nfo`
  byte-identical. **Only real packing deltas, both in props.mw4:** (1) release packs
  `shellscripts\graphics\multiplayer\lobbydecals\decal_46.tga`+`decal_47.tga` — ✅ RECOVERED
  2026-07-02: decoded the #VBD container (dir records = [len][name][FILETIME][origSize]
  [storedSize][offset]; payload base = dword@0x0C) + ported `gos_LZDecompress` (FileIO.cpp:1693
  — LZW, 9→12-bit LSB-first codes, 256=clear/257=EOF/dict@258) and extracted both TGAs into
  `Content\ShellScripts\graphics\multiplayer\lobbydecals\` (32×32 32bpp, verified: exact RLE/
  size/footer + rendered; 46=radiation emblem, 47="331" crest). Next resource repack picks
  them up and closes the props.mw4 gap;
  (2) OUR props.mw4 sweeps in junk `ConLobby.script.new`/`.script.org` backup files from
  `Content\ShellScripts\` (harmless but shippable-junk; note `.script.new` carries MSL ADD
  MECH markers — review before deleting/moving). Release ships user missions s1s1-s1s3
  (.nfo+.tga only, no .mw4 — campaign coop redirects per missionnames.tbl).
- **✅ FULL extraction of every release .mw4/.dep done (2026-07-02)** →
  `FS507D_20161015\extracted\<pkg>\...` (~1.3 GB, 60+ packages, 0 decode failures; extractor =
  the ported gos_LZDecompress + #VBD directory parser; raw-vs-LZ rule per Database.cpp:451:
  stored==orig → raw).
- **✅ Completeness audit (2026-07-02): NOTHING else in the release is absent from our tree.**
  Mapped all ~42k extracted package entries (23,649 unique base source paths after stripping
  `{GameModel}`/`{hint}`/`[page]` qualifiers) against the dev tree by path + whole-tree
  filename index: the only release-assets we lacked were the 13 hsh bmps + 2 lobby decal
  TGAs (recovered) and the JPP console-script revision (superseded by our ConLobby.script.new).
  Residuals: mission root-page pseudo-entries (not files) + 34 `{CampaignInterfacePlug}`
  records = compiled campaign-screen metadata whose `.Campaign` sources we have. (Caveat:
  audit is by existence, not content equality — release re-saved some shared files, e.g.
  decal_00.tga pixels differ.)
- **The FireStorm console script in the release sits under the package entry name
  `shellscripts\ComputerPlayer.script`** (138,990 B, title "BattleTech Console V5.07.D") — the
  release's `ConLobby.script` ENTRY holds a 6,551 B copy of an old CreatePilotModal.
  **2026-07-02 CORRECTION: nobody renamed anything — this is the packer's name↔content skew**
  (see "Resource packer quirks" below): the console SOURCE file in the 5.07D tree was
  ConLobby.script all along, same as ours; the packer stores script contents under alphabetically
  shifted entry names, and the runtime reproduces the same pairing, so it works in-game and only
  looks swapped to extraction tools. **Console script genealogy (oldest→newest):**
  dev `ConLobby.script` ("V5.07", 138,544 B) → release console ("V5.07.D",
  +29 lines: JPP's decal-dropdown feature adding decals 46=BKG/47=FSA, MAX_DECAL_COUNT 17→20)
  → dev `ConLobby.script.new` ("V5.0.7Df", 140,532 B, changelog "06/19/18 AVB": includes the
  JPP decal work PLUS ROWFIELD_TYPE_*/MAIL_SLOTTYPE_* slot framework + tab stops). **Our
  `.new` is the newest console revision anywhere** — the release only added what .new already
  contains. ✅ **RECONCILED 2026-07-02:** `.new` promoted to `Content\ShellScripts\ConLobby.script`
  (140,532 B), `.new` file removed, stale loose deploy copy `MW4\Content\shellscripts\ConLobby.script`
  removed (release also ships no loose copy), props.mw4 fully repacked + redeployed. Verified
  in-game: console title shows "BattleTech Console V5.0.7Df".
- **Release-only config:** `options-game.ini` enables `hardwaremixing=true`, `BiggieSizeIt=1`,
  `RuleBook=1` (our inis don't); ctcl inis use 10.0.0.x (ours 200.0.0.x) + `c:\games\MW4` paths.
- **Release drops all Movies** (~310 MB) and the loose `content\shellscripts\conlobby.script`
  (both deploys load shell scripts from props.mw4; ours ships that one loose 140 KB script —
  check whether a loose script shadows the packed one).

## 📋 Reference: adding a new map/mission (documented 2026-07-01)
Full workflow in **`ADDING-A-MAP.md`** (repo root). Key facts: unlike mechs, NO code changes /
positional arrays / exe rebuild — maps+missions are discovered by enumeration. Map (terrain) =
`Content\Maps\<Map>` (MapCreator.exe tiles) vs Mission (scenario) = `Content\Missions\<Name>`
(authored in MW4Ed2, which clones `EditorTemplate`). Registration: nest mission .build under its
map in `MechWarrior4.build` (manual — editor's AddToBuildFile() call is commented out), MP game
types via loose `Resource\Missions\<Name>.nfo`, `missionnames.tbl` self-row, optional
`InstantAction.Campaign` page for the IA list; user missions (`Resource\UserMissions\*.mw4+.nfo`,
editor-published) are auto-discovered with zero manifest edits. Original toolkit tutorials
(map creation/terrain/NFO/ABL) live in `_UNUSED\Gameleap\EditorDocs\`.

## 📋 Reference: raising the MP player cap 16 → 24 (investigated 2026-06-24; NOT planned)
Captured for the future; **we are not tackling this in the foreseeable future.** Question was
whether `ConLobby.script`'s `MAX_ROSTER_COUNT 16` could be 24. Findings:
- **No engine wall at 16 — the real ceiling is 255.** `Adept::Maximum_Players = 255`
  (`mw4\Libraries\Adept\Application.hpp:139`); every per-player fixed array (`servedConnectionData[]`,
  scoreboards, net-stats, mission-review logs, `pdmg_given/rcvd`) is sized to 255. `connectionID`
  is a `BYTE` (0–255). `Environment.NetworkMaxPlayers` is passed straight to DirectPlay's
  `dwMaxPlayers` (`CoreTech\...\GameOS\Net_Main.cpp:2142`) — no GameOS ceiling; `m_maxPlayers` has
  no hard upper clamp (`MW4Shell.cpp:1803` only clamps *bots* down to the player count).
- **16 is enforced in THREE layers — a script edit alone is insufficient/misleading:**
  1. UI: `Content\ShellScripts\ConLobby.script` `#define MAX_ROSTER_COUNT 16` (already 8→16; gated by
     `USE_O_MORE_PODS`/`g_nMechPodNum`; also clamps vs `MAXTESLA_P` = cafe Tesla count, ≤8).
  2. **Compiled defaults (the real connect gate):** `MW4Shell.cpp:13319-13321` non-coop sets
     `m_maxPlayers=16; Environment.NetworkMaxPlayers=16; m_maxBots=16` (coop branch caps at 9).
     Editing only the `.script` shows 24 slots but the session still rejects the 17th player.
  3. Lobby pods / scoreboard (`hudscore`) / radar UI are laid out for ≤16.
- **To actually reach 24:** raise `MAX_ROSTER_COUNT` **and** the `m_maxPlayers`/`NetworkMaxPlayers`/
  `m_maxBots` defaults (→ rebuild `MW4.exe`), rework lobby/scoreboard/radar layouts, and ensure maps
  define **≥24 drop zones / start points** (many MP maps only have ~16 — a per-map content limit).
- **Why the cap exists (the practical blocker):** replication is ~O(n²) — 24 vs 16 ≈ (24/16)² ≈
  **2.25× host outbound + CPU**; the dictionary/culling netcode was tuned for ≤16. Bandwidth, not
  the data structures, is the ceiling.

### 📐 PLAN: raise MP cap 16 → 32 (drafted 2026-06-24; NOT scheduled)
Concrete plan extending the reference above to **32 players**. Phased; each phase is independently
testable. 32 is safe vs the engine ceiling (255 arrays, `BYTE` connectionID) and has **no 32-bit
player-mask boundary risk** — verified the only `0x1<<id` masks are per-mech *weapon* bits
(`MWEntityManager.cpp:1332`, `NetWeapon.cpp`), not player/connection indices.

**Phase 1 — Code defaults + lobby-param path (rebuild `MW4.exe`, Release + Profile).**
- `MW4Shell.cpp:13319-13321` (non-coop game setup): `m_maxPlayers`, `Environment.NetworkMaxPlayers`,
  `m_maxBots` `16 → 32`. Coop branch `13309/13315-13316` (`=16`/`=9`/`=8`) — leave or bump per design.
- `MW4Shell.cpp:1803-1831` (lobby-driven path): `m_maxPlayers=INTPARM(1)`, `m_maxBots=INTPARM(1)`,
  `Environment.NetworkMaxPlayers = params->m_playerLimit`. No hard upper clamp exists
  (`1808 Max_Clamp(m_maxBots, m_maxPlayers)` only clamps bots down) — but confirm the lobby
  "player limit" control feeds a value up to 32 (param `PLAYER_LIMIT_PARAMETER`, `NetParams.h:53`).
- Grep-audit for any other implicit ≤16 assumptions before rebuild (none found so far; arrays=255).

**Phase 2 — Lobby + in-game UI (repack `props.mw4`). [LOW PRIORITY]**
Cosmetic/usability, not a functional blocker: with only Phases 1+3 done, 32 players can still
connect, spawn, and play — the lobby pods/roster and scoreboard/radar just overflow or clip past
16 (and the host can still set the limit via the param path even if the slider UI caps lower).
Defer this polish until 32p is otherwise proven; do the minimum to make the lobby usable, leave full
pod/scoreboard re-layout for later.
- `Content\ShellScripts\ConLobby.script`: `MAX_ROSTER_COUNT 16→32`, `MAX_TEAMMATE_COUNT 8→16`
  (both under `USE_O_MORE_PODS`/`g_nMechPodNum`); raise the player-limit control's max to 32.
  Note the `MAXTESLA_P` clamp is cafe-only (CTCL Tesla count) and won't block non-cafe play.
- **Biggest UI lift — the mech-selection "pods":** the lobby draws a fixed grid of player/mech pods;
  32 needs a reworked layout (grid size, scrolling, or smaller pods). Same for the team-assignment
  panel (`TEAM_MAX_PLAYERS`, `team_max_plyrs` @ ConLobby:328).
- Multiplayer lobby scripts (`Content\ShellScripts\Multiplayer\*` incl. `lobby_listbox.script`,
  `HostLobbyMission.script`): roster list sizing + player-limit slider range.
- In-game **scoreboard** (`hudscore.cpp`) and **radar** (`GUIRadarManager.cpp`): data is fine
  (255 arrays) but the on-screen score table / legend is laid out for ≤16 → rework for 32 (scroll
  or two columns). `MP_Review.script` top-down review screen likewise.

**Phase 3 — Map content (per-map; the gating gameplay task).**
- Every MP map used at 32p must define **≥32 drop zones / start points**. Most stock maps have ≤16;
  players 17-32 silently fail to spawn otherwise. Audit + author extra drop points in the editor
  (now runnable in place from `Gameleap\mw4\run-editor.bat`). This is per-map, independent of code.

**Phase 4 — Netcode / performance. [LIKELY FINE on modern hardware/networks]**
- Replication is ~O(n²): 32 vs 16 ≈ (32/16)² = 4× host outbound + CPU. This was the limiting factor
  in 2002 (dial-up / early broadband), but **modern broadband upstream + CPU absorb 4× easily** — raw
  bandwidth/CPU is no longer expected to block 32. Demote from "real ceiling" to a verification item.
- Residual (non-bandwidth) thing to check when implementing: any **fixed-size netcode buffers / per-
  frame packet or replication caps** hardcoded for ≤16 (distinct from bandwidth; e.g. packet-count or
  buffer limits). Audit during Phase 1; bounded by the 255-array design, so likely none. Still do a
  live load test to confirm, but don't expect tuning to be needed.

**Verification:** (1) rebuild → headless `-report`/launch sanity; (2) host a game, confirm 32 slots
in lobby + 17th–32nd players connect (`Environment.NetworkMaxPlayers`/DirectPlay `dwMaxPlayers`);
(3) confirm spawns on a 32-drop-zone map; (4) load/bandwidth test; (5) check scoreboard/radar/review
UI legibility. **Primary remaining work:** per-map drop-zone authoring (Phase 3) is now the main
gating task. UI (Phase 2) is low-priority non-blocking polish; bandwidth/CPU (Phase 4) is expected
fine on modern hardware — just verify fixed-size netcode buffers and run one live load test.

## 📋 Branch `tbaud`: `-tbaud` switch for high-speed replica RIO boards (2026-07-12)
A community user is building a **replica of the ORIGINAL RIO cockpit board with a
high-speed UART**. `-trio 1` was unsuitable (it switches the whole wire protocol: length
table B with id bytes, Ack2/Nak2 retransmit scheme, eject lamp 61→31 — not just baud).
New game switch **`-tbaud <rate>`** (validated 9600–921600) forces the COM1 baud while
keeping old-RIO (type 0) protocol behavior byte-identical:
- `mw4\Code\MW4\CRIOMAIN.CPP`: new `g_dwRIOBaud` (0 = default); `SetupConnection` applies
  the override after the type/console-based default; `SetupComm` buffers 2/2 → 1024/1024
  only when the override is active (defaults untouched for deployed pods).
- `mw4\Code\MW4Application\MW4Application.cpp`: parses `-tbaud ` next to `-trio `.
- RIO serial facts (for future work): COM1 is hardcoded (`OpenConnection(1)` in the
  CRIOMAIN ctor, reached via `VehicleInterface::ExecutionStateEngine::InitializeClass` —
  runs at MISSION/vehicle init, NOT in the shell); the plasma score display is a separate
  `C232Comm` link on COM2 (CPlasma, 9600), untouched by `-tbaud`.
- Rebuilt Release + Profile (0 errors each); fresh `rel.bin\MW4.exe` deployed to `MW4\`.
  Verified: game boots clean with `-tbaud 115200`; COM1 confirmed NOT opened at the shell
  (probe on this box's com0com pair COM1↔COM11 stayed silent/free), so byte-level
  verification needs mission entry + the real board (user's bench).
- ⚠️ **Encoding hazard (hit during this work, repaired pre-merge):** many mw4 sources carry **EUC-KR/CP949
  Korean comments** (`CRIOMAIN.CPP`, `MW4Application.cpp`, `MWApplication.cpp`, ...). Editing
  tools that decode/re-encode as UTF-8 silently mangle every Korean comment in the file.
  Edit such files byte-safely (Latin-1 round-trip / byte patch) and check the diff touches
  ONLY intended lines before committing.
- `run-firestorm` driver: `game-start` now takes optional quoted extra args. PS 5.1 quirk:
  call it via `-Command "& '...driver.ps1' game-start '-tbaud 115200'"` — `-File` binding
  rejects dash-leading positional args, and `Start-Process -ArgumentList` doesn't re-quote.

## 📋 Branch `mfdsplit`: mech loadouts, time list, build fixes (2026-07-18)

### Mech loadout changes
- **Battlemaster IS** (`Content\Mechs\Battlemaster\battlemaster.subsystems`): replaced lone
  MediumPulseLaser default with full stock IS loadout — PPC (Special2), 6×ML (3 RT, 3 LT),
  2×MG (LA, 200 rds), SRM6 (Special1, 15 rds), all GroupIndex=1 except SRM6 (group 2).
- **Battlemaster Clan 2C** (`battlemaster2c.subsystems`): ER PPC (RA), 6×ER ML (3 RT, 3 LT),
  2×Clan Gauss (LA, 16 rds each), Clan SSRM6 (Special1, 15 rds).
- **Behemoth / Behemoth2** (`.subsystems`): moved Gauss rifles from weapon group 3 → group 1
  (3 occurrences each; gauss rifles now in group 1 alongside other main weapons).

### MP time-limit list fix (ConLobbyMission.script)
- Expanded from 9 → 18 entries (1–15, 20, 25, 30 min); `max_displayed=10` so dropdown scrolls.
- Fixed bare `else if` (missing braces) that caused a null-reference crash in the console lobby
  when the commit was merged from `main`. This was the root cause of the `aa500be7` regression.
- Fixed `i==5` vs `i==6` max_displayed assignment for time vs radar dropdowns.

### Resource builder fix (build-resources.ps1) — VM / generic adapter support
- Builder now always runs with `-window` (windowed mode + DDrawCompat). Fullscreen native DDraw
  fails on VMs with generic Microsoft display adapters (no hardware DDraw); windowed DDrawCompat
  works on all tested configs including VMs.
- dgVoodoo2 D3D DLL interceptors (`D3D8.dll`, `D3D9.dll`, `D3DImm.dll`) in the working dir
  silently break the builder (it exits 0 without building anything). These files were left over
  from the abandoned dgVoodoo2 experiment (commit `87e25677`) — removed from repo (`git rm`).
  The build script also moves any such files aside defensively via the `$dgvMoved` block.
- Removed all dead code for fullscreen fallback, bitdepth patching, ddraw move-aside logic.
- `dgVoodoo.conf` / `dgVoodooCpl.exe` also removed from `Gameleap\mw4\` (no longer in use).
- Expected behaviour on VM: a "Hardware Error: not compatible with MechWarrior 4" dialog appears
  at the end of the build — this is a GameOS hardware-capability warning, NOT a fatal error.
  Click OK; the .mw4 packages are built correctly regardless.

### Source tree Korean → English translation + UTF-8 cleanup (2026-07-18)
- Translated all EUC-KR/CP949 Korean developer comments to English across **84 source files**
  (~876 lines). Zero Korean bytes remain outside intentional font-table headers (D3FFontEdit2/
  fontedit `all.h` etc.). Files are now clean UTF-8 (no BOM), safe for any modern editor.
- Notable functional translations: `recscore.cpp` body-part return values and kill-announcer
  format strings (gameplay-visible on Korean Windows; now English on all systems); `nonmfc.h`
  assert dialog; GosView profiler `킪` → `us` (microseconds).
- Latin-1 chars converted to proper UTF-8: © in 3dsmax4/Maxscrpt headers (82 files), ® in
  `gosHelp/Remote.cpp`, · bullet points in `ai command.hpp`, Û/ß in AnimationSuite headers.
- Font table `*.h` files (`D3FFontEdit2/`, `fontedit/`) intentionally left as-is (raw byte
  values in C array data, not text).
- `korean_diff.html` (side-by-side GitHub-style diff, ~873 KB) committed as documentation.

### Language DLL: English version from source
- `Gameleap\mw4\Language.dll` and `MW4\Language.dll` replaced with the freshly compiled English
  build from `Language - Win32 English` config (`Language.dsp`). Fixes Korean button labels in
  the GameOS exception/crash dialog (`??? ??...` / `??` / `???` → More Details / Continue / Exit).
  The old binary was the original Korean build from the 2009 dev machine.

### mw4print v2.0 additions
- **MySQL export** (`dbexport.h`/`dbexport.cpp`): late-bound runtime load of `libmysql.dll`
  (no compile-time MySQL SDK needed). Exports match data (match, player_result, pvp, optional
  event tables) to an external MySQL server after each print job. Schema in `db_schema.sql`.
  Config via `mw4print.ini [MySQLExport]`; UI via File → Database Settings (Ctrl+D).
- **Configurable banner text**: File → Banner Setting... edits the bottom-of-sheet URL string
  (was hardcoded `WWW.MECHJOCK.COM`). Persisted to `options.ini [battle tech print] BannerText=`.
- **libmysql.dll** (MySQL Connector/C 32-bit) added to `Gameleap\mw4\` so deploy copies it
  alongside `mw4print.exe`.
- Version bumped to **2.0**, copyright year updated to **2026**.

## 📋 MFD mode 4: right device stagger fix (2026-07-18, eaa5fd3)
Root cause: `CMFD_Device::BeginScene()` cleared BOTH MFD device back-buffers at `sh_step==0`.
But in mode 4 the right MFD flip also fires at `sh_step==1` (= old `sh_step==0` after the
stagger increment), so it presented a just-cleared buffer — only the grid, no channel data.

Fix: `BeginScene()` now only clears/grids the LEFT device at `sh_step==0`. New `BeginSceneRight()`
(added to `render.cpp` / `render.hpp`, called from `WinMain.cpp`) clears/grids the RIGHT device at
`sh_step==1` — one frame AFTER the right flip — so channels 3–4 render into a fresh buffer before
the next flip. Mode 1 is unchanged.

Mode 4 cycle (with stagger):
- `sh_step 0`: radar + left `BeginScene` (clear + grid); no flip
- `sh_step 1`: flip right MFD (shows channels 3–4 from previous cycle) + right `BeginScene` (clear + grid)
- `sh_step 2–4`: channels 0–2 → left device
- `sh_step 5–6`: channels 3–4 → right device
- `sh_step 0` (next): flip radar + left MFD (shows channels 0–2 from previous cycle)

Files: `CoreTech/Libraries/GameOS/WinMain.cpp`, `render.cpp`, `render.hpp`.

## 📋 Linux→Windows development workflow (2026-07-19, 55b9bfc5)
`build-env/sync-to-windows.sh` — rsync script that pushes all source, content, toolchain, and
assets to the Windows build machine at `/vwe/firestorm`. Excludes generated build outputs
(`rel.bin/`, `dbg.bin/`, `*.mw4`, `*.dep`), `.git`/LFS objects, `_UNUSED/`, and the game deploy
dir (`MW4/`). Run from Linux before triggering a Windows build.

## 📋 ddraw.dll removed from repo; build script moves it aside (2026-07-19, 0ceba9c7, 24825ff3)
`Gameleap/mw4/ddraw.dll` (DDrawCompat) removed from git (was LFS-tracked); now lives as
`ddraw.dll.old` in the same dir for reference. `deploy-editor.ps1` installs DDrawCompat as
`ddraw.dll` at deploy time (the editor needs it for its windowed D3D7 viewport).
`build-resources.ps1` moves `ddraw.dll` aside (`.buildaside`) before running `MW4pro.exe`
because **DDrawCompat is fatal to MW4pro.exe in BOTH windowed and fullscreen modes**, then
restores it in `finally`. The same block also defensively covers dgVoodoo2 interceptors
(`D3D8/D3D9/D3DImm.dll`) in case they reappear.

## 📋 Branch `5.1.0b-in-progress`: multiplayer + RIO + source cleanup (2026-07-19)

### ConLobby V5.1.0b1 — Super6 mech rotation (c768f7c4)
Changes from Buddy 'Highlight' Taylor (MCHL), merged manually:
- Console version string bumped to **V5.1.0b1**.
- `ROOKIEMECH` defines expanded from 4 → **6 mechs**: added Archer (ID=1) and Warhammer (ID=62).
- 16-slot default assignments cycle through all 6 Super6 mechs.
- Right-click randomizer expanded from `random(0,3)` → `random(0,5)`.

### CRIOMAIN.CPP — Korean translation + RIO poll timeout scaling (16fca6c4, a712002f)
- Translated all EUC-KR/CP949 Korean developer comments (~35 lines) to English. File re-saved
  as clean UTF-8. CRLF line endings preserved (`* -text` in `.gitattributes`).
  ⚠️ **Encoding hazard:** always read/write CRIOMAIN.CPP as binary (or with an explicit encoding
  codec). Python text-mode `readlines()` silently strips `\r`, causing a 3000+-line git diff when
  every line's `\r\n` becomes `\n`. If that happens, restore CRLF with `re.sub(b'(?<!\r)\n', b'\r\n', data)` in binary mode, then amend the commit.
- Added **`g_dwRIOPollTimeout`** global (default 50 ms). Computed in `SetupConnection` before the
  receive loop using: `clamp(⌈480000/baud⌉, 5, 50)`. Preserves 50 ms at 9600 baud; floors at
  5 ms for 115200 baud. Implemented with explicit ternary — **`min`/`max` are undeclared in this
  translation unit under VC6** (not pulled in by CRIOMAIN's includes); use ternary or `__min`/`__max`.

### 16 pilots + 1 cameraship in multiplayer (f76dc05f)
`MW4Shell.cpp`:
- `CTCL_DefaultHostSetup` (non-coop): `Environment.NetworkMaxPlayers` set to
  `params->m_maxPlayers + (CTCL_GetTeslaCountAll() - CTCL_GetTeslaCount())`.
  The delta = camera-only seats (Tesla seats not assigned to pilots), reserving one extra
  DirectPlay slot per cameraship so the 17th connection isn't rejected.
- `SetNetworkMissionParamater / PLAYER_LIMIT_PARAMETER`: same formula applied at runtime when
  host changes the player limit. `gos_NetServerCommands(gos_Commend_UpdateMaxPlayers)` still called;
  `break` must be present in this case (was accidentally dropped once — fall-through to
  `JOIN_IN_PROGRESS_PARAMETER` corrupts `m_joinInProgress`).
- COOP branch unchanged (capped at 9+bots; no camera seat needed there).

`ConLobby.script`: launch guard `nTempPlayerCount > 16` raised to `> 17` so the cameraship
connection doesn't trigger "Too many player/bots".

### hsh/ BMP canonical renames (5813aeb6, 2026-07-23)
All `hsh/MFD/*.bmp` and `hsh/Mechs/*.bmp` filenames reconciled against the canonical stems
expected by game code. The engine loads MFD images via `huddamage.cpp` `texturename[]` array
(lowercase, no spaces) and Mechs portraits via `GetLocString` DNL strings (mixed-case with
spaces). Any mismatch = silently missing image at runtime.
Key renames:
- `hsh/MFD/assassinii.bmp` → `assassin2.bmp` (matches `texturename[]` canonical)
- `hsh/Mechs/battlemasteriic.bmp` → `battlemaster iic.bmp`
- `hsh/Mechs/mad cat mk.ii.bmp` → `mad cat mkii.bmp`
- `hsh/Mechs/behemoth ii.bmp` added (was absent)
Most other files in both directories are LFS pointer updates only (content unchanged).

### RookieMission configurable defaults via options.ini (5813aeb6, 2026-07-23)
CTCL (console) arcade mode has a "Rookie Mission" quick-launch that previously hardcoded all
game params. Now all 13 params are overridable from an `[RookieMission]` section in
`options.ini` (read by `CTCL_SetCDSP` at startup):
- `MW4Shell.cpp`: added 14 `g_` globals (`g_szRookieMission`, `g_nRookieGameType`, and 12
  numeric params); registered as `gosScript_RegisterVariable` in `StartUp`/`ShutDown`;
  `CTCL_SetCDSP` reads the `[RookieMission]` page via `NotationFile` and populates them.
  Defaults: `"ScarabStronghold - Attrition"`, GameType=2 (Attrition), UnlimitedAmmo=1, all
  others zero. `g_nRookieTimeLimit=-1` means "use the server's current time setting".
- `ConLobbyMission.script`: all hardcoded values in `MAIL_SET_ROOKIE_MISSION` handler replaced
  with `$$g_szRookieMission$$` / `$$g_nRookieXxx$$` references.
- Requires rebuild: `MW4.exe` (Release + Profile).

### Mechlab turn rate label (5813aeb6, 2026-07-23)
`StringResource.rc` `IDS_ML_CH_TURNRATE`: "Turn Rate (Degrees/Sec.):" →
"Turn Rate (Top Speed Rad/Sec):" to match the actual `.data` field semantics
(`TopSpeedTurnRate` is in rad/s at top speed, not deg/s).
Requires rebuild: `ScriptStrings.dll`.

### BTFrstrm design documentation (840bc96c, 2026-07-23)
Two Word documents added to `BTFrstrm/`:
- `MechDependencyTree.docx` — dependency relationships between mech chassis/variants.
- `Special_Zones.docx` — documentation of special zone types used in maps/missions.

### mech_loadouts.md: MechEditor data model (0344418a, 2026-07-23)
`BTFrstrm/mech_loadouts.md` extended with a full reference section documenting the
MechEditor web app (`/home/rich/Repositories/MechEditor/mech_editor.py`, localhost:8765):
every `.data`/`.instance`/`.subsystems` field parsed, conversions performed (m/s ↔ kph,
rad/s ↔ deg/s), and `hsh/` naming rules for MFD/Mechs/HUD/Radar images. Canonical
reference for future mech data work.

### Load File autoconfig stabilization + docs update (2026-07-24)
End-to-end Load File flow for the console lobby was stabilized and verified with a
full-delta regression INI (all mission options + all 16 slots changed away from rookie
defaults), then round-tripped back via the Default button.

Key fixes in `ConLobby.script` / `ConLobbyMission.script`:
- Eliminated first-click vs second-click drift by moving auto-load to a dedicated
  deterministic mission path and preventing redraw-time decal mutation.
- Corrected decal handling: map INI decal IDs to lobby dropdown indices, clamp invalid
  indices, and show actual decal IDs in labels.
- Corrected mission+map sequencing: game type now rebuilds map list first; mission name
  then resolves against that list (with fallback to map index 0).
- Fixed option-state application so first click applies all options (visibility/weather/
  time/radar/heat/friendly fire/splash/unlimited/jam/advance/armor) without needing a
  second click.
- Fixed Weapon Jam checkbox visibility refresh when set via Load File (UI now re-inits
  correctly when heat/advanced states are applied).

New supported autoconfig key:
- Added `NoReturn=0|1` under `[mission]`:
  - parser + script variable in `MW4Shell.cpp` (`g_nAutoNoReturn`),
  - application in `ConLobbyMission.script` (`RESPAWN_LIMIT_PARAMETER`),
  - docs + sample INIs updated.

Docs updates:
- `BTFrstrm/autoconfig-file-spec.html` corrected for real behavior:
  - missing mission keys/section apply defaults (not current UI values),
  - missing `MissionName` now falls back to first map for that game type,
  - added `NoReturn` semantics and examples.

## 📋 STEP 10: Four-monitor MFD bring-up, display diagnostics, and the native-DirectDraw
## exclusive-mode wall (2026-07-25)

A long, dense session on the new 4-monitor bench (`MR_new`: AMD FirePro W4100, 4 outputs,
Win10). Everything below is empirical — measured on real hardware, not inferred.

### The root problem that made all of this hard
`CHSH_Device::InitFirst` / `InitSecond` (`CoreTech\Libraries\GameOS\render.cpp`) **discarded
every single `HRESULT`** (`SetCooperativeLevel`, `SetDisplayMode`, `CreateSurface`,
`GetAttachedSurface`, `QueryInterface`, `CreateDevice`) and **returned `true`
unconditionally**. A panel that failed to open produced no error, no crash and no log entry —
the monitor simply stayed on the desktop. SPEW is compiled out of shipping builds, so there
was no way to see any of it. **Fixing that visibility was what unblocked the whole session.**

### ✅ Diagnostics added (KEEP THESE — they are why everything below was findable)
- **`gos-displays.txt`** (written next to the exe by `VideoCard.cpp` `LogDisplayDevices()`):
  `NumDevices`/`NumHWDevices`/`NumMonitors`, every DirectDraw device + `hw_rasterization`,
  the role assignment (`FullScreenDevice`/`g_nNonDualHead`/`g_nDualHead`/`g_nDualHead2`/
  `g_nMFD1`/`g_nMFD2`), `-tmon` APPLIED/REJECTED per slot, and the decisive
  "mode 4 requires BOTH mfd1 and mfd2" line.
- **Per-call HRESULT logging** in `InitFirst`/`InitSecond` via `HSH_LogInit()` / `HSH_CheckHR()`
  / `HSH_HRName()` (27 `DDERR_*` codes decoded by name). Appends to the same file; each line is
  written and flushed individually so **the log survives a crash**.
- **`gos-fps.txt`** — new **`-fps`** switch (`WinMain.cpp` `GOS_LogFrameRate`, hooked onto the
  existing `frameRate` global). Per-second: frames, avg fps, **5% low**, worst frame in ms,
  and a count of frames over 2x average ("hitches"), plus a **session summary** at exit with
  true whole-session 1% / 0.1% lows (computed from a 0.5 ms-bucket histogram). Works in
  **Release** — the engine's own `AddDebugData("FrameRate")` is `#ifdef LAB_ONLY`
  (`MWMission.cpp`) so it only exists in `MW4pro.exe`. Off unless `-fps` is given (a global
  read + branch when absent, no file created).
  - ⚠️ **Lesson from the first real run: the instrument was measuring itself.** The original
    version wrote one line per second straight to the file. The frame time is recorded
    *before* the line is emitted, so the `WriteFile` cost landed in the **next** frame —
    producing exactly one inflated frame every second (median worst-frame 24.4 ms against a
    16.7 ms vsync interval, in 82% of seconds). Output is now buffered in memory and flushed
    only when full or at exit (`atexit`), so a normal session performs no writes while
    running. Trade-off: a hard crash loses the un-flushed tail — `gos-displays.txt` is the
    crash-survivable log, this one is a measurement instrument.
  - Also fixed: a "1% low" over 60 samples/sec degenerates to `nFrames/100 = 0` → clamped to
    1 → literally the worst frame restated, so the column was redundant. Per-second is now a
    5% low (worst 3-of-60); true 1% / 0.1% lows are in the session summary. And a single
    frame longer than a second (a level load) no longer spills into following buckets and
    emits a run of bogus one-frame rows.

### ✅ Crash-safety fix (independent of everything else, worth keeping)
`hsh_initialized` was set **unconditionally** after panel init. A failed panel therefore left
null surfaces and a null `IDirect3DDevice7` behind, and the per-frame path called straight
through them. Now: all four `InitSecond` overrides (`CMR`/`CRadar`/`CMFD`/`CMFDRight`) bail on
base failure, `CMFD_Device::InitFirst` reports the real result instead of always `true`, and
`hsh_initialized` is only set when the panels genuinely came up → **the game runs without MFDs
instead of dying**.
- **Crash signature to recognise:** `call [ecx+0x44]` with `ECX=0` = `IDirectDrawSurface7::GetDC`
  on a never-created surface (verified against `build-env\dx7asdk\include\ddraw.h` vtable order).
  Reported as `EXCEPTION : Attempt to read from address 0x00000044`.

### ✅ AppCompat shim — the single most important operational fact
**`DWM8And16BitMitigation` is keyed on the executable's FULL PATH.** A copy of the game at a
new path silently loses it. MW4 renders at `bitdepth=16` and **modern GPUs expose ZERO 16-bit
display modes** — the shim *synthesises* them (proved directly: crash dump shows
`16 bit modes :` EMPTY without the shim, populated with it).
- **Without the shim the error message actively misleads.** GameOS raises
  `GOS_DXRASTERIZER_NOFULLSCREEN` — *"Another application is preventing use of full screen
  mode"* (`DXRasterizer.cpp:~1125`). That is a **catch-all** fired after every `SetDisplayMode`
  attempt fails; it even scans for NetMeeting. It sends you hunting for a conflicting program
  that does not exist. The real cause is the missing shim / absent 16-bit modes.
- **NEW: `build-env\set-appcompat.ps1` + `set-appcompat.bat`** — self-locating (`$PSScriptRoot`)
  one-click installer. Applies the layer to `MW4.exe`/`MW4pro.exe`/`MW4Ed2.exe` sitting next to
  it, wherever that install lives; HKCU always, HKLM too if elevated (note the HKLM value format
  differs — it carries a leading `$` marker). Verifies by reading back; detects the
  `HIGHDPIAWARE`-only entry that *suppresses* the auto-shim (the original STEP 5 bug).
  `-Remove` / `-WhatIfOnly` supported. **`deploy-mw4.ps1` now ships both into every deployment.**

### ✅ Exclusive fullscreen DOES work on Win10 without dgVoodoo2
Confirmed on the W4100 with system `ddraw.dll` and dgVoodoo2 physically removed: `MW4.exe`
alt-enters to exclusive fullscreen correctly **once the shim is applied to that exe path**.
Windowed mode also works natively with no shim at all (windowed sets
`Environment.bitDepth = DesktopBpp` = 32, so there is no mode switch). Console/shell confirmed
windowed; a **full mission windowed is still untested**.

### ❌ Native multi-monitor MFD is BLOCKED — and no flag combination fixes it
The panels' cooperative-level call was genuinely wrong (a latent 2002 bug): every panel asked to
be **both** the process focus window **and** its own device window, on the one shared `hWindow`,
*after* the main device had already taken exclusive mode on it. The main device
(`DXRasterizer.cpp:~1027`) already uses the correct two-call idiom
(`SETFOCUSWINDOW` alone, then `EXCLUSIVE|FULLSCREEN`) — tagged `//sanghoon`, same author. The
panels never were.

A new **`-tcoop <0-5>`** switch was added to test every plausible form on real hardware without a
rebuild between attempts. Results (no dgVoodoo2, shim applied, `-tmfds 4`):

| `-tcoop` | Flags | Result |
|---|---|---|
| 0 | `SETFOCUSWINDOW\|CREATEDEVICEWINDOW\|ALLOWREBOOT\|EXCLUSIVE\|FULLSCREEN` (legacy) | `DDERR_EXCLUSIVEMODEALREADYSET` |
| 1 | `CREATEDEVICEWINDOW\|EXCLUSIVE\|FULLSCREEN` (no focus claim) | `DDERR_INVALIDPARAMS` |
| 2 | `SETFOCUSWINDOW`, then `CREATEDEVICEWINDOW\|…` | `DDERR_INVALIDPARAMS` |
| 3 | `SETFOCUSWINDOW`, then `EXCLUSIVE\|FULLSCREEN` | 1st panel collides; **that collision steals exclusive from the main display**, after which panels 2+3 fully init (radar reached `CreateDevice(HAL) = DD_OK`). Side effect: desktop left at 1920x1080 **16bpp**. Not viable. |
| 4 | `EXCLUSIVE\|FULLSCREEN` only | `DDERR_EXCLUSIVEMODEALREADYSET` on **all** panels |
| 5 | `ALLOWREBOOT\|EXCLUSIVE\|FULLSCREEN` | `DDERR_EXCLUSIVEMODEALREADYSET` on **all** panels |

**CONCLUSION: on modern Windows only ONE DirectDraw object per process may hold exclusive
fullscreen.** The main display takes it; every secondary panel is refused. XP allowed multiple;
**dgVoodoo2 allows it because it is a full reimplementation of ddraw, not bound by that rule.**
=> **dgVoodoo2 cannot be removed by fixing flags.** The limitation is per *secondary display*,
not per feature, so it applies to **every mode that opens a second monitor** — all MFD modes
(`-tmfds 1/3/4`) **and cameraship mode**, which drives two displays with no MFDs at all via the
single-secondary `mr_device` path (`IsMultimonitorAvaliable()` returns false for
`_ECTCL_CameraShip`, so it falls to `IsSecondaryMonitorAvaliable()` — still a second exclusive
IDirectDraw7). **Console mode is the only configuration that needs no dgVoodoo2**, because it is
single-display. Default stays `-tcoop 0`.
(`-tcoop` is retained: it is how this was settled and will re-settle it on different hardware.)

### ✅ Working 4-monitor config (WITH dgVoodoo2)
- **dgVoodoo2 Scaling mode MUST be "Stretched, Keep Aspect Ratio".** Plain "Stretched" fails
  *silently*: main + radar go fullscreen black, both MFD monitors keep showing the desktop, and
  **every DirectDraw call still returns `DD_OK`** — the devices are alive but dgVoodoo2 never
  drives those outputs. Diagnosed with a temporary per-panel colour-flash test (since removed).
- Device index → physical monitor is **1:1** on this bench (colour test confirmed): main=0,
  radar=1, mfd-left=2, mfd-right=3, so `-tmon 1,2,3,4` is identical to auto-detection.
- **Confirmed working end-to-end: all three secondary panels present, full mission played.**
- ⚠️ **dgVoodoo2 is deliberately NOT in the repo and must NOT be — it breaks Windows XP
  deployments.** The pod fleet is mixed: XP pods need native DirectDraw and nothing else, while
  Win10/11 pods need dgVoodoo2 for any MFD mode. Shipping it in the build (or having
  `deploy-mw4.ps1` place it) would push a Win10-only dependency onto XP machines and break them.
  It stays an **installed-per-machine, OS-dependent prerequisite** set up by the pod owner on
  Win10/11 only. `dgVoodoo.conf`/`dgVoodooCpl.exe` were removed in `0ceba9c7` for this reason —
  do not "fix" that by re-adding them. The required setting (Scaling mode
  `Stretched, Keep Aspect Ratio`, on the **General** tab) is documented in the pod-owner release
  notes instead.

### 📐 Engine is 4:3 ONLY (relevant to every display decision)
`ImageHlp.cpp:~464` asserts the complete supported resolution set: **640x480, 512x384, 800x600,
960x720, 1024x768, 1280x1024 (5:4), 1600x1200**. There is no 16:9 mode and **no aspect
correction anywhere** in the codebase (the only `aspect` hits are texture-dimension caps and
CameraShip). On a 16:9 monitor a 4:3 image must be adapted by the scaler: plain stretch =
distorted (circles → ovals, reticle wrong); keep-aspect = correct geometry + pillarbox bars.
Native widescreen would require re-authoring every 2D/shell layout (all fixed 640x480 pixel
coordinates) — a content project, not a code tweak.

### 📐 Assessment: moving to borderless windowed (discussed, NOT started)
The only native path to multi-monitor, since it removes exclusive mode from the picture entirely.
Smaller than it sounds because the mechanisms already exist:
- The windowed present is already `wBlt(FrontBufferSurface, &Window, BackBufferSurface, …)` —
  a blit to an arbitrary screen rect. `Blt` stretches when the dest rect differs in size, so
  aspect-preserving borderless output is **rect maths, not new machinery**.
- Clipper wrappers (`wCreateClipper`/`wSetClipper`/`wSetHWnd`) already exist in `DirectDraw.cpp`,
  unused by the fullscreen path.
- **None of the drawing code changes** — panels already render into `pDDSTarget` and composite;
  `DrawQuad`/`DrawTexture`/fonts/`SwapRightState` don't care whether the present is Flip or Blt.
- Work is confined to `render.cpp`, `DXRasterizer.cpp`, `WinMain.cpp` (+ a little `Windows.cpp`):
  panel init → `DDSCL_NORMAL` + clipper + offscreen render surface (~150 lines); ~4 panel `Flip`
  sites → `Blt`; break the `HSH_EnterFullScreen2()` ↔ `EnterFullScreenMode()` coupling
  (`DXRasterizer.cpp:~1132` is its ONLY call site, which is also why windowed mode currently
  creates no panels at all); **new: explicit frame pacing** (windowed Blt has no vsync — this is
  the same root cause as the known mechlab fast-spin bug).
- **Gains:** no dgVoodoo2, no shim, no 16-bit dependency, no exclusive-mode contention, aspect
  under our control, and panels stay at their monitor's native mode so "this panel won't accept
  640x480" becomes impossible.
- **Risk that decides it:** windowed D3D7 device creation is per-GPU (the *editor* hit
  `DDERR_INVALIDOBJECT` on Win11 and needed DDrawCompat; the *game* succeeded natively on the
  W4100). Version lockstep means one build must serve every pod.
- **Recommended staging:** (1) `-borderless` for the MAIN display only, default off; (2) convert
  **one** panel (radar) — the small, make-or-break test of `DDSCL_NORMAL` + clipper +
  D3D-on-offscreen on the target GPU; (3) all three panels; (4) retire dgVoodoo2 + shim. Keep
  exclusive fullscreen switch-selectable indefinitely.

### Incidental findings
- **`-2dt` is not a recognised switch anywhere in the codebase**, despite appearing in production
  `ctcl.ini` launch lines. Completely inert. (2D targets are already the default; `-3dt` is what
  switches to the 3D model.)
- `NumHWDevices` (5) can exceed `NumDevices` (4) — it counts D3D device-enumeration callbacks, and
  an adapter exposing both a HAL and a T&L HAL yields two. Benign; `InitSecond` hardcodes
  `IID_IDirect3DHALDevice`.
- New switches are documented in `-help` (`-fps` under LOGGING AND DIAGNOSTICS, `-tcoop` under
  DISPLAY AND VIDEO).

## STEP 11: Cameraship Map/Armor screen shows background but no overlays — SOLVED
## (2026-07-26): it is the CTCL role, NOT the video card

Reported as *"the secondary armor/score/map screen in cameraship mode shows the background BMP
fine but none of the overlay graphics, with certain video cards but not others"*. **The video
card is a red herring. The machine was not being told it was a cameraship.**

### Root cause
`-ctcltype <n>` maps **straight** to the role enum — no remapping
(`MW4Application.cpp:~1534`, `g_nCTCL = token[0]-'0'`; `ctcl_params.h`):
`1 = console`, **`2 = game pod`**, **`3 = cameraship`**, `4 = none`.
The failing launch line used `-ctcltype 2` (game pod). The overlay drawing in
`mw4\Code\MW4\hudchat.cpp:~596` is gated on
`bool draw_mr = CTCL_GetType()==_ECTCL_CameraShip;` (`#ifdef _DEBUG` -> unconditional `true`),
so with role 2 the overlay block simply never executes. Fixed by launching `-ctcltype 3`
(user-confirmed working).

### ⚠️ Why this is so hard to spot — the two gates are INDEPENDENT
**The Map/Armor screen opens and paints its background regardless of the CTCL role.**
`HSH_EnterFullScreen2` / `CMR_Device` decide purely on *"is a spare secondary monitor
available?"* and never consult `CTCL_GetType()`; only `hudchat.cpp` checks the role. So a wrong
`-ctcltype` produces a screen that lights up, shows the correct artwork, survives mode changes
and renders nothing — **visually identical to a graphics-card/DirectDraw fault**, which is
exactly why it got mis-attributed to specific GPUs. Diagnose it from `gos-displays.txt`
(`CTCL type = ...`), never by eye.
- Corollary: `mr_device` also opens in **non-cameraship** configs. Observed here with
  `-tmfds 1` when `g_nDualHead = -1` (no spanned pair found): MFD mode 1 fell through to the
  single-secondary `mr_device` path on a *game pod*, giving the same background-only screen.
- **Field triage:** on a failing machine read that pod's `ctcl.ini` launch line and its
  `gos-displays.txt`. `CTCL type = 2` means it is misconfigured, not broken.

### Diagnostics added (kept)
- **`CTCL type = <n>` line in `gos-displays.txt`** (`render.cpp` `HSH_EnterFullScreen2`, via the
  already-present `g_pfnCTCL_GetType` hook — no game-code dependency). Always logged, states in
  words whether overlays will be drawn. One look settles the question.
- **`-tmr <0-3>`** Map/Armor diagnostic ladder (`g_nMRDiag`, `CMR_Device::BeginScene`):
  0 = normal, 1 = background blit with `DDBLTFAST_WAIT|DDBLTFAST_NOCOLORKEY`,
  2 = `Clear` instead of the background blit, 3 = additionally alpha-blend off + untextured
  magenta `DrawQuad`. Results that cracked this: **3 drew magenta** (device/3D/flip all fine),
  **2 was pure black** (the background blit was never erasing the overlays), 1 unchanged.
- **Draw-call counter** — `CHSH_Device::m_nDrawCalls`, bumped in `DrawQuad` / `DrawThickFrame` /
  `DrawTexture`, reset in `CMR_Device::BeginScene`, reported for the first 5 frames from
  `CMR_Device::EndScene` when `g_nMRDiag` is set. **This was the decisive instrument:**
  `0 draw call(s)` proves the overlay code never ran (a gating problem), whereas non-zero with a
  blank screen would mean it ran and drew invisibly (texture/alpha). Keep it — it separates
  "not running" from "not visible" in one run.
- Also logged: `CMR_Device` surface creation (`pDDSBackground`/`pDDSTexture`/`pDDSMapTexture`)
  plus the overlay texture's size/bpp and channel masks. Healthy reference reading on the W4100:
  `256x256 16bpp a=0000F000 r=00000F00 g=000000F0 b=0000000F` (normal 4-bit alpha, not zero).

### Method note
Every hypothesis reasoned forward from the code was wrong again (blit erasing overlays; alpha
channel; per-GPU rendering). The `-tmr` ladder plus the draw-call counter settled it in two runs.
Consistent with the rest of STEP 10: **instrument, don't infer.**

## STEP 12: Intel iGPU + USB display MFD failure — was `-tmon` misread + a real
## panel re-entry leak (2026-08-05)

Reported as *"MFD modes work on a W4100 and a Quadro, and with the radar on a USB adapter, but
fail on a machine with CPU-integrated Intel graphics"* — 3 displays (main + 2 MFDs) on an
Intel HD 630, radar on a Trigger 6 USB adapter, Win10 + dgVoodoo2. Second MFD panel returned
`DDERR_EXCLUSIVEMODEALREADYSET`. **Neither the Intel iGPU nor the mixed-adapter setup was at
fault.** Two independent causes, both now fixed/instrumented.

### Cause 1 — Windows Display Settings numbers are NOT the DirectDraw device order
The operator set `-tmon 3,4,2,1` by reading the numbers off the Windows Display Settings
arrangement diagram. Those numbers are an **undocumented UI ordinal that no API exposes** and
that need not match `\\.\DISPLAYn` *or* the DirectDraw index. On this box all three differed:

| Settings | `\\.\DISPLAYn` | DDraw device | desktop X | role wanted |
|---|---|---|---|---|
| 3 (primary, 800x600) | DISPLAY1 | 0 | 0 | main |
| 4 | DISPLAY4 | 3 | 800 | radar |
| 2 | DISPLAY2 | 1 | 1440 | mfd1 |
| 1 | DISPLAY3 | 2 | 2080 | mfd2 |

A permutation with two accidental fixed points — no derivable rule, so it cannot be corrected
in code. `-tmon 1,4,2,3` (correct DirectDraw indices) worked first try, user-confirmed.
**This is why Windows itself ships an *Identify* button rather than publishing the mapping.**

### Cause 2 — panels were re-opened without being released (REAL BUG, all machines)
`EnterFullScreenMode()` (`DXRasterizer.cpp:1132`) calls `HSH_EnterFullScreen2()` on **every**
mode change and every lost-front-buffer recovery, but the teardown `HSH_DirectDrawRelease2()`
was only wired to `DirectDrawRelease()` — i.e. full shutdown. So `CHSH_Device::InitFirst()`
overwrote `pDD` with a fresh `IDirectDraw7` while the previous one still held exclusive
fullscreen, **leaking it and its exclusive claim for the life of the process**.
- The log shows it plainly: `HSH_EnterFullScreen2` runs **twice**; the first entry initialises
  all panels `DD_OK`, the second fails on the same device.
- Only bites when a panel sits on the **Windows primary** — secondary outputs grant exclusive
  mode again, the primary does not. Every working pod happens to have the *main display* on the
  primary, so no panel is ever there. That is the whole reason it looked hardware-specific.
- **FIX:** `render.cpp` `HSH_EnterFullScreen2()` now calls `HSH_DirectDrawRelease2()` first when
  `hsh_initialized || hsh_mrdev_initialized`. Tagged `[panelreinit]`.

### ✅ `gos-displays.txt` rewritten as a full startup trace (`VideoCard.cpp`)
Was: final device list + role numbers. Now: every stage in the order the game does it —
command line (with `-tmon` shown already converted 1-based→0-based), **Windows desktop topology**
(`EnumDisplayDevicesA`/`EnumDisplaySettingsA`), each enumeration callback with its GUID and
`HMONITOR` resolved to `\\.\DISPLAYn` + rect + primary flag, per-device ACCEPTED/REJECTED, the
device table **before and after** the NULL-device merge with `DeviceGUID` **and**
`guidDeviceIdentifier` and an **adapter grouping**, every index shift in the merge, every
role-selection decision *with the reason each device was skipped*, each `-tmon` override
(applied/rejected + what auto-detect had chosen), a **desktop left-to-right order** block, and a
**consistency check that names two roles landing on one device as a CLASH**.
- **Fixed a real desync while doing it:** the merge shifted `DeviceArray` but nothing tracked
  which monitor each slot drove → `g_ahDevMonitor[]` is now shifted in lockstep. `HMONITOR` was
  previously discarded entirely (`videoDevices` gained `hMonitor`; `BufferDevice` takes it).
- Startup-only, ~255 open-append-close writes (~25–130 ms), nothing on the frame path. Kept
  unbuffered deliberately — this is the crash-survivable log (`gos-fps.txt` is the buffered one).
- APIs resolved via `GetProcAddress` with locally-declared `MONITORINFOEXA`/`DISPLAY_DEVICEA`
  layouts, so nothing depends on the 1998 SDK having multimon headers.

### ✅ NEW: `-tident` — the game's own Identify
`MW4.exe -tident [3..120, default 20]` fills **every** display with a distinct colour and prints,
huge, the number to type into `-tmon`, plus its DirectDraw device index and the role currently
assigned to it. Then exits. This is the only reliable way to map monitors, precisely because the
Settings ordinal is unreadable.
- **Uses `DDSCL_NORMAL` + GDI on the primary surface — no exclusive mode, no mode change.**
  Taking exclusive fullscreen on several devices at once is the very failure being diagnosed; a
  diagnostic that trips over it is worthless. It paints onto each monitor's existing desktop, so
  it works even on a pod where the MFD modes are broken.
- Opens the **real DirectDraw devices** rather than positioning GDI windows by `HMONITOR` — that
  proves the device-index→physical-output association through the same path the panels use.
  (Positioning by DirectDraw's own reported `HMONITOR` would be circular.)
- Implemented in `VideoCard.cpp` `IdentifyDisplays()`, called at the end of `FindVideoCards()`
  then `ExitProcess(0)`. Everything it does is also written to `gos-displays.txt`.

### Docs
`-help` for `-tmon` now states outright that these are **not** Windows Display Settings numbers
and points at `-tident`/`gos-displays.txt`; the old `-tmon 1,2,3,4` example was actively inviting
the mistake. Release notes gained a `-tident` section, the clash/re-entry fixes, and an upgrade
checklist step.

### Method note
Same lesson as STEPs 10 and 11, and it caught me out again: my forward-reasoned hypothesis
(duplicate `-tmon` assignment) was **wrong**, and the new logging disproved it in one run — the
consistency check explicitly printed *"No duplicate device assignments"*. The double
`HSH_EnterFullScreen2` was only visible because the log covers the whole start-up rather than a
summary. **Instrument, don't infer.**

## ✅ STEP 13: External MFD/Radar damage mappings installed + art audited (2026-08-07)

The coordinate and art workflow for the external MFD and Radar damage paper dolls is now fully
documented in **`MFD-RADAR-MAPPINGS.md`**. Start there; do not rediscover the image transforms,
tuple meanings, runtime offsets, legacy J&J formats, or BMP comparison rules from scratch.

### Controlling source and semantics
- `Gameleap\code\CoreTech\Libraries\GameOS\coord.cpp` owns four positional 65x11 arrays:
  `texuv2`/`offset2` = external MFD source rectangles/exploded origins;
  `texuv3`/`offset3` = Radar source rectangles/exploded origins. Numeric Mech ID (array row) is
  authoritative; comments are labels. Zone order is `LL,RL,LA,RA,RT,LT,CT,CTR,HD,S1,S2`.
- These are NOT the normal cockpit HUD's `texuv`/`offset` arrays in `huddamage.cpp`, and NOT the
  small atlas images under `hsh\MFD`.
- Runtime loose art is `Gameleap\mw4\hsh\hud\<stem>.bmp` for MFD and
  `Gameleap\mw4\hsh\radar\hud\<stem>.bmp` for Radar. Coordinate changes require an MW4.exe
  rebuild because `DXRasterizer.cpp` includes `coord.cpp`; loose art changes do not require a
  `.mw4` resource repack.

### Installed mappings and reproducible audit (`97558039`)
- Added `Finished HUDS from J&J/generate_comparison_maps.py` (Pillow) plus 38 LFS-tracked review
  PNGs and `COMPARISON-SUMMARY.md`. Red = current `coord.cpp`; green = supplied J&J geometry.
- Installed every complete supplied set: **19 displays across 13 chassis** (12 MFD + 7 Radar).
  Final generator result: **19 exact, 0 different, 0 warnings, 7 missing opposite-display
  inputs**. Missing inputs are Argus/Fafnir/Flea/Gladiator/Kodiak/Longbow Radar and Hellspawn MFD;
  they were not guessed or changed.
- J&J data has two formats. Modern = 4-value unexploded source rectangle + 2-value exploded
  origin. Legacy = 2-value source upper-left + 4-value exploded bounding box; derive source width/
  height from that exploded box. Combined files use section headings. All-zero tuples may appear
  at either width and mean "absent zone."
- Normalized four unambiguous input typos without changing reviewed geometry: Behemoth MFD LT
  `1742→174`, Behemoth Radar CT `202.242→202,242`, blank Behemoth Radar S2 `→0,0`, and Fafnir MFD
  LL `162.326→162,326`.
- Preserve validated odd coordinates rather than rounding them. Radar divides full-size authored
  values by two using integer arithmetic, so rounding changes final placement.

### Behemoth II inheritance (`deafc2b0`)
- Behemoth II is Mech ID 12 and intentionally shares Behemoth ID 11's art and geometry. All four
  ID 12 rows now exactly inherit ID 11. The generator asserts this relationship and fails on any
  future divergence. There is **no Annihilator II** in the 65-ID roster; do not invent a row or
  runtime stem for it.

### Runtime-art result: no copies needed
- Decoded every supplied exploded 512x512 BMP and compared pixels against its canonical `hsh`
  destination. All 19 supplied display assets were already pixel-identical. File hashes differed
  because BMP mode/palette/header encoding differed (`P`/`L`/`RGB`), which is not an art change;
  no `hsh` file was replaced.
- Separately compared `hud\behemothii.bmp` and `radar\hud\behemothii.bmp` to Behemoth's runtime
  files; both existing Behemoth II files were already pixel-identical 512x512 grayscale images.
- Compare decoded pixels before copying. The exploded BMP is the runtime asset; unexploded BMPs
  and full-color images are authoring references. Resolve canonical stems rather than trusting
  supplied names (`assian2_*` is a typo; runtime is `assassin2.bmp`; preserve existing `Fafnir.bmp`
  case on Linux).

### Validation and remaining work
- Run: `python3 "Finished HUDS from J&J/generate_comparison_maps.py"`. Expected current output:
  `Generated 38 maps for 19 display sets: 19 exact, 0 different` and
  `Warnings: 0; missing display sets: 7`, followed by the Behemoth II inheritance assertion in
  `COMPARISON-SUMMARY.md`.
- `coord.cpp` must remain CRLF-only and retain four 65-row arrays with 11 zones per row.
- **Still required:** rebuild Release/Profile on the Windows VC6 machine, deploy MW4.exe, and test
  intact/damaged states on physical MFD and Radar displays. Linux validation proves source/data
  equality and image alignment, not runtime hardware behavior.

## ✅ STEP 14: High Explosive MechLab firing repaired (2026-08-08)

High Explosives (weapon ID 87) could be installed in MechLab but never fired. They are ordinary
one-shot weapons activated through their assigned weapon group (group 3 by default), **not** by
ejecting. Put multiple charges in the same group and use Group Fire to detonate all of them in one
trigger cycle; Chain Fire activates only one at a time. Each fired charge independently creates a
100-damage blast in a 30 m radius and applies 200 center-torso damage to its own mech.

Three legacy defects made the feature unusable:
- `HighExplosive.data` used the unregistered key `MaxAmmoCount=1`; the weapon model registers
  `MaxAmmo`, so the charge had no valid conventional ammo capacity.
- MechLab computes initial ammo as `MaxAmmo / (3 * TotalSlotsTaken)`. Even with the corrected key,
  `1 / (3 * 2)` truncated to zero. `Weapon.cpp` now clamps starting ammo and ammo-per-pack to one
  only when the weapon declares a positive maximum; zero-capacity weapons remain unchanged.
- `MechBay/weapons.script` applied High Explosive UI handling to stale ID 84 (NARC) instead of 87.
  Side effect: NARC Beacon (`MaxAmmo=18`, 1 slot → 6 rounds) was wrongly inheriting the
  `highexplosive` flag, which suppresses the ammo-round count, so NARC now displays its rounds
  in MechLab again. Intended; the flag exists only to hide the count on a one-shot charge.

The two edited content files live in **different packages** — `HighExplosive.data` is packed by
`core.build` → **`core.mw4`** (`props.build` contains no `WeaponSubsystems` entries at all), and
`weapons.script` by `props.build` → **`props.mw4`**. `build-resources.ps1` repacks any package with
newer sources, so both are picked up automatically; the distinction only matters if a package is
ever repacked selectively.

Residual gap (not hit today): the MechLab path fixed here is `Weapon::CreateStream`. Authored
content takes a different path — `Weapon_Tool.cpp:66` defaults `ammoCount` to `-1` when the
instance page omits `AmmoCount`. No mech `.subsystems` currently mounts a High Explosive, but
adding one to a stock loadout requires an explicit `AmmoCount=1` or it will be unfireable for the
same reason. (`Content\ShellScriptsDev\MechBay\weapons.script` still has the old `84`; no `.build`
packages that tree, so it never ships.)

Linux validation confirmed High Explosive now constructs with one round, zero-capacity weapons
still produce zero ammo packs, a sweep of every weapon subsystem found High Explosive to be the
**only** one whose starting-ammo formula truncated to zero (so no other weapon's ammo changed),
edited files retain their original CRLF/legacy encodings, and the focused diagnostics/diff checks
are clean. Still required: rebuild `MW4.exe` on the Windows VC6 machine, repack resources, deploy,
then remove/reinstall the charge in existing saved variants that may retain serialized zero ammo.

## Next steps (proposed)
- [ ] (Parked, diagnostics-only, cannot affect a real pod) Two gaps found while testing `-tident`
  on a **single-monitor VM** (2026-08-05). Both only occur when one physical monitor is exposed as
  two DirectDraw devices, which happens because the NULL-device merge is gated on
  `NumMonitors>=2` (`VideoCard.cpp`) — by design, it is a multi-monitor fixup, so a single-monitor
  box keeps the alias in slot 0.
  1. **`-tident` overpaints itself.** With device 0 = NULL alias and device 1 = the real output,
     both resolve to the same screen, so it paints red/"1" then immediately green/"2" over the top.
     Looks like a glitch; is actually correct. Should detect devices sharing an `HMONITOR` and
     label them together (`1 & 2 - same monitor`) instead of painting sequentially.
  2. **The clash check compares device INDICES, not resolved monitors.** On that VM it reported
     *"No duplicate device assignments"* while `main`→device 0 (primary alias) and `radar`→device 1
     (`\\.\DISPLAY49`) were the **same physical monitor** — i.e. exactly the collision the check
     exists to predict, reported as all-clear. Fix: treat a device with no `HMONITOR` as the
     Windows primary, then compare resolved `HMONITOR`s rather than indices.
  Neither can occur on a multi-monitor pod. Worth doing only if bench/VM testing starts relying on
  the check being trustworthy.
- [ ] (Researched, NOT started) Make `-window` work for **all** modes — full findings in
  **`WINDOWED-MODE.md`** (repo root, 2026-08-05). Headline: `-window` does not fail for MFD/
  cameraship modes, it **silently disables** them, because `use_shgui` is derived from
  `Environment.fullScreen` (`MW4Application.cpp:1373`). Beyond that one line the work is a windowed
  variant of `CHSH_Device::InitFirst`/`InitSecond` (DDSCL_NORMAL + clipper + offscreen backbuffer,
  no SetDisplayMode), one HWND per panel, and 8 `Flip`→`Blt` sites in `WinMain.cpp`. The deciding
  risk is whether **four** windowed D3D7 devices can coexist in one process — the editor failed
  this on Win11 and needed DDrawCompat (STEP 8), while the game's own windowed main display
  succeeds on the W4100. Overlaps heavily with the borderless-windowed assessment below.
- [ ] (Decision pending) Borderless-windowed migration — see the staged assessment in STEP 10.
  Step 2 (one panel) is the cheap, decisive experiment. Note this is the only route that would
  remove the dgVoodoo2 prerequisite **without** breaking the XP pods, since it needs no external
  DLL on either OS.
- [ ] Test a **full mission windowed** (only the console/shell has been verified windowed).
- [x] ~~Windowed 3D viewport on Win11~~ — DONE via DDrawCompat (see STEP 8 viewport section).
- [ ] (Optional) Curate remaining WIP content as features are exercised (editor loads dev `Content\`).
- [ ] (Optional) Build Debug/Armor configs (need `dbg.bin`/`arm.bin` output dirs).
- [ ] (Parked, NOT scheduled) Raise MP cap to 24 or 32 — see "Reference"/"PLAN" sections above.
- [ ] (Parked, minor, cosmetic-only) Mechlab background mech spins way too fast in **windowed**
  mode while focused (normal fullscreen / when unfocused). Root cause diagnosed 2026-07-02:
  `MechLab::Execute()` adds a fixed `yaw += 0.02f` **per frame** (`MechLab.cpp:285`, called
  per-frame via `DoShellLogic`) — no frame-time scaling. Fullscreen is vsync-locked (~60 FPS,
  flip chain) and unfocused-windowed gets GameOS's `Sleep(20)` (`Windows.cpp:391`), but
  focused-windowed uses the unsynced Blt path (`DXRasterizer.cpp:1267`) → uncapped FPS on
  modern GPUs → spin rate ∝ FPS. Not gameplay-affecting (in-game sim is time-based; this is
  the shell's decorative turntable). Fix when convenient: scale by frame time (1.2 rad/s;
  needs MW4.exe rebuild) or set `gos_Set_LoseFocusBehavior` mode 3 (built-in 60 Hz cap,
  `Windows.cpp:407`) to pace the whole shell.
