Commit Graph
22 Commits
Author SHA1 Message Date
dicion 9092b78dbf Correct STEP 14 deployment notes for the High Explosive fix
Record the actual packages for the two edited content files: HighExplosive.data
is packed by core.build into core.mw4, not props.mw4, and weapons.script by
props.build into props.mw4. build-resources.ps1 repacks on newer sources, so both
are picked up automatically; the distinction only matters for selective repacks.

Also document that restoring the NARC Beacon ammo-round display is an intended
consequence of moving the highexplosive UI flag from ID 84 to 87, note that
authored .subsystems loadouts need an explicit AmmoCount=1 because Weapon_Tool
defaults ammoCount to -1, and record that a sweep of every weapon subsystem found
High Explosive to be the only one whose starting-ammo formula truncated to zero.
2026-08-08 23:05:05 -05:00
dicion cd10398f71 Fix High Explosive firing from MechLab
Correct the High Explosive ammo key and ensure positive-capacity weapons receive at least one starting round and ammo per pack. Apply MechLab special handling to weapon ID 87 and document activation, stacking, validation, and deployment requirements.
2026-08-08 22:46:42 -05:00
dicion 10301ee5dd Document external MFD and radar mapping work 2026-08-07 20:56:58 -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
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
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
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
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
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 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
dicion 72e1e59d8e CLAUDE.md: document eaa5fd3 through 5.1.0b-in-progress work
- MFD mode 4 right-device stagger fix (eaa5fd3): cycle diagram, files touched
- Linux→Windows rsync workflow: sync-to-windows.sh (55b9bfc5)
- ddraw.dll removed from repo; build-resources.ps1 moves it aside (0ceba9c7, 24825ff3)
- ConLobby V5.1.0b1 / Super6 6-mech rotation from Highlight (c768f7c4)
- CRIOMAIN.CPP Korean translation + RIO poll timeout scaling; CRLF hazard note;
  min/max undeclared in VC6 in this TU (16fca6c4, a712002f)
- 16 pilots + 1 cameraship: NetworkMaxPlayers formula, fall-through break warning (f76dc05f)
2026-07-19 19:55:46 -05:00
e45a67a8fe mfdsplit: mech loadouts, time list, build fixes, Korean→English, mw4print v2.0
Battlemaster stock loadouts (Content\Mechs\Battlemaster\battlemaster.subsystems):
- Replace lone MediumPulseLaser with full IS stock: PPC (Special2, group 1),
  6×ML (3 RT + 3 LT, group 1), 2×MG (LA, 200 rds, group 1), SRM6 (Special1,
  15 rds, group 2).

Battlemaster Clan 2C (battlemaster2c.subsystems):
- Replace ClanMediumPulseLaser with: ER PPC (RA, group 1), 6×ER ML (3 RT + 3
  LT, group 1), 2×Clan Gauss (LA, 16 rds each, group 1), Clan SSRM6 (Special1,
  15 rds, group 2).

Behemoth / Behemoth2 (.subsystems):
- Move Gauss rifles from weapon group 3 → group 1 (3 occurrences each).

Resource builder (build-resources.ps1):
- Always run with -window (windowed + DDrawCompat). Fullscreen native DDraw
  fails on VMs with the generic Microsoft display adapter.
- Remove dgVoodoo2 D3D interceptors (D3D8/D3D9/D3DImm.dll) from Gameleap\mw4:
  they silently break the builder (process exits 0 without packing anything).
  Script also defensively moves any such files aside via $dgvMoved block.
- Remove dgVoodoo.conf and dgVoodooCpl.exe (abandoned experiment, no longer used).
- Expected: 'Hardware Error: not compatible with MechWarrior 4' dialog at end
  of build on VMs -- click OK, packages are built correctly regardless.

CLAUDE.md: updated with mfdsplit branch notes covering all 2026-07-18 work.

Co-authored-by: Claude Sonnet 4.6 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-18 16:22:03 -05:00
CydandClaude Fable 5 dbcf4052e2 Add -tbaud switch: force COM1 RIO baud for high-speed replica boards
-tbaud <rate> (9600-921600) overrides the COM1 baud rate while keeping
old-RIO (type 0) protocol behavior unchanged; independent of -trio.
SetupComm buffers 2/2 -> 1024/1024 only when the override is active.
Rebuilt Release + Profile (0 errors), deployed rel.bin\MW4.exe to MW4\.
run-firestorm driver: game-start now takes optional extra args.
CLAUDE.md: branch notes incl. the CP949 comment-encoding hazard
(mw4 sources must be edited byte-safely).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 21:50:10 -05:00
CydandClaude Fable 5 1fd489d9a6 CLAUDE.md: mechlab windowed-mode spin root cause (parked cosmetic note)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 21:27:54 -05:00
CydandClaude Fable 5 2f17631081 FS507D asset recovery, ConLobby V5.0.7Df promotion, mechlab New-Mech crash fix
FS507D_20161015 release analysis wrap-up (drop itself is gitignored; art-review
folder kept local for review):
- Recovered the only assets our tree lacked: 13 hsh HUD/radar/mech bmps and the
  two 5.07D lobby decals (decal_46/47.tga, extracted from release props.mw4 via
  a ported gos_LZDecompress).
- ResourceImagePool.cpp: missing-texture placeholder is now LAB_ONLY (editor
  keeps degraded mode); Release restores the original fatal STOP. Release +
  Profile rebuilt, 0 errors.

Console script reconciled:
- ConLobby.script.new ("BattleTech Console V5.0.7Df", newest revision anywhere)
  promoted to Content\ShellScripts\ConLobby.script; .new removed; stale loose
  deploy copy removed. Verified in-game (console title shows V5.0.7Df).
- Corrected CLAUDE.md: the release never "renamed" the console to
  ComputerPlayer.script -- the resource packer stores script contents under
  alphabetically skewed entry names (runtime resolves the same pairing).
  Packer quirks documented (stale entry carry-forward on incremental builds,
  name/content skew in directory sweeps).

Mechlab bug fix (first FireStorm bug hunt):
- New-Mech dialog showed blank rows for Wolfhound/Zeus and crashed (KERNELBASE
  read AV) when creating a Zeus. Root cause: newmech in chassis.script created
  its Type droplist without setting $$m_listBoxSize$$, so capacity defaulted to
  60 while 65 chassis were written in -- OOB script-array writes. Fixed by
  sizing the list from $$m_chassisCount$$. Latent stock-MW4 bug armed when the
  FireStorm roster passed 60 chassis. Verified: Zeus variant creates cleanly.

props.mw4 fully repacked (decals + promoted console + mechlab fix, junk
.new/.org entries gone); game deploy refreshed at MW4\. Gitignore: FS507D drop
and player-created MW4\Resource\Variants.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 13:04:45 -05:00
CydandClaude Fable 5 e84d6e603c Document mech + map/mission workflows; README folder map + clone warning
- ADDING-A-MECH.md: full workflow for adding a 'Mech chassis (MSL ADD MECH
  touch points, positional Mech IDs, scriptaddmech.xls generator, hardpoints
  in .damage, rebuild/repack/deploy steps)
- ADDING-A-MAP.md: full workflow for maps (MapCreator terrain) vs missions
  (MW4Ed2-authored), .nfo MP registration, user vs stock routes, pitfalls
- CLAUDE.md: reference pointers to both new docs
- README.md: annotated folder map; warning to read RECOVERY.md before cloning

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 21:15:52 -05:00
Cyd 64c57eee37 update cluade notes 2026-06-26 10:45:26 -05:00
CydandClaude Opus 4.8 202563065f Moved to proper subfolder to make room for other VWE projects.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:12:09 -05:00
Cyd 2b8ca921cb Initial full mirror of c:\VWE (source + assets + toolchain + outputs) via Git LFS
Complete disaster-recovery snapshot: engine/game source, game data assets,
VC6 toolchain + DX SDKs, build outputs, deployed game, and _UNUSED archive.
Large binaries in Git LFS; text preserved byte-for-byte (core.autocrlf=false,
no eol attributes). See RECOVERY.md for the one-clone rebuild procedure.
2026-06-24 21:28:16 -05:00