Commit Graph
94 Commits
Author SHA1 Message Date
e088555b96 Fix content defects found while decompiling V4H packages
Three unrelated source-data bugs surfaced while round-tripping our own
Content/Mechs tree against the decompiled V4H packages. All three were
found because a decompiler verifier disagreed with the shipped data, not
by inspection.

Hellspawn / Sunder: unterminated section headers
-------------------------------------------------
  hellspawn.subsystems:101   [HeatSink10  ->  [HeatSink10]
  sunder.subsystems:120      [HeatSink16  ->  [HeatSink16]

The notation parser keys a page on the bracketed name. With the closing
bracket missing the page name is malformed, so that heat sink's block is
not registered as its own page and its Model/ExecutionState/
InternalLocation keys are absorbed by the preceding page. Net effect: one
heat sink silently missing from each mech (Hellspawn LeftTorso, Sunder
LeftArm), and the preceding sink's location keys overwritten.

CauldronBorn: undefined macro in TwistRadius
--------------------------------------------
  cauldronborn.torso:15      TwistRadius=$(OBSTUSE_TRADIUS)  ->  100

OBSTUSE_TRADIUS is a typo and is defined nowhere in the define tables;
the intended symbol is OBTUSE_TRADIUS (=140). An undefined macro does not
fail loudly - the factory substitutes its own default - so this shipped
for years as a silent fallback.

Resolved to the literal 100 rather than $(OBTUSE_TRADIUS) because the
compiled .torso record in the shipped package holds 100.0f, i.e. the
factory default that has always been in effect. Using 140 would change
long-standing behaviour; 100 preserves it and makes it explicit. Verified
against the packaged record: verify_smallmodel now reports 1280/1280 keys.

Lesson recorded: an undefined macro is not a build error here. Check what
the compiled package actually holds before "correcting" a symbol name.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-08 16:47:29 -05:00
77e19a723c mw4print: document known issues and research notes
Captures all findings from the 2026-08-07 debugging session:
print flow diagram, DoPrint error codes, which commits changed what,
hypotheses explored/ruled out, and next steps once the -debug log
is available.

Co-authored-by: Claude Sonnet 4.6 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-07 21:55:35 -05:00
3256c103a2 mw4print: add -debug flag for step-by-step diagnostic logging
Adds runtime diagnostic logging to track down why printing is broken.
Pass -debug (or /debug) on the command line; all output goes to
mw4print-debug.txt next to the exe (append mode, timestamped lines).
No effect on normal operation when the flag is absent.

What is logged:
- Startup: AssetsDirectory1 path and full command line
- OnCreate: DB config loaded, timer created
- DoCopyData / OnFileOpen: each file queued for printing with filename
- OnTimer: which file is being processed each second, LoadPR result
- DoPrint: nPlayers count, PrintDlg call + result (on failure logs
  CommDlgExtendedError hex code), DB export call/result, SetupFonts
  result, StartDoc result, EndDoc vs AbortDoc, final return code

The CommDlgExtendedError value on a PrintDlg failure will identify the
root cause (e.g. CDERR_NODEFAULTPRN = no default printer configured).

Co-authored-by: Claude Sonnet 4.6 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-07 21:53:02 -05:00
dicion 10301ee5dd Document external MFD and radar mapping work 2026-08-07 20:56:58 -05:00
dicion deafc2b01b Make Behemoth II inherit validated display mappings
Copy Behemoth's validated texuv2, offset2, texuv3, and offset3 rows from Mech ID 11 to Behemoth II at Mech ID 12. Both variants intentionally share the same external MFD and Radar geometry.

Add an inherited-mapping assertion to the J&J comparison generator so future runs fail if any Behemoth II coordinate row diverges from Behemoth. Record the passing inheritance check in the generated summary.

Document the inherited variant relationship and effective mapping count in MFD-RADAR-MAPPINGS.md.

Compare hud/behemothii.bmp and radar/hud/behemothii.bmp against the corresponding Behemoth runtime assets. Both 512x512 grayscale images are already pixel-identical, so no hsh art files are copied or replaced.
2026-08-07 20:52:00 -05:00
dicion 9755803949 Fix external MFD and radar damage mappings
Install the validated J&J coordinates for 19 external display sets across 13 chassis. Update all affected texuv2/offset2 and texuv3/offset3 rows while preserving the 65-mech positional layout and coord.cpp CRLF encoding.

Add a reusable Pillow comparison generator, 38 red/green review maps, and a generated summary. The final audit reports 19 exact mappings, zero differences, zero input warnings, and seven display sets without complete supplied inputs.

Normalize four unambiguous measurement transcription issues: Behemoth MFD LT 1742 to 174, Behemoth Radar CT punctuation and blank S2, and Fafnir MFD LL punctuation.

Document the complete authoring and runtime workflow, including legacy tuple normalization, MFD/Radar scaling, odd-coordinate handling, canonical runtime names, pixel-versus-byte BMP comparison, validation checks, rebuild requirements, and the final installed-set inventory.

Compare every supplied exploded runtime BMP against Gameleap/mw4/hsh. All 19 are already pixel-identical, so no runtime art files are replaced.
2026-08-07 20:46:32 -05:00
dicion 13675c8cf4 move important documentation to main folder. 2026-08-07 13:38:39 -05:00
8bfaf9b9ad Publish V5.1.0b_RC2: rebuilt binaries, repacked props, RC2 documentation
RC2 build, verified to launch on the Windows box and copied back into the
deployment at MW4/. Supersedes RC1 (a0331e78), which predates everything in
sections 12-17 of the test checklist.

Binaries and content
--------------------
* MW4.exe / MW4pro.exe rebuilt from CoreTech GameOS + MW4Application. Carries
  the display work that landed after the RC1 build: -tident, the full
  gos-displays.txt start-up trace with the CLASH and CTCL-type reports, the
  HSH_EnterFullScreen2 panel re-entry fix, the -tmr cameraship ladder, and the
  updated -help text.
* Launcher / autoconfig / mw4print / ctcls / MissionLang / ScriptStrings
  relinked in the same pass.
* props.mw4 + props.dep fully repacked (deleted first, not incremental), so the
  23-entry time list, the restored 7-minute default and the V5.1.0b2 console
  title are actually in the package rather than only in the source tree.
* mw4-help.txt regenerated from the new exe; the diff is the proof the built
  binary carries the documentation changes.

Documentation, renamed to RC2
-----------------------------
* RELEASE-NOTES-5.1.0b_RC1.{md,html} -> ..._RC2.{md,html}, both hand-maintained
  in step, ASCII + CRLF so they open correctly in Notepad on a pod.
  - New "Already testing RC1?" block at the top. RC1's notes already described
    -tident and the CLASH report, but the RC1 BINARY does not contain them, so
    anyone comparing the two needed that stated explicitly.
  - Time limits corrected to 23 entries (1-15, 20, 25, 30, 45, 60, 120, 180,
    240) with the 7-minute default restoration called out.
  - New section on the cameraship Map/Armor screen: background but no overlays
    is -ctcltype 2 on a cameraship, not a video card. Includes the
    "CTCL type =" log line and -tmr 3 as the follow-up check.
  - -fps description corrected: the per-second column is a 5% low and the 1% /
    0.1% lows are in the session summary. The old text described behaviour that
    had already been changed.
  - Switch table gained -tmr; known issues gained stereo-only audio and the
    single-monitor -tident caveat; upgrade checklist now names V5.1.0b2.
* testing-checklist-5.1.0b1.txt -> testing-checklist-5.1.0b_RC2.txt, with a
  build-requirements header and new sections 12-17 covering -tident, the
  display trace (including a deliberate -tmon clash to exercise the CLASH
  report), the panel re-entry fix, cameraship/-tmr, 240-minute missions and a
  -help verification pass.
* OPTIONS-INI.md: TimeList_Index is documented as no longer driving the console
  lobby default (the script uses a literal since the list was expanded) while
  TimeList_Value remains live; added a table of the files the game writes next
  to MW4.exe. Also repaired the CP949/CP1252 damage in that file - it carried
  literal 0xA1 0xE6 arrows, 0x97 em dashes and ~20 '?' characters where dashes
  had been lost. Now pure ASCII.
* README.md points at the RC2 notes.

Source
------
MW4Application.cpp help text: -fps now matches what gos-fps.txt actually
prints, and -ctcltype states that a cameraship must use 3 and what goes wrong
when it does not. Audited all 80 switches parsed in the file against the help
array - none missing, and no game-facing switch is parsed anywhere else.

Deployment housekeeping
-----------------------
* dgVoodoo.conf: ScalingMode = stretched_ar, which is the setting the release
  notes require and which fails SILENTLY when wrong. FPSLimit was left at 20
  from bench testing and would have shipped a 20 fps cap to every Win10/11 pod
  - raised to 60.
* banner.txt reset from a test string to the shipped placeholder.
* Removed two stray screen000*.bmp captures and the duplicate dbstruct.txt
  (db_schema.sql is the current name, per b4089291).
* .gitignore: gos-displays.txt and gos-fps.txt are truncated on every launch
  and are per-machine, so they are no longer mirrored.

Not yet done: multi-monitor pod testing of this binary. -tident, the re-entry
fix and -tmr have not been exercised on real MFD hardware - that is what the
new checklist sections are for.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-07 13:07:43 -05:00
9bf49a8084 Bump console title to V5.1.0b2
Console lobby now reads "BattleTech Console V5.1.0b2", covering the
240-minute time limit work and the default-time fix on this branch.

Also corrects the testing checklist, whose section 5 still asserted the
V5.1.0b1 title and an 18-entry time list -- both would now fail on the
bench. Added a check for the 7-minute default, which was silently
landing on 4 before this branch's TIME_LIST_DEFAULT fix.

Content only, no MW4.exe rebuild. ConLobby.script lives in props.mw4, so
this needs build-env\build-resources.ps1 to appear in game.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-06 16:22:17 -05:00
136e2ff05c Extend MP mission time limit to 240 min (from LYLT 3514acf6)
Time-limit pick lists go to 23 entries: 1-15, 20, 25, 30, 45, 60, 120,
180, 240. Content + console only, no MW4.exe rebuild -- nothing between
the dropdown and the mission timer clamps the value, and m_gameLength is
8 bits on the wire (MWApplication.cpp:790), so 255 was always the ceiling.

Hand-applied rather than merged. LYLT reverted aa500be7 (9aa317ea) and
built on the original 9-entry list, while this branch carries the fixed
18-entry version from 456e1978, so the two sides have different bases.
Merging the branch would also drag in that revert of the Battlemaster and
Behemoth loadout work.

Two deliberate deviations from 3514acf6:

- Its max_displayed restructure is NOT taken. Both sides independently
  reached the same logic (i==5 -> 10, i==6 -> 4, else 16), but LYLT wrote
  the outer else without braces. That bare else-then-if is the construct
  behind the null-reference console lobby crash in the aa500be7
  regression; 456e1978 already fixed it here. Ours is kept.
- HostLobbyMission.script was untouched on this branch so LYLT's version
  is taken wholesale, but its else block (i==5 / i==11) is re-braced for
  the same reason before it can bite the PC host lobby.

Fixes a real bug this branch already had: TIME_LIST_DEFAULT was still the
C++ global g_nTimeList_Index (3), which indexed "7" in the old 9-entry
list but indexes "4" in the expanded one -- the default mission time had
silently become 4 minutes. Now the literal 6, which is "7" in both the
18- and 23-entry lists. Kept as a literal so the script stays independent
of the exe; the reset-to-defaults path matches by value (g_nTimeList_Value
= 7) and needed no change.

The rest scales on its own: drop_list_size[5], the doh loop bounds and the
nselected fallbacks in ConLobbyMission all already used TIME_LIST_COUNT /
TIME_LIST_DEFAULT symbolically. The DEMO_CODE list (11 entries, default 5)
is untouched. Verified 23 contiguous entries and balanced braces in both
files, no unbraced else-if left, CRLF and us-ascii preserved.

Not yet repacked: these live in props.mw4, so run build-env\build-resources.ps1
on the Windows box, deleting resource\props.mw4 + props.dep first to force a
full repack (incremental carries stale entries forward). Not run on a real bay.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-06 14:51:01 -05:00
020dc04612 Add HD-AUDIO.md: multi-channel audio investigation notes
Research only, no code changes. Captures why the pods play in stereo and
what it would take to drive a 4-speaker (quad) rig on Win10/11.

Key findings:
- "Channels" is ambiguous in this codebase: 32 mixer voices (Adept splits
  them 1 VO / 2 music / 1 Betty / 6 mechanical / 21 spatialized SFX) versus
  the speaker output, which is hardcoded stereo at 22050 Hz.
- The blocker is DS3DALG_HRTF_LIGHT, a two-speaker head model, requested
  unconditionally because dwFreeHw3DAllBuffers is always 0 on Vista+ where
  DirectSound hardware acceleration no longer exists. hardwaremixing=true
  is therefore inert, and the EAX reverb path has been dead since XP.
- GameOS has a full gosAudio_SetSpeakerConfig API that nothing ever calls.
  GetSpeakerConfig also has no 5.1 case, so it returns 0 on such systems.
- Cheapest route is DSOAL (drop-in dsound.dll over OpenAL Soft) plus
  hardwaremixing=true, which re-enables the existing hardware-3D branch
  with no code change. Verified in source that Libraries.cpp:376 calls
  LoadLibrary("dsound.dll") unqualified, and dsound.dll is not a KnownDLL,
  so a local copy next to MW4.exe wins over System32.
- Same deployment rule as dgVoodoo2: per-machine Win10/11 prerequisite,
  must NOT be committed, XP pods need the native dsound.dll.

Also documents the reverted fallback design (a third NO_VIRTUALIZATION
branch, a -tspk override and a gos-audio.txt trace) as a re-implementation
recipe, plus a verification plan and caveats.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-06 14:50:42 -05:00
b3c5ecfe56 CLAUDE.md: note two -tident/clash-check gaps found on a single-monitor VM
Parked, not fixed. Diagnostics-only and impossible on a real pod, but the
second one produces a confidently wrong "all clear" on exactly the kind of
machine someone would bench-test with, so it is worth recording.

Both stem from one physical monitor being exposed as two DirectDraw
devices. The NULL-device merge is gated on NumMonitors>=2 - deliberately,
it is a multi-monitor fixup - so a single-monitor box keeps the alias in
slot 0 alongside the real device.

1. -tident overpaints itself. Device 0 (NULL alias) and device 1 (the real
   output) resolve to the same screen, so it paints red/"1" and then
   immediately green/"2" over the top. Observed on a VM as a brief flash
   before the second number appears. Behaving as written; there simply is
   not a second screen to paint. Should detect devices sharing an HMONITOR
   and label them together rather than painting sequentially.

2. The consistency check compares device INDICES, not resolved monitors.
   On the same VM it reported "No duplicate device assignments" while
   main -> device 0 (primary alias) and radar -> device 1 (\\.\DISPLAY49)
   were the same physical monitor - the exact collision the check exists
   to predict. Fix would be to treat a device with no HMONITOR as the
   Windows primary and compare resolved HMONITORs.

Context for the VM in question: RDP session (Microsoft Remote Display
Adapter, 1870x985) with the Microsoft Basic Render Driver and no real GPU,
dgVoodoo2 masquerading as its default emulated NVIDIA device. MFD modes
cannot work there regardless - only two DirectDraw devices exist and one
of them is an alias.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-05 17:37:09 -05:00
22495d7245 Docs: research notes on making -window work for all display modes
Research only. Nothing implemented, nothing scheduled. Captured now so the
investigation does not have to be repeated when we come back to it.

New file: WINDOWED-MODE.md (repo root), indexed from README.md and the
CLAUDE.md next-steps list.

Goal being assessed
-------------------
Make -window work for every pod configuration, so a full pod (main + radar
+ MFDs, or a cameraship pair) can be tested on a single monitor as a set
of moveable windows. Target order: console (already works), cameraship,
-tmfds 1, -tmfds 4. -tmfds 3 deliberately out of scope.

Headline finding
----------------
-window does NOT fail for the MFD and cameraship modes - it silently
DISABLES them, and the reason is a single line:

  MW4Application.cpp:1373
    use_shgui = Environment.fullScreen ? 1 : 0;

use_shgui is the master switch for the entire secondary-panel subsystem
and is derived from fullscreen, so with -window it is 0 and
DXRasterizer.cpp:1131 never calls HSH_EnterFullScreen2(). No panel is ever
created. Console mode only appears to work windowed because it has no
panels to lose.

What the document covers
------------------------
* Why it behaves the way it does today, with the exact gating code.
* What already exists in our favour: the main display is a working
  windowed reference implementation (primary + clipper + offscreen
  backbuffer, presented with Blt); the clipper wrappers are already in
  use; and none of the panel DRAWING code would change, because panels
  render to an offscreen target and composite - they are indifferent to
  whether the present is a Flip or a Blt.
* The six things that must change, with quoted code: decouple use_shgui;
  windowed variants of CHSH_Device::InitFirst (DDSCL_NORMAL, no
  SetDisplayMode, per-panel HWND) and InitSecond (plain primary + clipper
  + offscreen backbuffer, D3D device on the offscreen); one window per
  panel; 8 Flip->Blt sites in WinMain.cpp; and explicit frame pacing.
* Frame pacing is called out as the item that will actually bite: sh_step
  advances once per frame and drives MFD channel cycling, so an uncapped
  windowed rig would not faithfully represent pod behaviour. Same root
  cause as the known mechlab fast-spin bug.
* Reference tables: every panel class with its InitFirst location, device
  slot and resolution; the SwapRightState member-swap mechanism that any
  windowed work must keep intact; and the full lifecycle/gating map.
* Suggested phasing, with radar-only as the decisive Phase 1 experiment.

The risk that decides it
------------------------
Windowed D3D7 device creation is per-GPU, and this would need four
windowed devices in one process. Precedent runs both ways: the mission
editor hit DDERR_INVALIDOBJECT creating a windowed D3D device on Win11 and
needed DDrawCompat (STEP 8), while the game's own windowed main display
succeeds on the W4100. Nothing has proven four. If it fails, the fallback
means rewriting the panel RENDER path, not just its present - a
substantially bigger job.

Why this may be worth more than a test convenience
--------------------------------------------------
Windowed mode removes exclusive-mode contention entirely, which is the
whole reason dgVoodoo2 is currently mandatory for every multi-display
configuration on Windows 10/11 (STEP 10). If windowed panels work
natively, that is a route to dropping the dgVoodoo2 prerequisite WITHOUT
breaking the XP pods, since it needs no external DLL on either OS. Same
groundwork as the borderless-windowed migration already parked in STEP 10.

Note on citations
-----------------
All 22 file:line references were derived directly and verified to resolve
to the expected symbol. An exploratory pass had produced line numbers that
were substantially wrong (CHSH_Device::InitFirst reported at 717, actually
627; IsMultimonitorAvaliable at 2178, actually 2961), and a claim that the
radar renders at 480x640 rotated - the code passes 640,480 like the
others. Worth knowing before trusting generated citations in a document
intended to outlive the session.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-05 17:31:09 -05:00
4e04fd1fb2 Displays: -tident identify mode, panel re-entry fix, desktop-order log
Follow-up to the gos-displays.txt enumeration trace. The expanded log
arrived from the Intel tester and settled the failure - and disproved the
hypothesis it was written to test.

What the log showed
-------------------
The consistency check printed "No duplicate device assignments", so the
suspected -tmon clash was NOT the cause. The actual evidence was that
HSH_EnterFullScreen2 runs TWICE: the first entry brings every panel up
DD_OK, the second fails on the same device with
DDERR_EXCLUSIVEMODEALREADYSET and then DDERR_NOCOOPERATIVELEVELSET.

Two independent causes, one operational and one a real defect.

Cause 1 - Windows Display Settings numbers are not DirectDraw indices
--------------------------------------------------------------------
The operator set -tmon by reading the numbers off the Display Settings
arrangement diagram. On that machine all three numbering systems differ:

  Settings 3 -> \\.\DISPLAY1 -> device 0   (primary, 800x600, main)
  Settings 4 -> \\.\DISPLAY4 -> device 3   (USB adapter, radar)
  Settings 2 -> \\.\DISPLAY2 -> device 1   (mfd1)
  Settings 1 -> \\.\DISPLAY3 -> device 2   (mfd2)

A permutation with two accidental fixed points - no derivable rule, and
the Settings ordinal is not exposed by any documented API, so it cannot
be translated in code. -tmon 1,4,2,3 worked first try (user-confirmed).
This is why Windows ships an Identify button rather than publishing the
mapping.

Cause 2 - panels re-opened without being released (REAL BUG, all machines)
-------------------------------------------------------------------------
EnterFullScreenMode() 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. CHSH_Device::InitFirst() therefore 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.

It 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 - which is the whole reason this looked hardware-specific.

FIX: HSH_EnterFullScreen2() now releases first when hsh_initialized ||
hsh_mrdev_initialized. Tagged [panelreinit]. Reuses the existing teardown,
which already restores the display mode, drops the coop level and clears
the flags for both the MFD/radar and cameraship paths.

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 device
index and the role currently assigned to it. Then exits.

Deliberately uses DDSCL_NORMAL and paints via GDI on the primary surface:
no exclusive mode, no display mode change. Taking exclusive fullscreen on
several devices at once is the very failure being diagnosed, and a
diagnostic that trips over that fault is worthless - this works even on a
pod where the MFD modes are broken.

It opens the real DirectDraw devices rather than positioning GDI windows
by HMONITOR, so it 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 as IdentifyDisplays() in VideoCard.cpp, called at the end of
FindVideoCards() followed by ExitProcess(0). Everything it paints is also
written to gos-displays.txt, so the mapping survives even if a monitor is
dead.

Also added: desktop-order block
-------------------------------
gos-displays.txt now prints the monitors sorted left-to-right by desktop
position with their device indices, which maps directly onto the picture
in Display Settings. For the reporting machine it reads out as
-tmon 1,4,2,3 with no derivation required.

Documentation
-------------
* -help: new -tident entry. The -tmon text now states outright that these
  are NOT Display Settings numbers and points at -tident. Its old
  "-tmon 1,2,3,4" example was actively inviting the mistake that caused
  this report, so that section was rewritten rather than appended to.
* Release notes (md + html, both hand-maintained): -tident section with
  sample output and the reason it has to exist; -tmon warning; new
  sections for the CLASH report and the re-entry fix in operator terms;
  switch-table row; expanded log description with the copy-before-relaunch
  warning; upgrade-checklist step to run -tident once after upgrading.
* CLAUDE.md: STEP 12 with the full engineering record, including the
  three-way numbering table and both causes.
* OPTIONS-INI.md: videodriverindex note now points at -tident.

Cost
----
-tident is opt-in and exits immediately after. The desktop-order block is
a handful of extra startup writes. Nothing added is reachable from the
frame loop.

Testing
-------
-tmon 1,4,2,3 confirmed working on the reporting machine, which validates
the diagnosis. The -tident and re-entry changes are NOT yet built or run;
both need a rebuild of MW4.exe (Release + Profile) as they touch CoreTech
GameOS. Worth checking on the W4100 bench first that
CreateSurface(PRIMARYSURFACE) under DDSCL_NORMAL succeeds on secondary
devices through dgVoodoo2 - if a panel comes up blank the log names the
failing call.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-05 15:00:37 -05:00
2e85cd2066 GameOS: full display enumeration trace in gos-displays.txt
Motivation
----------
A tester's 3-display Intel integrated-graphics box (main + 2 MFDs on the
iGPU, radar on a USB adapter) failed MFD bring-up with the second MFD
panel returning DDERR_EXCLUSIVEMODEALREADYSET, while the same build works
on a W4100 and on a Quadro. The old report showed only the final device
list and role numbers, which was not enough to tell whether the cause was
device count, capability, index shifting, or a duplicate assignment.

The report now traces every stage of display discovery in the order the
game performs it, so the cause can be read off the file instead of
inferred.

What gets logged
----------------
* Header: executable path, full command line, and the requested -tmfds /
  -tmon values already converted from the 1-based command-line form to
  the 0-based DirectDraw indices the code uses. That conversion is an
  easy off-by-one to miss: "-tmon 1,..." means device 0.

* Windows desktop topology via EnumDisplayDevicesA + EnumDisplaySettingsA
  - every \\.\DISPLAYn adapter, its friendly name, attached/primary
  state and current mode. Stated explicitly so it can be compared
  against the DirectDraw order, which is NOT the same ordering.

* Stage 1, enumeration callbacks: each callback as it fires, with driver
  description, driver name, GUID, and the HMONITOR resolved through
  GetMonitorInfoA to \\.\DISPLAYn plus desktop rect and primary flag.
  Entries with no HMONITOR are named as the primary-display-driver alias,
  noting that NumMonitors is not incremented for them.

* Stage 2, capability check: per device ACCEPTED (with assigned index and
  whether a HAL was found) or REJECTED.

* Device table printed THREE times - before the NULL-device merge, after
  it, and final. Each entry shows description, driver, DeviceGUID,
  guidDeviceIdentifier, vendor/device IDs, hardware-rasterizer flag and
  its monitor. Followed by an adapter grouping listing which device
  indices belong to the same physical card, since guidDeviceIdentifier
  identifies the ADAPTER and not the output - which is precisely why a
  single card and a mixed iGPU + USB adapter setup enumerate differently.

* Stage 3, the NULL-device merge: whether the NumMonitors>=2 guard
  passed, which device matched the primary's adapter GUID, and every
  index shift printed individually. If nothing matches, it says so
  loudly - slot 0 then remains the alias and any role pointed at device 0
  lands on the Windows primary monitor.

* Stages 4-7, role selection: span / main / radar / MFD searches, each
  printing the device chosen AND the reason every skipped device was
  skipped. Then the -tmon overrides, each showing what auto-detection had
  chosen and what the override replaced it with, or why it was rejected.

* Final summary: role -> device -> physical monitor, plus a consistency
  check that reports two roles landing on one device as an explicit
  CLASH, with the explanation that the second panel will fail with
  DDERR_EXCLUSIVEMODEALREADYSET. That is the suspected failure on the
  Intel box and nothing in the old log pointed at it.

Fixes
-----
* Monitor/device desync: the NULL-device merge shifted DeviceArray but
  nothing tracked which monitor each slot drove. g_ahDevMonitor[] is now
  shifted in lockstep; without this every monitor attribution after a
  merge would be off by one, i.e. confidently wrong rather than absent.

* HMONITOR is now retained. The Ex enumeration callback is the only place
  the device-to-monitor association is available and it was being
  discarded, so videoDevices gained an hMonitor field and BufferDevice
  takes it.

Behaviour
---------
No functional change. Every condition, code path and assignment is
unchanged - only logging was added, plus the monitor-array shift above.
-tmon still applies each slot independently and still does not reject
duplicates; the duplicate is now merely visible. Whether to make it an
error is a separate decision, pending test results.

Cost
----
Startup only. Nothing added is reachable from the frame loop, so there is
no runtime cost of any kind. Roughly 255 writes for a 4-monitor pod, at
~25-130 ms total on a warm cache - inside the noise of FindVideoCards()
itself, which creates DirectDraw objects and enumerates every display
mode per device. Worst case is antivirus on-access scanning of each
CreateFile, which could reach ~1 s; a folder exclusion removes it.

Lines remain open-append-close so the report survives a hard crash during
panel init, which is exactly when it is most needed. gos-fps.txt is the
buffered log; this one deliberately is not.

Portability
-----------
GetMonitorInfoA and EnumDisplayDevicesA are resolved with GetProcAddress
and the MONITORINFOEXA / DISPLAY_DEVICEA layouts are declared locally, so
nothing depends on the 1998 SDK headers carrying multimon support. If the
APIs are unavailable the report degrades gracefully instead of failing to
build. ENUM_CURRENT_SETTINGS is defined defensively.

Testing
-------
Not yet built. Requires a rebuild of MW4.exe (Release + Profile) as this
is CoreTech GameOS. Note the report is truncated on every launch - a
failing run's log must be copied before relaunching.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-05 10:07:11 -05:00
dicion e912c58843 README: rewrite with project background and history
Add Background section covering what FireStorm is, the full VWE /
BattleTech Center history from ESP (1988) through Tesla II FireStorm
(2002) and community continuation, and a summary of V5.1.x changes
since the 5.07D official release.

Reorganize build instructions under a dedicated 'Building from source'
heading. Demote Outputs / Design / _UNUSED from top-level headings to
subsections under the layout section.
2026-08-02 19:57:18 -05:00
dicion 07750da2fa Delete daf52e2c-8107-4f40-88df-a2744c8861f6.mr 2026-07-31 09:39:20 -05:00
24ce46ab6d Cameraship Map/Armor diagnostics: -tmr ladder, draw counter, CTCL type log
Investigating a field report that the cameraship secondary armor/score/map
screen showed its background BMP but no overlay graphics "on certain video
cards". The video card was a red herring: the machine was launched with
-ctcltype 2 (game pod) instead of 3 (cameraship), and the overlay block in
hudchat.cpp is gated on CTCL_GetType()==_ECTCL_CameraShip, so it never ran.

This is hard to spot because the two gates are independent. HSH_EnterFullScreen2
and CMR_Device open the screen and paint its background based purely on whether
a spare secondary monitor exists, and never consult the CTCL role. A wrong
-ctcltype therefore yields a screen that lights up, shows the correct artwork
and renders nothing, which is visually identical to a DirectDraw failure. The
same background-only screen also appears on a game pod when an MFD mode finds no
dual-head span and falls through to the single-secondary mr_device path.

Diagnostics added:

- render.cpp: log the CTCL type in gos-displays.txt from HSH_EnterFullScreen2,
  via the existing g_pfnCTCL_GetType hook so GameOS gains no game-code
  dependency. Always logged, and states in words whether overlays will be drawn.
- render.cpp/.hpp: CHSH_Device::m_nDrawCalls, incremented in DrawQuad,
  DrawThickFrame and DrawTexture, reset in CMR_Device::BeginScene and reported
  for the first 5 frames from CMR_Device::EndScene when -tmr is active. Zero
  draws proves the overlay code never ran; non-zero with a blank screen would
  mean it ran and drew invisibly. This distinction is what settled the case.
- render.cpp: log CMR_Device surface creation plus the overlay texture's
  dimensions, depth and channel masks.
- New -tmr <0-3> switch (MW4Application.cpp, documented in -help): 0 normal,
  1 background blit with DDBLTFAST_WAIT|DDBLTFAST_NOCOLORKEY, 2 Clear instead of
  the background blit, 3 as 2 plus alpha blending off and an untextured magenta
  DrawQuad.

Mode 3 drew magenta and mode 2 was pure black, which cleared the 3D device, the
flip and the background blit in two runs; the draw counter then reported 0 and
pointed straight at the gate. Documented as STEP 11 in CLAUDE.md, including the
field triage rule: read "CTCL type =" in gos-displays.txt before suspecting
drivers.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-26 11:59:56 -05:00
dicion b40892910b fix filename for db structure file to match the new naming convention. 2026-07-26 10:35:29 -05:00
0f901f71d5 Release notes: document which -tmon positions apply in each MFD mode
-tmon has four positions (main, radar, MFD1, MFD2) but only the first two do
anything in the spanned modes. Positions 3 and 4 write to the mode 4 MFD device
slots, which -tmfds 1 and -tmfds 3 never read -- they drive both MFDs from one
wide display instead. The switch accepts them either way, so an operator could
reasonably assume they had taken effect.

Also states what was previously undocumented: the spanned MFD display itself
cannot be assigned with -tmon at all. It is chosen automatically as the first
display advertising a 1280x480 mode, which is a distinctive enough signature that
it normally lands correctly. Noted that it can be added if anyone needs to force it.

Adds a per-mode table, a pointer to gos-displays.txt for diagnosing a display that
lands in the wrong place, and a qualifier on the -tmon row of the switch summary.

No code changes -- documentation only.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-26 10:23:42 -05:00
7fc9089aad Clarify dgVoodoo2 scope: any second display, cameraship included
The dgVoodoo2 requirement was written as an MFD-mode problem. It is not. The
limitation is per SECONDARY DISPLAY, not per feature, so it applies to every mode
that lights up a second monitor -- including cameraship mode, which uses two
displays and no MFDs at all.

Console mode is the only configuration that does not need dgVoodoo2, because it is
single-display.

This matches the code: IsMultimonitorAvaliable() returns false for
_ECTCL_CameraShip, so cameraship falls through to IsSecondaryMonitorAvaliable()
and the single-secondary mr_device path -- which is still a second exclusive
IDirectDraw7, and therefore still refused by modern Windows for exactly the same
reason as the MFD panels.

Release notes updated in all three places it appears -- section heading and opening,
the known-issues entry, and upgrade checklist step 4 -- so an owner running
cameraship pods cannot read the requirement as "only applies to MFD setups" and
skip it. CLAUDE.md STEP 10 and repo memory corrected likewise, since both stated
the narrower "-tmfds 1/3/4" scope.

Both release-notes formats regenerated; still plain ASCII with CRLF.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-26 10:20:10 -05:00
7f7f98d992 Release notes: soften dgVoodoo2 wording, note -tbaud is hardware-limited
Two wording corrections from the project owner, plus repair of three characters
left mangled by the earlier CP949 encoding problem.

1. dgVoodoo2 known-issue entry. "This is a Windows limitation, not a bug we can
   fix" overstated it -- it reads as permanent. Replaced with a statement of what
   dgVoodoo2 actually does (works around a limitation on exclusive full-screen use
   of multiple displays on platforms after Windows XP) and that a future release
   may address it. The borderless-windowed design assessed in CLAUDE.md STEP 10 is
   exactly such a route, so leaving the door open is the accurate framing.

2. -tbaud. The notes implied any rate in the 9600-921600 range simply works. The
   software will set any of them, but the achievable rate is bounded by hardware at
   both ends: the serial UART in the pod PC and the RIO or replica RIO board. Added
   that, with the symptoms of an unsupported rate (garbled input, dropped buttons,
   no response) and the advice to step down. Without it an owner could conclude a
   high rate is broken in the game when it is the UART or board refusing it.

Also repaired three leftovers from the CP949 encoding issue: two em dashes at ends
of lines that the earlier pass missed because it only matched dashes surrounded by
spaces, and the warning glyph on the MySQL export note, which had become a literal
"??" and is now a WARNING: label. It renders as a styled warning callout in the
HTML alongside the XP one, so both hazards look like hazards.

Both formats regenerated and verified: 0 non-ASCII bytes, 0 remaining artefacts,
2 warning and 5 note callouts in the HTML.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-26 10:16:00 -05:00
a0331e78d2 Compiled Build 5.1.0b_RC1 Files
Deployed game tree copied back from the Windows build machine, containing the
compiled 5.1.0b_RC1 binaries and every asset the deploy produces. 846 files:
21 added, 825 modified.

Freshly built binaries: MW4.exe, Launcher.exe, autoconfig.exe, ctcls.dll,
MissionLang.dll, ScriptStrings.dll, Language.dll and the rest of the runtime DLL
set, carrying everything merged this cycle -- the 16-pilots-plus-cameraship launch
fix, the MFD mode 4 split-display support and its stutter fix, -tmon, -tcoop, -fps,
-tbaud, the Load File console lobby feature, configurable Rookie Mission, and the
English Language.dll build.

New in the deployment:
- set-appcompat.bat / set-appcompat.ps1 -- the one-click AppCompat shim installer,
  now shipped by deploy-mw4.ps1 so any copy of an install can repair its own
  registration (the layer is keyed on the exe's full path).
- libmysql.dll -- required by mw4print 2.0's MySQL export, late-bound at runtime.
- Four hsh art files restored from 5.0.7D: MFD/assassin2.bmp, Mechs/battlemaster
  iic.bmp, Mechs/behemoth ii.bmp, Mechs/mad cat mkii.bmp.
- banner.txt, dbstruct.txt -- mw4print banner text and the exported DB structure
  reference.

dgVoodoo2 is included as MW4/dgvoodoo2_files/ -- deliberately in a subfolder and
NOT alongside the executable. Nothing loads from there, so the files are available
for a pod owner to install on Windows 10/11 without being active by default. This
keeps XP pods safe: they use native DirectDraw, and having dgVoodoo2 loose in the
game folder would break them. Do not move these files up a level in the repo.

Runtime leftovers from testing on the build machine are included as well
(DDrawCompat logs, DebugLog.txt, mw4-help.txt, two screenshots), consistent with
this repo's stated purpose as a full disaster-recovery mirror in which nothing is
excluded on purpose.

All binaries and art are stored via Git LFS per .gitattributes; verified that
MW4.exe, libmysql.dll, the dgVoodoo2 DLLs and the screenshots staged as LFS
pointers rather than raw blobs.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
firestorm
2026-07-25 19:52:43 -05:00
1b931d5fb8 Correct dgVoodoo2 guidance: never ship it, XP pods must not have it
Two corrections from the project owner.

1. dgVoodoo2 must NEVER be added to the repo or the deploy script.

The pod fleet is mixed. XP pods use native DirectDraw and need nothing extra --
installing dgVoodoo2 on them BREAKS them. Only Windows 10/11 pods need it, and
only for the MFD modes. It is therefore an installed-per-machine, OS-dependent
prerequisite that the pod owner sets up, not a build artifact.

This reverses the recommendation added in the previous commit, which suggested
versioning the working dgVoodoo.conf and having deploy-mw4.ps1 place it. That
advice was wrong: it would have pushed a Win10-only dependency onto every XP
machine. dgVoodoo.conf and dgVoodooCpl.exe were removed in 0ceba9c7 deliberately
and must stay out.

- CLAUDE.md STEP 10: the "TODO: commit it" note is replaced with the reasoning,
  flagged so nobody re-adds the files later thinking it was an oversight.
- CLAUDE.md Next steps: that TODO is removed. The borderless-windowed item now
  notes it is the only route that removes the dgVoodoo2 prerequisite WITHOUT
  breaking the XP pods, since it needs no external DLL on either OS.
- Release notes gain an explicit warning callout: dgVoodoo2 is not part of the
  build and must not be installed on XP pods, with the advice to configure it per
  machine rather than inside the game folder that gets copied around. Without this
  an owner with a mixed fleet could reasonably have copied a working Win10 install
  onto an XP pod and broken it.

2. Version numbering is intentional, not a mismatch.

V5.1.0b<n> denotes beta build n; these are handed out as release candidates for
testing. The "b" is dropped and it becomes 5.1.0 when the release is final. So the
console string V5.1.0b1 alongside a release named V5.1.0b_RC1 is expected, and the
earlier suggestion to reconcile them is withdrawn.

Both release-notes formats regenerated; the XP warning renders as a warning
callout in the HTML. Both remain plain ASCII with CRLF.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-25 19:47:26 -05:00
10dab8f2d5 Add V5.1.0b_RC1 end-user release notes (markdown + HTML)
Release notes covering the 58 commits since dbcf4052 (-tbaud), written for pod
owners rather than developers: what changed, what the defaults are if they touch
nothing, and how to switch on anything new.

Two formats. The HTML is generated from the markdown so the two cannot drift, and
carries the same inline styling as BTFrstrm/autoconfig-file-spec.html -- no external
files, opens in any browser, prints cleanly. Both are plain ASCII with CRLF so they
open correctly in Notepad on a pod.

Structure leads with the two things that will otherwise generate support traffic:
running set-appcompat.bat after install (the AppCompat layer is keyed on the install
path, and its absence produces the misleading "Another application is preventing use
of full screen mode"), and the dgVoodoo2 scaling mode, which fails silently when set
wrong. Every command line switch is listed with an explicit "if you leave it off"
column.

Facts checked against the source while writing, and corrected:

- [RookieMission] key names. The first draft documented RookieMission=, RookieGameType=
  and so on -- the internal variable names. The game actually reads MissionName,
  GameType, TimeLimit, Visibility, Weather, TimeOfDay, Radar, HeatOn, FriendlyFire,
  SplashDamage, UnlimitedAmmo, WeaponJam, AdvanceMode, ArmorMode. Anyone following the
  draft would have edited options.ini and seen no effect with nothing to explain why.
  The section now carries the full annotated block verbatim from options.ini, and all
  14 documented keys are machine-verified against MW4Shell.cpp.

- [automaticmode] needs TWO keys, not one. automaticmode=1 shows the Load File button;
  automaticfile=<path> tells it what to load. Without the second, CTCL_LoadAutoFile
  returns immediately -- the button appears and silently does nothing. Both are now
  documented in a table, with the path rules (bare filename resolves next to MW4.exe,
  or give a full path).

- dgVoodoo2 is required on Windows 10/11 for EVERY MFD mode, including the original
  spanned display (-tmfds 1 and 3), not only the new split mode (-tmfds 4). The first
  draft implied it was specific to the new feature, which would have led owners running
  the span to skip it.

- The mw4print MySQL export is flagged as included but not yet tested against a live
  server. Everything else in the notes is confirmed working by the project owner.
  libmysql.dll ships in the deployment (Gameleap/mw4, kept by the deploy's root *.dll
  rule and not on the skip list), so there is nothing for owners to download.

Also repaired 23 em dashes that had been written as literal '?' characters, including
in headings -- a side effect of the workspace saving new files as CP949, which cannot
represent them. Both files are now pure ASCII.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-25 19:22:54 -05:00
2f4ed244ea Fix -fps logger measuring its own overhead; buffer output, fix percentiles
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>
2026-07-25 18:43:42 -05:00
9c67dddd83 Display diagnostics + -fps/-tcoop; native multi-monitor MFD ruled out
Four-monitor MFD bring-up on the new bench (MR_new: AMD FirePro W4100, 4 outputs,
Win10). Adds permanent display diagnostics, a Release-capable frame pacing logger,
a self-locating AppCompat shim installer, and settles -- empirically -- whether
dgVoodoo2 can be dropped for the multi-monitor MFD modes. It cannot.

WHY THIS WAS HARD
-----------------
CHSH_Device::InitFirst/InitSecond discarded EVERY 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 just stayed on the desktop. SPEW is
compiled out of shipping builds, so none of it was visible. Restoring that
visibility is what unblocked everything else.

DIAGNOSTICS ADDED (keep these)
------------------------------
* VideoCard.cpp -- LogDisplayDevices() writes gos-displays.txt next to the exe:
  NumDevices/NumHWDevices/NumMonitors, every DirectDraw device with its
  hw_rasterization flag, the role assignment (FullScreenDevice / g_nNonDualHead /
  g_nDualHead / g_nDualHead2 / g_nMFD1 / g_nMFD2), per-slot -tmon APPLIED/REJECTED
  (previously silent), and whether mode 4's "BOTH mfd1 and mfd2" requirement is met.
* render.cpp -- HSH_LogInit()/HSH_CheckHR()/HSH_HRName() log every DirectDraw call
  in the panel init path with its HRESULT decoded by name (27 DDERR_* codes, all
  verified present and collision-free against build-env/dx7asdk/include/ddraw.h).
  Each line is opened/appended/closed individually so the log survives a crash.
* WinMain.cpp -- GOS_LogFrameRate() writes gos-fps.txt: per second, frame count,
  average fps, 1% low (mean of the worst 1% of frames), worst frame in ms, and a
  count of frames exceeding 2x average ("hitches"). Average fps alone cannot
  distinguish 60fps from 60fps-with-a-dropped-frame-every-second; the 1% low can.
  The engine's own FrameRate readout is #ifdef LAB_ONLY (MWMission.cpp) so it only
  exists in MW4pro.exe; this works in Release, which is what runs on the pods.

NEW SWITCHES (both documented in -help)
---------------------------------------
* -fps    Enable the frame pacing report. Off by default: the gate is the first
          statement in GOS_LogFrameRate, so an unflagged run does no arithmetic
          and does not even create the file. The file handle is held open for the
          process lifetime -- opening/closing it every second would put a syscall
          of unpredictable latency on the render thread, i.e. the measurement tool
          perturbing what it measures.
* -tcoop <0-5>  Selects the SetCooperativeLevel form used by the radar/MFD panels.
          0 = legacy (unchanged shipped behaviour, remains the default).
          Exists so every candidate fix could be compared on real hardware without
          a rebuild between attempts.

CRASH-SAFETY FIX
----------------
hsh_initialized was set unconditionally after panel init, so a failed panel 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 its real result instead of always returning true,
  - hsh_initialized is only set when the panels genuinely came up.
A display failure now leaves the game running without MFDs instead of bombing to
desktop. Crash signature for the record: `call [ecx+0x44]` with ECX=0 is
IDirectDrawSurface7::GetDC on a never-created surface (vtable offset confirmed
against the DX7 header), reported as "Attempt to read from address 0x00000044".

APPCOMPAT SHIM INSTALLER (new)
------------------------------
build-env/set-appcompat.ps1 + set-appcompat.bat. Self-locating via $PSScriptRoot:
applies DWM8And16BitMitigation to the MW4 executables sitting next to it, wherever
that install lives. HKCU always, HKLM too when elevated (the HKLM value format
differs -- it carries a leading "$" marker -- so the two must not be interchanged).
Verifies by reading back; detects the HIGHDPIAWARE-only entry that SUPPRESSES the
automatic shim; supports -Remove and -WhatIfOnly. deploy-mw4.ps1 now ships both
files into every deployment.

This matters because the layer is keyed on the executable's FULL PATH -- any copy
of an install to another folder or machine silently loses it, and the resulting
error is actively misleading (see below).

WHAT WE LEARNED
---------------
* The AppCompat shim SYNTHESISES 16-bit display modes. Proved directly: the crash
  dump shows "16 bit modes :" EMPTY without it and fully populated with it. MW4
  renders at bitdepth=16 and modern GPUs expose no 16-bit modes at all.
* Without the shim, GameOS reports "Another application is preventing use of full
  screen mode" (GOS_DXRASTERIZER_NOFULLSCREEN, DXRasterizer.cpp ~1125). That is a
  catch-all fired after every SetDisplayMode attempt fails -- it even scans for
  NetMeeting -- and it sends you looking for a conflicting program that does not
  exist. The real cause is the missing shim.
* Exclusive fullscreen DOES work on Win10 with the system ddraw.dll and dgVoodoo2
  physically removed, once the shim is applied to that exe path.
* Windowed mode works natively with no shim at all: the windowed path sets
  Environment.bitDepth = DesktopBpp (32), so there is no mode switch. Verified for
  the console/shell; a full mission windowed is still untested.
* Native DirectDraw enumerates all four W4100 outputs, so dgVoodoo2 was never
  needed for device enumeration.
* The engine is 4:3 ONLY. ImageHlp.cpp ~464 asserts the complete supported set:
  640x480, 512x384, 800x600, 960x720, 1024x768, 1280x1024 (5:4), 1600x1200. No
  16:9 mode and no aspect correction anywhere in the codebase. On a 16:9 monitor
  the scaler must adapt: plain stretch distorts geometry, keep-aspect pillarboxes.
* -2dt is not a recognised switch anywhere in the codebase, despite appearing in
  production ctcl.ini launch lines. Completely inert.
* 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.

WHAT WE TRIED AND WHY IT FAILED
-------------------------------
The panel 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, the same
author. The panels never were.

Results on real hardware, no dgVoodoo2, shim applied, -tmfds 4:

  -tcoop 0  SETFOCUSWINDOW|CREATEDEVICEWINDOW|ALLOWREBOOT|EXCLUSIVE|FULLSCREEN
            -> DDERR_EXCLUSIVEMODEALREADYSET
  -tcoop 1  CREATEDEVICEWINDOW|EXCLUSIVE|FULLSCREEN (no focus claim)
            -> DDERR_INVALIDPARAMS (CREATEDEVICEWINDOW needs a focus window)
  -tcoop 2  SETFOCUSWINDOW, then CREATEDEVICEWINDOW|EXCLUSIVE|FULLSCREEN
            -> DDERR_INVALIDPARAMS
  -tcoop 3  SETFOCUSWINDOW, then EXCLUSIVE|FULLSCREEN
            -> first panel collides, but that collision STEALS exclusive mode from
               the main display, after which panels 2 and 3 fully initialise (the
               radar reached CreateDevice(HAL) = DD_OK -- a secondary panel running
               entirely on native DirectDraw). Side effect: the desktop was left at
               1920x1080 16bpp. Not viable.
  -tcoop 4  EXCLUSIVE|FULLSCREEN only          -> EXCLUSIVEMODEALREADYSET, all panels
  -tcoop 5  ALLOWREBOOT|EXCLUSIVE|FULLSCREEN   -> EXCLUSIVEMODEALREADYSET, 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 and is not bound by that rule -- it is not papering over a bug we can fix.

=> dgVoodoo2 CANNOT be removed for -tmfds 1/3/4 by correcting these flags. The
default stays -tcoop 0. The switch is retained because it is how this was settled
and it will re-settle it on different hardware.

The only native path is a borderless windowed panel design (DDSCL_NORMAL + clipper
per monitor, no exclusive mode anywhere). Assessment and staged plan are recorded
in CLAUDE.md STEP 10; not started.

WORKING 4-MONITOR CONFIG (with dgVoodoo2)
-----------------------------------------
dgVoodoo2 Scaling mode MUST be "Stretched, Keep Aspect Ratio". Plain "Stretched"
fails silently: main and 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), which also established that device index maps
1:1 to physical monitor on this bench, so -tmon 1,2,3,4 equals auto-detection.
Confirmed working end to end: all three secondary panels present, full mission
played.

KNOWN GAP: the working dgVoodoo.conf is still not versioned in the repo (removed
in 0ceba9c7), so a fresh deploy will reproduce the silent MFD failure.

Behaviour with no new switches supplied is unchanged from the previous build
except on failure paths, which now degrade gracefully instead of crashing.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-25 18:22:31 -05:00
f402e5becc Add OPTIONS-INI.md reference
options.ini had no documentation. This documents every section and key, compiled
by reading the actual read sites rather than by describing the shipped file, so
that settings which look active but are not are identified as such.

Covers [graphics options], [sound options], [special commands], [server],
[joystick], [Cameraship Params], [Battle Tech Misc], [RookieMission] and
[battle tech print], plus how the file is loaded, which parts the game writes
back, and the per-role options-game/cam/mr variants.

Notable findings, all verified against source:

- The entire [network options] section is DEAD. None of connectiontype,
  connectionspeed, packetsize, defaultconnection, playericon or teamicon is read
  by any code; the connection type actually used comes from the multiplayer
  connection wizard. These are stock MW4 leftovers.

- Several [Battle Tech Misc] keys have names that do not describe what they do,
  and the real meanings are now recorded: RuleBook sets g_nMechVariant, DawnWar
  sets g_nMechLabOp, BiggieSizeIt sets g_nMechPodNum (the console lobby's larger
  roster flag), and CanYouHearTheFootSteps sets g_nBlackMech.

- The shipped options.ini misspells two keys. It contains secmissionreplay and
  secmissionreport, but the code reads SecsMissionReplay and SecsMissionReport.
  The shipped values are therefore ignored and the compiled defaults apply.

- Three keys are read and then immediately overridden, so editing them does
  nothing: videodriverindex (device forced to 0), huddamagemode and
  hudtargetdamagemode (both forced false). Their GetEntry calls are commented out.

- [special commands] killgame is a self-clearing kill switch: if true at startup
  the game rewrites it to false, saves options.ini and exits immediately.

- maxplayers/maxbots are serialised to clients as 5-bit fields, so 31 is the
  maximum usable value; cross-referenced to RAISING-PLAYER-CAP.md.

Also records why a typo produces no diagnostic: GetEntry returns false and the
compiled default is kept silently. Page and key lookup were confirmed to be
case-insensitive (NotationFile::FindPage lowercases; Page::FindNote uses
_stricmp), so the capitalisation differences between the shipped file and the
code are harmless -- the misspellings above are not.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-25 08:54:28 -05:00
308fe041d4 Make -help a complete command line reference
MW4.exe accepts 76 command line switches. Only 27 of them appeared in the old
-help output, that output went to the debug log rather than to the user, and it
did not stop the game from launching afterwards -- so in practice the switches
were undocumented.

-help now writes a full, categorised reference to mw4-help.txt next to the
executable, opens it in Notepad, and returns from WinMain without starting the
game. If Notepad cannot be launched, a message box reports where the file was
written. The check runs immediately after the command line is lower-cased at the
top of WinMain, before any subsystem is initialised.

A file plus a viewer was chosen over a message box because MW4 is a GUI-subsystem
application with no console, and 76 switches with real descriptions do not fit
legibly in a dialog.

The reference documents every switch actually parsed, grouped as: display and
video, audio and plasma display, pod hardware and arcade (CTCL), zoom and field
of view, multiplayer and network, logging and diagnostics, development and test
builds, and other. Each entry records the accepted value range where the parser
enforces one -- for example -tbaud 9600-921600, -armorlevel 0-4, -tmfds 0-4,
-zmfovb 0.01-0.5, and the -zmtime special case where 0 means instant.

Two switches are listed under "recognised but inactive" so their behaviour is not
misrepresented: -join, whose consuming line is commented out, and -noabzug, which
is only parsed inside a disabled code path. The LAB-only switches are marked as
accepted and ignored in Release builds.

The old partial SPEWALWAYS list in GetGameOSEnvironment was removed so there is a
single maintained reference rather than two that can drift apart. A comment there
points at the new one.

Also adds an explicit #include <stdio.h>; this translation unit previously had no
direct stdio use and relied on transitive inclusion.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-25 08:54:10 -05:00
0009f868eb RAISING-PLAYER-CAP.md: sharpen the drop-zone wording
The previous correction overstated the case by implying the code reading was
wrong outright. It was not. The engine really does place surplus 'Mechs on
already-occupied spawn points -- two 'Mechs dropped on the same spot -- exactly
as reading the code suggests.

The only wrong part was the predicted consequence. The original draft said those
players "silently fail to spawn". They do spawn; the collision system then pushes
the stacked 'Mechs apart within a second or two, costing some minor contact
damage, and play continues normally.

Reworded Layer 7 and the known-traps entry to separate the two claims: the
spawn-point reuse is real and code-predictable, the failure-to-spawn conclusion
was not.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-24 23:50:12 -05:00
33c28cf816 RAISING-PLAYER-CAP.md: correct the drop-zone analysis
The original draft claimed that players beyond a map's available drop zones
"silently fail to spawn", and called per-map drop-zone authoring the true gating
task for raising the player cap. That was inferred from reading the code and is
wrong.

Corrected from real pod testing: when there are more 'Mechs than drop zones, the
surplus 'Mechs spawn on top of each other. They clip and collide briefly, take
some minor damage, then separate and play normally. It resolves itself within
seconds.

So a drop-zone shortfall is a quality-of-experience issue, not a functional
failure. It does not block raising the cap and should not gate the schedule.
Adding start points to busy maps is still worth doing eventually -- overlapping
spawns are untidy and hand out free chip damage -- but it can happen at any point
and never needs to be complete.

Updated accordingly: the TL;DR table, Layer 7, the implementation order (drop
zones moved from step 5 to last and marked optional), the verification checklist,
and the known-traps list.

The biggest remaining non-code task is now the lobby pod-grid and scoreboard
layout rework.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-24 23:49:01 -05:00
a41dfb4aa8 Add RAISING-PLAYER-CAP.md reference
Captures the full audit of every player-count limit in the codebase, done while
tracking down the "16 pilots + 1 cameraship" launch failure. Research only --
nothing in it is implemented.

Key finding: the practical ceiling without a wire-format change is 31, not 32.
m_maxPlayers and m_maxBots are serialized as 5-bit fields in
NetMissionParameters, so 32 truncates to 0. This supersedes the "32" figure in
the existing CLAUDE.md plan sections.

Documents, with file references:
- what is NOT a limit (Adept::Maximum_Players is 255, connectionID is a BYTE,
  DirectPlay imposes nothing, and there is no 32-bit player bitmask)
- the 5-bit serialization ceiling and what widening it would cost
- the compiled defaults in CTCL_DefaultHostSetup that actually gate connections
- the CTCL roster arrays, including that g_aPlayerInfos[20] has NO bounds check
  in CTCL_AddPlayer and that ctcl.h is duplicated across ~6 directories
- MAX_LANCEMATES 16 for bots
- the lobby script constants and the pod-grid UI work
- scoreboard/radar/review layout work
- per-map drop zones, which is the real gating task and produces silent spawn
  failures when short
- the O(n^2) replication cost, reframed as verify-don't-assume on modern hardware

Also records the failure signatures to expect, so a future attempt recognises
them quickly: silent launch hang from a count mismatch, silent non-spawn from
missing drop zones, and 5-bit truncation looking like "max players became zero".

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-24 23:40:14 -05:00
9ba2d594b1 Add -tmon switch to hard-assign display devices
Display roles are auto-detected in FindVideoCards(): the main view takes the
first hardware-rasterizing device that is not a 1280x480-capable span, the radar
takes the next, and mode 4's two MFD panels take the two after that. That works
until DirectDraw enumerates the adapters in an unexpected order, at which point
the wrong content appears on the wrong monitor with no way to correct it.

Adds an override that keeps auto-detection as the default:

    -tmon <main>,<radar>,<mfd1>,<mfd2>

Values are 1-based, so the normal case is "-tmon 1,2,3,4". A value of 0 leaves
that slot auto-detected, so "-tmon 2,1,0,0" swaps only main and radar. Separators
may be ',' '/' or ':'. mfd1/mfd2 are only meaningful with -tmfds 4. Omitting the
switch entirely preserves existing behaviour exactly.

Implementation:

- Parsed in MW4Application's WinMain alongside -tmfds. That runs before GameOS
  calls FindVideoCards(), so the values are in place for device selection.
- Stored in g_naMonitorOverride[4] (VideoCard.cpp), extern'd in MW4Application.cpp.
- ApplyMonitorOverride() validates the index against NumDevices, so a stale -tmon
  on a machine with fewer monitors falls back to auto-detection rather than
  breaking startup.
- Ordering matters and is deliberate: main and radar are applied BEFORE the mode 4
  MFD search so that search still skips whichever devices the operator picked;
  mfd1/mfd2 are applied after it. Partial overrides therefore compose correctly.

Role map: main = Environment.FullScreenDevice, radar = g_nNonDualHead,
mfd1 = g_nMFD1, mfd2 = g_nMFD2. The mode 1 span (g_nDualHead) is intentionally
not overridable -- it is detected by 1280x480 mode support, which only the span
card advertises, so that detection is reliable.

Also adds a SPEW line logging the final role -> device map and device count.
Note this is only visible in Profile/LAB builds; SPEW compiles out in Release
(gos2X/Gos.h), so in Release the order is determined by observation.

Verified: compiles clean, console launches. Multi-monitor testing pending.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-24 23:39:59 -05:00
0a657b5998 Fix constant rhythmic stutter in MFD mode 4 (split dual 640x480)
-tmfds 4 splits the 1280x480 MFD span into two independent 640x480 monitors so
the span hardware is no longer required. It rendered correctly but stuttered
rhythmically and constantly, making the game unplayable. -tmfds 1 on the same
binary was flawless.

Root cause: a cross-DirectDraw-object texture read, twice every 7-frame cycle.

CHSH_Device::InitFirst with no pOtherHSHD peer creates its OWN IDirectDraw7 (via
wDirectDrawCreateEx on that monitor's device GUID) and InitSecond creates its OWN
primary flip chain and IDirect3DDevice7. CMFDRight_Device::InitFirst passes no
peer, so the right MFD is an entirely separate DirectDraw object.

EndChannel then did, for channels 3-4:

    target->pD3DDevice->SetTexture(0, pDDSTarget);

where target->pD3DDevice belonged to the RIGHT device but pDDSTarget was the LEFT
device's render-target texture. The old code comment asserted "both devices are
on the same GPU so VRAM textures are mutually accessible" -- that premise is
wrong. In DirectDraw 7 a surface belongs to the IDirectDraw7 that created it, not
to the physical GPU, so it is not a valid texture on another object's D3D device.

The MFDs still displayed, which means the runtime was emulating the access with a
VRAM -> system-memory readback and re-upload of the 1024x512 16-bit render target.
That forces a full GPU pipeline stall, and it happened on channels 3 and 4 (that
is, sh_step 5 and 6) -- twice per 7-frame cycle, on the same GPU drawing the main
view. Hence a fixed-period hitch in the whole game, forever.

This also explains why the earlier stagger work (eaa5fd3, BeginSceneRight) did not
help: it only moved a Flip from sh_step 0 to 1, and the flips already used
DDFLIP_DONOTWAIT|DDFLIP_NOVSYNC and never blocked. The flips were never the
problem.

Fix: give the right device everything it draws with, and render channels 3-4
entirely on it.

- New HSH_CreateMFDTextures() builds the mech image atlas and the MFD sprite atlas
  on a caller-supplied IDirectDraw7. Both devices now call it, so each owns a
  complete independent texture set. CMFD_Device::InitSecond was refactored onto it.

- CMFDRight_Device gained its own pDDSMechTexture / pDDSDamageTexture /
  pDDSTargetTexture plus a Release() override, and its InitSecond now sets up
  tw/th and the material/render state exactly like the left device.

- New CMFD_Device::SwapRightState() exchanges this object's DATA members with the
  right device's. BeginChannel swaps in when channel >= 3 in mode 4; EndChannel
  swaps back. This routes all existing drawing to the correct monitor without
  touching the ~233 mfd_device.* call sites in hudchat/huddamage/hudweapon/
  GUIRadarManager. The vtable pointer is deliberately never swapped, so virtual
  dispatch is unaffected; CHSHFont has no virtual functions so its array is
  swapped bytewise to avoid ctor/dtor side effects on a temporary.

- EndChannel's composite is now a single path for all modes. Mode 4 composites
  full 640 width at x=0 (each device is a standalone panel); modes 1-3 keep the
  half-width (ch/3)*w packing into one backbuffer.

Side effects: startup builds the 65-bitmap mech atlas twice (once per device), and
VRAM use rises by a few MB. Modes 0-3 are behaviourally unchanged.

Known cosmetic leftover, deliberately not changed: huddamage.cpp lines ~1319 and
~1901 call LoadTargetTexture outside the channel-3 block, so those loads land on
the left device and go unused. The in-channel call at ~2209 runs every frame in
that branch and correctly populates the right device's copy, so behaviour is
correct -- it is just a redundant load on target change.

Requires rebuild: MW4.exe (GameOS changes recompile the engine library).
Verified: compiles clean, console launches. Two-monitor testing pending.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-24 23:39:42 -05:00
29aee4f71b Fix 16 pilots + 1 cameraship failing to launch
A full 16-'Mech roster plus a cameraship silently refused to launch: the console
sat in nLaunchState 3 ("loading") forever with no crash and no error. 15 mechs +
camera worked, and 16 mechs with no camera worked.

Two independent bugs, both counting the cameraship against the 16 'Mech slots.

1. Session capacity (broke all-human rosters)

   CTCL_DefaultHostSetup derived the camera reserve from
   CTCL_GetTeslaCountAll() - CTCL_GetTeslaCount(). Those counters read the CTCL
   tesla table, which is only populated when CTCL_IsConsoleOrCOOP() is true --
   i.e. only on the console. But the machine that creates the network session is
   the cameraship pod (CTCL_DoMission sets g_nServer = nCameraship, and that pod
   runs CTCL_DoCreateGame -> CTCL_DefaultHostSetup(0) -> Mech4CreateGame ->
   gos_CreateGame(..., Environment.NetworkMaxPlayers, ...)).

   On that pod both counters return 0, so the reserve collapsed to +0 and the
   session was created with dwMaxPlayers = 16. The 17th connection was refused,
   CTCL_CheckServerReady never saw nCount == g_nTeslas + 1, and the launch hung.

   Fixed by reserving with a constant, MW4_CAMERASHIP_RESERVE (4, matching
   MAX_CAMERAS), which is valid on every machine regardless of the tesla table.

2. Bot admission (broke any roster containing bots)

   MW4Shell::AddBot rejects when (player_count + bot_count) >= m_maxPlayers.
   player_count is the DirectPlay player count, which includes the cameraship
   connection, so with m_maxPlayers = 16 the last bot was silently refused.
   g_nBOTs then never matched the connected lancemates and the same readiness
   check spun forever.

   Fixed by subtracting cameraship participants from player_count under CTCL,
   via a new CTCL_CountCameraShipsInGame() helper (non-bot entries with
   m_nMechIndex == 0). Cameraships hold a network slot but pilot no 'Mech, so
   they must not consume a 'Mech slot.

Also applies the constant reserve in the PLAYER_LIMIT_PARAMETER path, guarded by
!CTCL_IsNone() so a standalone non-pod host keeps its exact configured limit.

This supersedes commit f76dc05f, which had the right formula but evaluated it on
the console rather than on the pod that actually creates the session.

Note for later: m_maxPlayers is serialized in only 5 bits (MWApplication.cpp),
so 31 is the hard ceiling for any future player-cap work. See
RAISING-PLAYER-CAP.md.

Requires rebuild: MW4.exe (Release + Profile). No script or resource changes.
Verified: compiles clean, console launches. Pod testing pending.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-24 23:39:16 -05:00
dicion 515adc2413 mw4print: persist banner text via banner.txt
- Add banner.txt load/save helpers in banner settings dialog

- Load banner from banner.txt first, with options.ini BannerText fallback

- Fix options.ini path join to include missing path separator
2026-07-24 21:56:24 -05:00
dicion ff650f7bec Load File: finalize first-click deterministic apply, add NoReturn, and align docs
- Stabilize Load File first-click behavior across mission/map/options/slots.

- Fix map sequencing by rebuilding game-type scenario list before mission lookup.

- Resolve mission-name miss to map index 0 fallback for selected game type.

- Fix decal handling: map INI decal IDs to dropdown indices, clamp invalid indices, display actual decal IDs in UI labels, and avoid redraw-time decal overwrite.

- Fix option apply timing and UI visibility issues, including Weapon Jam refresh.

- Add NoReturn support end-to-end: parse/store in MW4Shell auto globals, expose script variable, and apply to respawn/no-return mission params and UI checkbox.

- Update autoconfig spec to reflect actual parser/default/fallback behavior and add NoReturn examples.
2026-07-24 21:42:54 -05:00
dicion e5561181bb Fix MAIL_LOAD_AUTO_MISSION crash: defer initialize(this) to sender==this self-mail
'gui_objects have no parent' crash at line 907: initialize(this) cannot be
called from sender==@conlobby@ context (external mail). Fix:

1. Replace initialize(this)+mail(-9998,parent) in MAIL_LOAD_AUTO_MISSION
   with mail(MAIL_LOAD_AUTO_MISSION_DONE=-8888, this) -- a self-mail.

2. Add MAIL_LOAD_AUTO_MISSION_DONE handler in sender==this block, which
   safely calls initialize(this)+mail(-9998,parent) from the correct context.

Also repaired a corrupt duplicate MAIL_PREVIOUS_MISSION_PARAMS block
that was left orphaned by an earlier edit (the original body of
MAIL_LOAD_AUTO_MISSION had been inserted there when the handler was
moved from sender==this to sender==@conlobby@).

No rebuild required (script-only).
2026-07-24 11:09:50 -05:00
dicion 06de4534da BTFrstrm: add Load File test ini files (FFA Coliseum + 4-team KotH CentralPark) 2026-07-24 11:01:11 -05:00
dicion 23284b18b0 Fix MAIL_LOAD_AUTO_MISSION: move handler to sender==@conlobby@ block
Handler was inside 'if (sender == this)' but MAIL_LOAD_AUTO_MISSION is sent
from ConLobby (sender != this), so it never fired. All game params (FriendlyFire,
SplashDamage, UnlimitedAmmo, WeaponJam, AdvanceMode, ArmorMode, etc.) were
silently ignored on every Load File click.

Fix: move the ~70-line handler to the 'if (sender == @conlobby@)' block,
alongside MAIL_SET_ROOKIE_MISSION and MAIL_PREVIOUS_MISSION — where all
ConLobby-originated mails are handled. No rebuild required (script only).
2026-07-24 10:56:57 -05:00
dicion 62dc0952b1 BTFrstrm: add Load File test configs (FFA Coliseum, Team KoH CentralPark) 2026-07-24 10:46:24 -05:00
dicion 9a9f2d0df2 autoconfig-file-spec.html: add TeamAllowed/TeamCount to example; fix caveat #3 2026-07-24 10:37:37 -05:00
dicion cd2fe73c88 Fix Load File: separate Auto globals; fix team/FFA double-press
Issue 1 — double press to fix team/FFA display:
  cur_team_val is updated by ConLobbyMission's -9998 signal which was async.
  Fix: call SetNetworkMissionParamater(team_allowed, ...) directly in ConLobby
  before the slot loop, then re-read cur_team_val via CTCL_GetTeamParams.
  This is synchronous so the slot display is correct on the first click.
  New file keys: TeamAllowed=0/1, TeamCount=2 (default 2 teams).

Issue 2 — Default button broken after Load File click:
  CTCL_LoadAutoFile was overwriting g_nRookieGameType/g_szRookieMission etc.
  so the Default button loaded the last auto-file params instead of defaults.
  Fix: 14 new dedicated g_nAutoXxx/g_szAutoMission globals. CTCL_LoadAutoFile
  populates ONLY these. Rookie Mission globals are never touched.
  New MAIL_LOAD_AUTO_MISSION (-6666) sent to ConLobbyMission applies the Auto
  globals (mirrors MAIL_SET_ROOKIE_MISSION_PARAMS but uses g_nAutoXxx).
  ConLobby reads game type/mission from Auto globals directly into the
  ConLobbyMission dropdowns (@ConLobbyMission@o_game_options[N].nselected).

Files changed:
  MW4Shell.cpp: 16 new globals, StartUp/ShutDown registration, CTCL_LoadAutoFile
  ConLobby.script: MAIL_LOAD_AUTO_MISSION define, updated handler
  ConLobbyMission.script: MAIL_LOAD_AUTO_MISSION define + handler
  autoconfig-file-spec.html: document TeamAllowed + TeamCount fields
Rebuild required: MW4.exe (Release + Profile).
2026-07-24 10:35:11 -05:00
dicion 3f2d79a038 BTFrstrm: add weapon location spreadsheets (Special1/2 and rear-facing) 2026-07-24 10:18:06 -05:00
dicion c3d82d78c9 Load File: hide button when automaticmode!=1; fix team/skin double-click
1. Expose g_bAutomaticMode as gosScript variable so ConLobby can check
   it at init time. o_load_file.state is set to 3 (disabled/hidden) if
   automaticmode != 1 in options.ini. Button is fully visible and active
   only when the feature is intentionally enabled.
   Rebuild required: MW4.exe (Release + Profile).

2. Remove cur_team_val conditional for team/skin slot assignment.
   Previously, only one of o_team[k] or o_skins[k] was set depending on
   cur_team_val at click time, but MAIL_SET_ROOKIE_MISSION propagates the
   new game type asynchronously -- cur_team_val would not reflect the
   file's GameType until the next frame, requiring a second click.
   Fix: always set both o_team[k] and o_skins[k] unconditionally. The
   mission launch code uses whichever is relevant for the active mode;
   the other is harmlessly ignored. No rebuild (script-only).
2026-07-24 10:08:21 -05:00
dicion 17ca966473 Fix Load File mech lookup: use stock_array to get chassis name
mech[j] in the flat sorted array hits variant entries (e.g. 'Assassin2 A'
sorts before 'AssassinII' alphabetically, pushing all subsequent chassis
indices off by 1 or more). The script's stock_array[] maps each chassis
index -> its actual position in the flat mech[] array, bypassing variant
entries.

Fix: mech[allowed_mechs[j]] -> mech[stock_array[allowed_mechs[j]]]

Only stock (chassis) names are supported in Mech= field. Operators can
adjust variants manually after Load File is clicked. No rebuild required
(script-only change).
2026-07-24 10:02:13 -05:00
dicion 7852269dc7 Fix Load File crash: VALUEPARM for literal field arg in CTCL_GetAutoSlotInt
Script calls callback(CTCL_GetAutoSlotInt, k, 0/1/2/3) where 0-3 are
integer literals. The script engine passes literals as (void*)N directly
(not as pointers), so INTPARM(1) = *((int*)data[1]) dereferences NULL
when field=0, producing the 'Attempt to read from NULL' STOP.

Fix: VALUEPARM(1) = (int)data[1] reads the value without dereferencing.
k (data[0]) remains INTPARM because it is a script variable (passed as
a pointer to the variable's storage, not a literal).

Also add exists(@ConLobbyMission@) guard before MAIL_SET_ROOKIE_MISSION
for defensive safety if the sub-script is not running.

Rebuild required: MW4.exe (Release + Profile).
2026-07-24 09:31:51 -05:00
dicion 76121f1c68 BTFrstrm: add autoconfig-file-spec.html (Load File format reference) 2026-07-24 09:13:37 -05:00
dicion c33465611f Add [automaticmode] Load File button to console lobby
options.ini [automaticmode] section:
  automaticmode=1
  automaticfile=c:\path\to\config.ini

Right-click was considered then dropped in favor of a dedicated button
at 467,510 (below Pick Cond., left of Reprint).

C++ (MW4Shell.cpp):
- SAutoFileSlot struct + g_aAutoSlots[16], g_bAutomaticMode, g_szAutomaticFile globals
- [automaticmode] ini read at StartUp
- CTCL_LoadAutoFile: checks file exists, reads [mission] page into existing
  g_nRookieXxx globals + [slot0]..[slot15] pages into g_aAutoSlots[]; returns 1 if loaded
- CTCL_GetAutoSlotName(out_str, k): pilot name for slot k
- CTCL_GetAutoSlotMech(out_str, k): mech display name for slot k
- CTCL_GetAutoSlotInt(k, field): Type/Team/Skin/Decal for slot k (fields 0-3)
- Register/unregister all 4 callbacks in StartUp/ShutDown

Script (ConLobby.script):
- o_load_file button at 467, 510
- Handler: CTCL_LoadAutoFile -> if loaded, sends MAIL_SET_ROOKIE_MISSION to
  ConLobbyMission (game options), clears all slots, then applies per-slot data
  in a loop (pilot names, mech by display-name lookup in allowed_mechs[], team,
  skin, decal). USE_ALLOWED_MECHS/non-ALLOWED_MECHS both handled via #if.

File not consumed (stays on disk); external app overwrites for next load.
Rebuild required: MW4.exe (Release + Profile).
2026-07-24 08:44:57 -05:00
dicion fccdc2dee4 CLAUDE.md: document 5813aeb6 through 0344418a work (2026-07-23)
- hsh/ BMP canonical renames (MFD + Mechs, commit 5813aeb6)
- RookieMission configurable defaults via options.ini (MW4Shell.cpp + ConLobbyMission.script, 5813aeb6)
- Mechlab turn rate label correction (StringResource.rc, 5813aeb6)
- BTFrstrm design docs: MechDependencyTree.docx + Special_Zones.docx (840bc96c)
- mech_loadouts.md: MechEditor data sources, field conversions, hsh naming reference (0344418a)
2026-07-23 22:18:43 -05:00