First real run of -fps (222 seconds, 4-monitor bench, in-mission) showed a
rock-solid 60.0 fps average but a worst frame of ~24ms in 82% of seconds, with a
median of 24.4ms and a minimum of 23.2ms. That regularity was the tell: 60Hz vsync
intervals are 16.7 / 33.3 / 50.0 ms, so 24ms is not a multiple of anything and had
no business recurring once per second with that consistency.
Cause: the logger was measuring itself. The per-second line was written straight to
the file with WriteFile on the render thread, and the frame duration is recorded
BEFORE the line is emitted -- so the write's cost landed in the *next* frame's
measurement, which belongs to the next second's bucket. Result: exactly one
inflated frame per second, indefinitely. Holding the handle open (as the first
version did) was not enough; a single WriteFile is still several milliseconds when
something like antivirus is in the path.
Three fixes:
1. Buffered output. Lines accumulate in a 128 KB static buffer (~2000 rows, about
33 minutes at one row per second) and are flushed only when the buffer fills or
at exit via atexit(). A normal measurement session now performs no file writes
at all while running. Trade-off, deliberately accepted: a hard crash loses the
un-flushed tail. gos-displays.txt remains the crash-survivable log; gos-fps.txt
is a measurement instrument and must not perturb what it measures.
2. Meaningful percentiles. A "1% low" over 60 samples per second computes
nFrames/100 = 0, which was clamped to 1 -- so the column was literally
1000/worst_frame, i.e. the worst-frame column restated in different units.
Verified against the log: worst 24.1ms -> 41.5 fps, exactly the "1% low" printed.
Per-second is now a 5% low (worst 3 of 60), which is a number that actually
differs from the worst frame. True whole-session 1% and 0.1% lows are computed
at exit from a 0.5ms-bucket frame time histogram (2000 buckets, 0-1000ms) and
printed in a new session summary along with total frames, elapsed time, average,
and total hitches. The histogram gives exact-enough percentiles over an entire
session without retaining every frame time.
3. Carry-over. A single frame longer than one second (a level load: the log shows
4338ms and 4799ms frames) left the accumulator above the 1000ms threshold, so
the next few frames each emitted their own bogus one-frame row -- visible in the
log as rows reporting "1 frame, 1339 fps". The excess is now discarded.
Also adds #include <stdlib.h> to WinMain.cpp for atexit(); it was not reachable
through pch.hpp. GOS_FpsAtExit is declared __cdecl: GameOS builds with /Gz, which
makes __stdcall the default calling convention, but atexit() takes a __cdecl
callback -- without it VC6 rejects the call with C2664.
What the run did establish, and still stands: 60 frames per second sustained for
~150 seconds with no rhythmic multi-hitch pattern, so the mode 4 split-MFD stutter
fix (0a657b59) is holding. Genuine dropped frames (>40ms) occurred in 18% of
seconds, including clusters of 80-90ms; those are real and unaffected by this fix.
Re-measure after this change to see the true baseline.
CLAUDE.md STEP 10 updated: the previous claim that holding the handle open kept the
logger from perturbing the measurement was wrong, and is replaced with what was
actually observed.
Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
83 KiB
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 statuswas 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), thebuild-env\*.ps1deploy/build scripts,build-env\play-mr.*, thebuild-env\*.regfiles (incl. UTF-16vc6-hklm-registration.reg), and the docs (CLAUDE.md/README.md/RECOVERY.md). Stalebuild-env\*.log/.plg/.mapbuild 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) + AppCompatLayersshims forMW4.exe,MW4pro.exe,MW4Ed2.exe; dropped two staleC:\VWE\MW4Editor\*entries (that dir was removed in STEP 9). - HKLM (elevated): VC6 install registration (
Wow6432Node\...\VisualStudio\6.0, ProductDir) + all-users AppCompat shims forMW4.exe/MW4pro.exe. - Reminder: AppCompat (
DWM8And16BitMitigation) is keyed on the exe PATH — relocating an exe requires re-applying the shim (the.regfiles +deploy-*.ps1now use firestorm paths).
- HKCU: VC6 IDE directories (
- Git remote (new):
origin = https://gitea.mysticmachines.com/VWE/firestorm.git(washttp://192.168.1.167:3000/cyd/firestorm.git; host + ownercyd→VWEchanged; same backend, so LFS objects already present — pushes only send new deltas). LFS access hint set tobasic. - 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 needsrepository: Read and Write). Thengit pushuses it with no browser prompt. - Uses Git LFS;
.gitattributesforces* -textfor byte-exact restores regardless ofcore.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. SeeREADME.mdfor 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) (ItsContent\+hsh\were stale 2005 duplicates → moved to_UNUSED\; live data isGameleap\mw4.)Gameleap/— onlymw4/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/— per-mech HUD art (MFD + Radar) for ~15 'Mechs.
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 (~65AI_*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,.buildfiles.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/.dspare "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
.dspfiles — they come from the IDE's global Tools → Options → Directories, which is why DX7-first ordering matters.
- DirectX 7.0 SDK — headers must be FIRST in include order
(
- Compiler flags of note:
/G6 /Zp4(struct packing 4 — must be consistent across all libs),/MDruntime (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\MFCCommon\MSDev98\Bin\MSDEV.EXE(IDE + command-line builder).
- DirectX/Media/EAX SDKs — check these era-typical roots:
- DX7 SDK:
C:\DXSDK\,C:\mssdk\, orC:\DX7SDK\ - DX8 SDK:
C:\DXSDK\orC:\Program Files\Microsoft DirectX 8*SDK\ - DX Media 6:
C:\DXMedia\orC:\DXMEDIASDK\ - EAX SDK: under
C:\or the Creative/SB SDK folder.
- DX7 SDK:
- ⭐ Most valuable artifact — the IDE directory ordering (reproduces the env exactly).
Load the build user's
NTUSER.DATand 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 (verifyDIRECTDRAW_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/LibrariesandCoreTech/Libraries.
- Self-contained: all 24 sub-projects verified present on disk, spanning both
MechWarrior4Build.dsw= LEGACY/alternate (uses oldMW4Gos,Network,GamePlatform,MW4GameEdv1). Do not use; superseded byMechWarrior4.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 .libs,
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)
stlport— foundation, no deps. Build first.- Leaf libs (depend only on stlport / nothing):
GameOS,Stuff,MLR,gosFX,ImageLib,Compost,GOSScript,ElementRenderer,Adept,server,DLLPlatform,MFCPlatform,GamePlatformNoMain,ctcls. MW4— the game library.- Helper exes/dlls:
Launcher,autoconfig,mw4print,MissionLang,ScriptStrings,MW4DedicatedUI. MW4Application— links everything intoMW4.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.dswin 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/REBUILDfor a clean build.
How to RUN (the mw4/*.bat scripts are LAUNCH, not compile)
game_dbg.batetc. just run the exe:binaries\mw4\MW4_dbg -armorlevel 3 -nobuild.-build= (re)build game resource.erfs from source assets at startup;-nobuild= skip.bldresources_game_dbg.bat= run with-buildto regenerate resources.editor_dbg.bat= launch the editor (MW4Ed2_d).
⚠️ Open discrepancies to resolve before a real build
- Output path vs. launch path mismatch:
.dspfiles emit todbg.bin/MW4.exe/rel.bin/MW4.exe, but thegame_*.batlaunchers point atbinaries\mw4\MW4_dbg.exe. There is a missing copy/deploy step (or the bats target a different deployed layout).mw4/Binaries/currently only holds3DSPlug-ins/. TODO: find/recreate the deploy step. FullExport.batchreferences 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 staticeax.libneeded (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
#errorguard); 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\LIBonly. Two verified facts that make this work:
DXMedia\includehas NO core DX headers (DirectShow only) →ddraw.hstill resolves fromdx7asdkbefore VC98's old one (satisfies thepch.hpp0x0700 guard).- The DX/DShow
.libs were copied intoVC98\Libon the original machine (verified present in our copy: ddraw/dinput/dsound/dxguid/strmiids/quartz/strmbase/amstrmid); pulled in via#pragma comment(lib,...)inGameOS\guids.cpp, not the link line. Captured asbuild-env/vc6-directories.reg(rebased to local copies; HKCU import, no admin) and reflected exactly inbuild-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-bitmsdevon 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(copiedVS98+MSDesigners98so 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.regthenvc6-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 viaStart-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 builtMW4.exe(3,637,248 bytes) + all runtime DLLs + prebuiltresource\*.mw4. - Sources (all from working dir): runtime base =
Gameleap\mw4; binaries =rel.bin. - The old
game_*.batassumed abinaries\mw4\MW4_dbg.exelayout that the real game never used — the runnable game hasMW4.exeat the deployment root. Launch viaC:\VWE\firestorm\MW4\run-mw4.bat(resources prebuilt, no-buildneeded).
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\MINUSGraphics\(119 MB source .tga) andOriginal\(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.binbinaries (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:
- DirectDraw 16-bit compat shim (Win11). MW4 runs
bitdepth=16; modern Windows fails its DirectDraw hardware-surface creation withDDERR_NODIRECTDRAWHWunless theDWM8And16BitMitigationAppCompat layer is applied to the exe path. Our path had a strayHIGHDPIAWARE-only layer that suppressed the auto-shim. Fix: set HKCU layerDWM8And16BitMitigation HIGHDPIAWAREforC:\VWE\firestorm\MW4\MW4.exe(deploy script does this; matching all-users HKLM$ DWM8And16BitMitigationis inbuild-env\mw4-compat-hklm.reg). Note: AppCompat is keyed on exe PATH, so a copy at a new path needs the layer re-applied. - Dev-only DLLs shadowing system DLLs. Our rules copied every root
*.dll, which pulled indbghelp.dll/imagehlp.dll(2003 copies that shadow Windows' system DLLs and break DirectDraw init) plus tool DLLsijl10.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
-buildflag, which is#ifdef LAB_ONLY— so only Debug/Armor/Profile (MW4_dbg/MW4_arm/MW4pro.exe) can build resources; ReleaseMW4.execannot. (That's whybldresources_game_*.batuse the LAB_ONLY exes.) - Entry point:
MW4Application.cpp->Tool::Instance->BuildResources("Content\MechWarrior4.build", VER_CONTENTVERSION=63, deps); recursive driverAdept::Tool::BuildResources/ParseBuildFile; per-type packers inAdept\Tool.cpp+MW4\MWTool.cpp. - Input:
Content\MechWarrior4.build(master tree) -> per-package.buildmanifests ([resource\X.mw4]+data=/instance=/file=/notation=/campaign=/... entries) -> source assets underContent\. Output:resource\*.mw4+*.dep(incremental via.deptimestamps + content version). Needs the full ~4.6 GBContent\source tree.
⚠️ Resource packer quirks (discovered 2026-07-02, ConLobby promotion)
- Incremental rebuilds carry forward stale entries. A package rebuild re-packs only
entries the
.depflags 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: deleteresource\<pkg>.mw4+<pkg>.dep, then runbuild-env\build-resources.ps1. Also:Copy-Itempreserves the source mtime, so a promoted/copied file can look OLDER than the package and be skipped — touch it (or full-repack). - Script entry names are alphabetically skewed vs their contents (in
directory=sweeps, e.g. props'content\shellscripts): e.g. entryConLobby.scriptholds CreatePilotModal-like text, entryConLobbyMission.scriptholdsGUNStatus.script(byte-identical), and our 140 KB console (sourceConLobby.script) lands under entryComputerPlayer.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); needsmfc42.dll(present in deployment) at runtime. Profile config also exists (->pro.bin\MW4Ed2.exe). - Source fix required (editor source had drifted; shipped
MW4Ed2.exeis from 2003 and was never rebuilt against the 2009 code): enumMPGameTypesinmw4\Code\MW4GameEd2\ObjectManager.hwas missingGT_StockCampaign, whichObjectManager.cppreferences (string map @1013, int mapcase 14). Added the enum member betweenGT_StockSiegeAssaultandGT_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 byContent\Skies\fact01_sky\fact01_sky.hint) — genuinely absent from the dev tree (no file of that name anywhere). The FireStorm devContent\is an incomplete WIP with real asset gaps. GameOS'sSTOPpops 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 triedcontinue(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 insideSysWOW64\ntdll.dllheap 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(registryImage File Execution Options\MW4Ed2.exeGlobalFlag=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.cppplaceholder 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 inmw4ed2-pageheap-off.reg). Confirmed active (editor mem 45->94 MB). Builtpro.bin\MW4Ed2.map(added/mapto 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 runcdb -g -G -c "kb;q" -o MW4Ed2.exewith PageHeap on -> one-shot symbolicated faulting stack. Alt: WER LocalDumps (HKLM) -> parse the minidump's exception stream for the faulting EIP -> map viaMW4Ed2.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 + ourMW4Ed2.exe; the bulk (Content\,resource\,hsh\) are directory junctions to the dev treeGameleap\mw4(no 5.5 GB copy). SameDWM8And16BitMitigationcompat shim as the game. - Content data fix needed to launch:
Content\skies.build:109declaresFile=Skies\reduex_sky\reduex_night.erf, but the night-sky erf was misnamedReduex_sky_night.erf. The editor (LAB_ONLY, asserts on missing files) STOPped on it. Fixed by addingreduex_night.erf(copy ofReduex_sky_night.erf) inGameleap\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:
-
Joystick enumeration heap-corrupts in modern
dinput.dll.CMInstall->CMCreateJoysticks->wEnumDevices(DIEDFL_ATTACHEDONLY)->CMCreateJoystick7(ControlManager.cpp). On Win11 the legacy IDirectInput7 path, when itRelease()s a phantom/virtual HID "joystick" (0-axis "misreported device" at line ~602), corrupts the heap insidedinput.dll. Surfaces asRtlFreeHeapAV (normal heap) or, under full PageHeap, as a guard-page over-read indinput!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.cppInitGameOSnow setsgDisableJoystick=1(editor/Platform_MFConly; the game uses a different platform path and is unaffected). Chosen over the/gosnojoystickswitch because in LAB_ONLY buildsWinMain.cpp:~398shows a help dialog and exits if any leftover/gostoken remains on the command line — fragile (e.g. cdb perturbs the cmdline -> trips it). Tagged[editor modern-Windows fix].
- FIX (source):
-
Mouse
SetCooperativeLevel(NULL, FOREGROUND|NONEXCLUSIVE)fails 0x80070006.CMCreateMouse(Mouse.cpp) resolves the coop window fromhWindow, which is always NULL in the editor atCMInstalltime: the editor passespFrame->GameWnd->m_wndView.m_hWnd, butInitSecondaryWindows(MainFrm.cpp:173) onlynewsCGameFrameand neverCreate()s it, soCGameFrame::OnCreate(which buildsm_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.cppCMCreateMousefalls back toGetForegroundWindow()/GetDesktopWindow()withBACKGROUND|NONEXCLUSIVEwhen the resolved window is NULL; the game path (validhWindow) is unchanged. Tagged[editor modern-Windows fix].
- FIX (source):
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):
_backupdev-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 intoresource\maps\colsm01_backup.mw4etc. — confirms they're valid.)- Dangling map refs:
Missions\EditorTemplate\EditorTemplate.instanceMap=arctic01 (exists nowhere) ->arctic04;Missions\s1s2\s1s2.instanceMap=lunar02 ->lunar01. - Double-nested user mission:
Missions\PhoenixPalaceSTB\PhoenixPalaceSTB\*moved up one level (the files already referenced the top-level path); then its.instanceModel=pointed atdropzones\hill.contents(page[droppoints]) -> fixed tophoenixpalacestb.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.regELEVATED (UAC) -> HKLM IFEOMW4Ed2.exekey gone. The real-nameMW4Ed2.exenow runs directly (no longer AVs at ~2s). TheMW4Ed2_nph.execopy remains as a page-heap-immune fallback. deploy-editor.ps1updated: sources the Profile build (pro.bin, the working config) +.pdb; writesrun-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(orMW4Ed2.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.binoutput 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.dllis fatal toMW4pro.exe's fullscreen build init ([DDrawCompat] Fatal Error). Since the resource builder runs from the sameGameleap\mw4,build-resources.ps1now movesddraw.dllaside (.buildaside) for the build and restores it in afinally(verified: build exit 0, ddraw restored). - Game deploy unaffected:
deploy-mw4.ps1addsddraw.dllto its skip list, so DDrawCompat + the editor exe/pdb/launcher/.disabledDLLs never leak intoC:\VWE\firestorm\MW4(verified: 682 files / 868 MB, 58 packages, fullscreen native DDraw). Old launch pathC:\VWE\firestorm\MW4Editor\...is superseded byC:\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.exelinked 2016-10-04, Launcher/MissionLang/ScriptStrings/ctcls 2014, butmw4pro.exeis the ORIGINAL 2009-06-14 build, byte-size-identical to our rebuild.__TIMESTAMP__banner in MW4.exe: oursSun Jun 14 01:45:57 2009vs release02: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 ourVehicleInterface.cpphas). No evidence of missing source. - ✅ Degraded-mode placeholder patch made LAB-only (2026-07-02). Our rebuilt game exe used
to contain the
01AACA1.tgaplaceholder (Adept links into the game); release was clean.ResourceImagePool.cppnow:#ifdef LAB_ONLYplaceholder (editor keeps degraded mode),#elseoriginalSTOP(("Texture %s could not be found!")). Release+Profile rebuilt (0 err), verified placeholder gone fromrel.bin\MW4.exe/ present inMW4pro.exe; both deployed toC:\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.cppwantshud\assassin2/behemoth/ behemothii...). Copied the 13 missing files intoGameleap\mw4\hsh+ deploy (hud/radar assassin2+behemoth+behemothii, Mechs behemoth/black hawk/longbow/solitare/victor, decals 46/47);Mechs\black hawk.bmp+solitare.bmpupgraded 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.nfobyte-identical. Only real packing deltas, both in props.mw4: (1) release packsshellscripts\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) + portedgos_LZDecompress(FileIO.cpp:1693 — LZW, 9→12-bit LSB-first codes, 256=clear/257=EOF/dict@258) and extracted both TGAs intoContent\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 junkConLobby.script.new/.script.orgbackup files fromContent\ShellScripts\(harmless but shippable-junk; note.script.newcarries 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.Campaignsources 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'sConLobby.scriptENTRY 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): devConLobby.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) → devConLobby.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.newis the newest console revision anywhere — the release only added what .new already contains. ✅ RECONCILED 2026-07-02:.newpromoted toContent\ShellScripts\ConLobby.script(140,532 B),.newfile removed, stale loose deploy copyMW4\Content\shellscripts\ConLobby.scriptremoved (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.inienableshardwaremixing=true,BiggieSizeIt=1,RuleBook=1(our inis don't); ctcl inis use 10.0.0.x (ours 200.0.0.x) +c:\games\MW4paths. - 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.connectionIDis aBYTE(0–255).Environment.NetworkMaxPlayersis passed straight to DirectPlay'sdwMaxPlayers(CoreTech\...\GameOS\Net_Main.cpp:2142) — no GameOS ceiling;m_maxPlayershas no hard upper clamp (MW4Shell.cpp:1803only clamps bots down to the player count). - 16 is enforced in THREE layers — a script edit alone is insufficient/misleading:
- UI:
Content\ShellScripts\ConLobby.script#define MAX_ROSTER_COUNT 16(already 8→16; gated byUSE_O_MORE_PODS/g_nMechPodNum; also clamps vsMAXTESLA_P= cafe Tesla count, ≤8). - Compiled defaults (the real connect gate):
MW4Shell.cpp:13319-13321non-coop setsm_maxPlayers=16; Environment.NetworkMaxPlayers=16; m_maxBots=16(coop branch caps at 9). Editing only the.scriptshows 24 slots but the session still rejects the 17th player. - Lobby pods / scoreboard (
hudscore) / radar UI are laid out for ≤16.
- UI:
- To actually reach 24: raise
MAX_ROSTER_COUNTand them_maxPlayers/NetworkMaxPlayers/m_maxBotsdefaults (→ rebuildMW4.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_maxBots16 → 32. Coop branch13309/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 (paramPLAYER_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 underUSE_O_MORE_PODS/g_nMechPodNum); raise the player-limit control's max to 32. Note theMAXTESLA_Pclamp 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.scripttop-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: newg_dwRIOBaud(0 = default);SetupConnectionapplies the override after the type/console-based default;SetupCommbuffers 2/2 → 1024/1024 only when the override is active (defaults untouched for deployed pods).mw4\Code\MW4Application\MW4Application.cpp: parses-tbaudnext to-trio.- RIO serial facts (for future work): COM1 is hardcoded (
OpenConnection(1)in the CRIOMAIN ctor, reached viaVehicleInterface::ExecutionStateEngine::InitializeClass— runs at MISSION/vehicle init, NOT in the shell); the plasma score display is a separateC232Commlink on COM2 (CPlasma, 9600), untouched by-tbaud. - Rebuilt Release + Profile (0 errors each); fresh
rel.bin\MW4.exedeployed toMW4\. 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-firestormdriver:game-startnow takes optional quoted extra args. PS 5.1 quirk: call it via-Command "& '...driver.ps1' game-start '-tbaud 115200'"—-Filebinding rejects dash-leading positional args, andStart-Process -ArgumentListdoesn'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=10so dropdown scrolls. - Fixed bare
else if(missing braces) that caused a null-reference crash in the console lobby when the commit was merged frommain. This was the root cause of theaa500be7regression. - Fixed
i==5vsi==6max_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 (commit87e25677) — removed from repo (git rm). The build script also moves any such files aside defensively via the$dgvMovedblock. - Removed all dead code for fullscreen fallback, bitdepth patching, ddraw move-aside logic.
dgVoodoo.conf/dgVoodooCpl.exealso removed fromGameleap\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.hetc.). Files are now clean UTF-8 (no BOM), safe for any modern editor. - Notable functional translations:
recscore.cppbody-part return values and kill-announcer format strings (gameplay-visible on Korean Windows; now English on all systems);nonmfc.hassert 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 inai command.hpp, Û/ß in AnimationSuite headers. - Font table
*.hfiles (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.dllandMW4\Language.dllreplaced with the freshly compiled English build fromLanguage - Win32 Englishconfig (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 oflibmysql.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 indb_schema.sql. Config viamw4print.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 tooptions.ini [battle tech print] BannerText=. - libmysql.dll (MySQL Connector/C 32-bit) added to
Gameleap\mw4\so deploy copies it alongsidemw4print.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 + leftBeginScene(clear + grid); no flipsh_step 1: flip right MFD (shows channels 3–4 from previous cycle) + rightBeginScene(clear + grid)sh_step 2–4: channels 0–2 → left devicesh_step 5–6: channels 3–4 → right devicesh_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.
ROOKIEMECHdefines 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 (
* -textin.gitattributes). ⚠️ Encoding hazard: always read/write CRIOMAIN.CPP as binary (or with an explicit encoding codec). Python text-modereadlines()silently strips\r, causing a 3000+-line git diff when every line's\r\nbecomes\n. If that happens, restore CRLF withre.sub(b'(?<!\r)\n', b'\r\n', data)in binary mode, then amend the commit. - Added
g_dwRIOPollTimeoutglobal (default 50 ms). Computed inSetupConnectionbefore 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/maxare 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.NetworkMaxPlayersset toparams->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;breakmust be present in this case (was accidentally dropped once — fall-through toJOIN_IN_PROGRESS_PARAMETERcorruptsm_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(matchestexturename[]canonical)hsh/Mechs/battlemasteriic.bmp→battlemaster iic.bmphsh/Mechs/mad cat mk.ii.bmp→mad cat mkii.bmphsh/Mechs/behemoth ii.bmpadded (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 14g_globals (g_szRookieMission,g_nRookieGameType, and 12 numeric params); registered asgosScript_RegisterVariableinStartUp/ShutDown;CTCL_SetCDSPreads the[RookieMission]page viaNotationFileand populates them. Defaults:"ScarabStronghold - Attrition", GameType=2 (Attrition), UnlimitedAmmo=1, all others zero.g_nRookieTimeLimit=-1means "use the server's current time setting".ConLobbyMission.script: all hardcoded values inMAIL_SET_ROOKIE_MISSIONhandler 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|1under[mission]:- parser + script variable in
MW4Shell.cpp(g_nAutoNoReturn), - application in
ConLobbyMission.script(RESPAWN_LIMIT_PARAMETER), - docs + sample INIs updated.
- parser + script variable in
Docs updates:
BTFrstrm/autoconfig-file-spec.htmlcorrected for real behavior:- missing mission keys/section apply defaults (not current UI values),
- missing
MissionNamenow falls back to first map for that game type, - added
NoReturnsemantics 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 byVideoCard.cppLogDisplayDevices()):NumDevices/NumHWDevices/NumMonitors, every DirectDraw device +hw_rasterization, the role assignment (FullScreenDevice/g_nNonDualHead/g_nDualHead/g_nDualHead2/g_nMFD1/g_nMFD2),-tmonAPPLIED/REJECTED per slot, and the decisive "mode 4 requires BOTH mfd1 and mfd2" line.- Per-call HRESULT logging in
InitFirst/InitSecondviaHSH_LogInit()/HSH_CheckHR()/HSH_HRName()(27DDERR_*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-fpsswitch (WinMain.cppGOS_LogFrameRate, hooked onto the existingframeRateglobal). 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 ownAddDebugData("FrameRate")is#ifdef LAB_ONLY(MWMission.cpp) so it only exists inMW4pro.exe. Off unless-fpsis 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
WriteFilecost 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.txtis 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.
- ⚠️ 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
✅ 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]withECX=0=IDirectDrawSurface7::GetDCon a never-created surface (verified againstbuild-env\dx7asdk\include\ddraw.hvtable order). Reported asEXCEPTION : 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 everySetDisplayModeattempt 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 toMW4.exe/MW4pro.exe/MW4Ed2.exesitting 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 theHIGHDPIAWARE-only entry that suppresses the auto-shim (the original STEP 5 bug).-Remove/-WhatIfOnlysupported.deploy-mw4.ps1now 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 for -tmfds 1/3/4 by fixing flags. 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,4is identical to auto-detection. - Confirmed working end-to-end: all three secondary panels present, full mission played.
- ⚠️ The working
dgVoodoo.confis NOT versioned in the repo (it was removed in0ceba9c7). A fresh deploy will reproduce the silent MFD failure. TODO: commit it + havedeploy-mw4.ps1place it.
📐 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.Bltstretches 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 inDirectDraw.cpp, unused by the fullscreen path. - None of the drawing code changes — panels already render into
pDDSTargetand composite;DrawQuad/DrawTexture/fonts/SwapRightStatedon't care whether the present is Flip or Blt. - Work is confined to
render.cpp,DXRasterizer.cpp,WinMain.cpp(+ a littleWindows.cpp): panel init →DDSCL_NORMAL+ clipper + offscreen render surface (~150 lines); ~4 panelFlipsites →Blt; break theHSH_EnterFullScreen2()↔EnterFullScreenMode()coupling (DXRasterizer.cpp:~1132is 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_INVALIDOBJECTon Win11 and needed DDrawCompat; the game succeeded natively on the W4100). Version lockstep means one build must serve every pod. - Recommended staging: (1)
-borderlessfor the MAIN display only, default off; (2) convert one panel (radar) — the small, make-or-break test ofDDSCL_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
-2dtis not a recognised switch anywhere in the codebase, despite appearing in productionctcl.inilaunch lines. Completely inert. (2D targets are already the default;-3dtis what switches to the 3D model.)NumHWDevices(5) can exceedNumDevices(4) — it counts D3D device-enumeration callbacks, and an adapter exposing both a HAL and a T&L HAL yields two. Benign;InitSecondhardcodesIID_IDirect3DHALDevice.- New switches are documented in
-help(-fpsunder LOGGING AND DIAGNOSTICS,-tcoopunder DISPLAY AND VIDEO).
Next steps (proposed)
- Commit the working
dgVoodoo.confand havedeploy-mw4.ps1place it — without it a fresh 4-monitor deploy silently fails (see STEP 10). - (Decision pending) Borderless-windowed migration — see the staged assessment in STEP 10. Step 2 (one panel) is the cheap, decisive experiment.
- Test a full mission windowed (only the console/shell has been verified windowed).
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.binoutput 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 fixedyaw += 0.02fper frame (MechLab.cpp:285, called per-frame viaDoShellLogic) — no frame-time scaling. Fullscreen is vsync-locked (~60 FPS, flip chain) and unfocused-windowed gets GameOS'sSleep(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 setgos_Set_LoseFocusBehaviormode 3 (built-in 60 Hz cap,Windows.cpp:407) to pace the whole shell.