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>