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>
This commit is contained in:
2026-08-05 15:00:37 -05:00
co-authored by Claude Opus 5 GitHub Copilot
parent 2e85cd2066
commit 4e04fd1fb2
7 changed files with 516 additions and 4 deletions
+85
View File
@@ -1167,6 +1167,91 @@ Every hypothesis reasoned forward from the code was wrong again (blit erasing ov
channel; per-GPU rendering). The `-tmr` ladder plus the draw-call counter settled it in two runs.
Consistent with the rest of STEP 10: **instrument, don't infer.**
## STEP 12: Intel iGPU + USB display MFD failure — was `-tmon` misread + a real
## panel re-entry leak (2026-08-05)
Reported as *"MFD modes work on a W4100 and a Quadro, and with the radar on a USB adapter, but
fail on a machine with CPU-integrated Intel graphics"* — 3 displays (main + 2 MFDs) on an
Intel HD 630, radar on a Trigger 6 USB adapter, Win10 + dgVoodoo2. Second MFD panel returned
`DDERR_EXCLUSIVEMODEALREADYSET`. **Neither the Intel iGPU nor the mixed-adapter setup was at
fault.** Two independent causes, both now fixed/instrumented.
### Cause 1 — Windows Display Settings numbers are NOT the DirectDraw device order
The operator set `-tmon 3,4,2,1` by reading the numbers off the Windows Display Settings
arrangement diagram. Those numbers are an **undocumented UI ordinal that no API exposes** and
that need not match `\\.\DISPLAYn` *or* the DirectDraw index. On this box all three differed:
| Settings | `\\.\DISPLAYn` | DDraw device | desktop X | role wanted |
|---|---|---|---|---|
| 3 (primary, 800x600) | DISPLAY1 | 0 | 0 | main |
| 4 | DISPLAY4 | 3 | 800 | radar |
| 2 | DISPLAY2 | 1 | 1440 | mfd1 |
| 1 | DISPLAY3 | 2 | 2080 | mfd2 |
A permutation with two accidental fixed points — no derivable rule, so it cannot be corrected
in code. `-tmon 1,4,2,3` (correct DirectDraw indices) worked first try, user-confirmed.
**This is why Windows itself ships an *Identify* button rather than publishing the mapping.**
### Cause 2 — panels were re-opened without being released (REAL BUG, all machines)
`EnterFullScreenMode()` (`DXRasterizer.cpp:1132`) calls `HSH_EnterFullScreen2()` on **every**
mode change and every lost-front-buffer recovery, but the teardown `HSH_DirectDrawRelease2()`
was only wired to `DirectDrawRelease()` — i.e. full shutdown. So `CHSH_Device::InitFirst()`
overwrote `pDD` with a fresh `IDirectDraw7` while the previous one still held exclusive
fullscreen, **leaking it and its exclusive claim for the life of the process**.
- The log shows it plainly: `HSH_EnterFullScreen2` runs **twice**; the first entry initialises
all panels `DD_OK`, the second fails on the same device.
- Only bites when a panel sits on the **Windows primary** — secondary outputs grant exclusive
mode again, the primary does not. Every working pod happens to have the *main display* on the
primary, so no panel is ever there. That is the whole reason it looked hardware-specific.
- **FIX:** `render.cpp` `HSH_EnterFullScreen2()` now calls `HSH_DirectDrawRelease2()` first when
`hsh_initialized || hsh_mrdev_initialized`. Tagged `[panelreinit]`.
### ✅ `gos-displays.txt` rewritten as a full startup trace (`VideoCard.cpp`)
Was: final device list + role numbers. Now: every stage in the order the game does it —
command line (with `-tmon` shown already converted 1-based→0-based), **Windows desktop topology**
(`EnumDisplayDevicesA`/`EnumDisplaySettingsA`), each enumeration callback with its GUID and
`HMONITOR` resolved to `\\.\DISPLAYn` + rect + primary flag, per-device ACCEPTED/REJECTED, the
device table **before and after** the NULL-device merge with `DeviceGUID` **and**
`guidDeviceIdentifier` and an **adapter grouping**, every index shift in the merge, every
role-selection decision *with the reason each device was skipped*, each `-tmon` override
(applied/rejected + what auto-detect had chosen), a **desktop left-to-right order** block, and a
**consistency check that names two roles landing on one device as a CLASH**.
- **Fixed a real desync while doing it:** the merge shifted `DeviceArray` but nothing tracked
which monitor each slot drove → `g_ahDevMonitor[]` is now shifted in lockstep. `HMONITOR` was
previously discarded entirely (`videoDevices` gained `hMonitor`; `BufferDevice` takes it).
- Startup-only, ~255 open-append-close writes (~25130 ms), nothing on the frame path. Kept
unbuffered deliberately — this is the crash-survivable log (`gos-fps.txt` is the buffered one).
- APIs resolved via `GetProcAddress` with locally-declared `MONITORINFOEXA`/`DISPLAY_DEVICEA`
layouts, so nothing depends on the 1998 SDK having multimon headers.
### ✅ NEW: `-tident` — the game's own Identify
`MW4.exe -tident [3..120, default 20]` fills **every** display with a distinct colour and prints,
huge, the number to type into `-tmon`, plus its DirectDraw device index and the role currently
assigned to it. Then exits. This is the only reliable way to map monitors, precisely because the
Settings ordinal is unreadable.
- **Uses `DDSCL_NORMAL` + GDI on the primary surface — no exclusive mode, no mode change.**
Taking exclusive fullscreen on several devices at once is the very failure being diagnosed; a
diagnostic that trips over it is worthless. It paints onto each monitor's existing desktop, so
it works even on a pod where the MFD modes are broken.
- Opens the **real DirectDraw devices** rather than positioning GDI windows by `HMONITOR` — that
proves the device-index→physical-output association through the same path the panels use.
(Positioning by DirectDraw's own reported `HMONITOR` would be circular.)
- Implemented in `VideoCard.cpp` `IdentifyDisplays()`, called at the end of `FindVideoCards()`
then `ExitProcess(0)`. Everything it does is also written to `gos-displays.txt`.
### Docs
`-help` for `-tmon` now states outright that these are **not** Windows Display Settings numbers
and points at `-tident`/`gos-displays.txt`; the old `-tmon 1,2,3,4` example was actively inviting
the mistake. Release notes gained a `-tident` section, the clash/re-entry fixes, and an upgrade
checklist step.
### Method note
Same lesson as STEPs 10 and 11, and it caught me out again: my forward-reasoned hypothesis
(duplicate `-tmon` assignment) was **wrong**, and the new logging disproved it in one run — the
consistency check explicitly printed *"No duplicate device assignments"*. The double
`HSH_EnterFullScreen2` was only visible because the log covers the whole start-up rather than a
summary. **Instrument, don't infer.**
## Next steps (proposed)
- [ ] (Decision pending) Borderless-windowed migration — see the staged assessment in STEP 10.
Step 2 (one panel) is the cheap, decisive experiment. Note this is the only route that would
@@ -56,6 +56,10 @@ int g_nMFD2 = -1; // mode 4: right 640x480 MFD monitor
// -1 = leave that slot to auto-detection. Populated by the -tmon command-line switch,
// which is parsed in MW4Application's WinMain - that runs before FindVideoCards().
int g_naMonitorOverride[4] = { -1, -1, -1, -1 };
// [tident] Non-zero paints each display with its -tmon number and then exits.
// Set by the -tident command-line switch, parsed in MW4Application's WinMain.
int g_nIdentDisplays = 0;
int g_nIdentSeconds = 20;
// MSL 5.03 Mechview
int g_nMechViewType; // jcem : 0 - no mechview, 1 - on radar screen, 2 - on main screen
@@ -594,6 +598,244 @@ static void ApplyMonitorOverride(int slot, int* pTarget)
}
}
//
// Desktop arrangement, left to right, annotated with the DirectDraw device index.
//
// Display Settings draws the monitors in this order and labels them with its own
// ordinals, which are NOT the \\.\DISPLAYn numbers and NOT the DirectDraw indices.
// All three can disagree, and on mixed-adapter machines they usually do. Printing
// the physical order lets an operator match the picture on screen to a device index
// without guessing.
//
static void DispLogDesktopOrder()
{
int anOrder[8];
long alLeft[8];
int nCount=0;
int i;
char szMon[192];
DispLogResolveApis();
if( !pfnDispLogGetMonitorInfoA )
return;
for( i=0; i<(int)NumDevices && i<8; i++ )
{
DispLogMonitorInfo mi;
if( !g_ahDevMonitor[i] )
continue;
memset( &mi, 0, sizeof(mi) );
mi.cbSize=sizeof(mi);
if( !pfnDispLogGetMonitorInfoA( g_ahDevMonitor[i], &mi ) )
continue;
anOrder[nCount]=i;
alLeft[nCount]=mi.rcMonitor.left;
nCount++;
}
if( nCount<2 )
return;
// Small n; a plain insertion sort keeps this readable.
for( i=1; i<nCount; i++ )
{
int nDev=anOrder[i];
long lPos=alLeft[i];
int j=i-1;
while( j>=0 && alLeft[j]>lPos )
{
anOrder[j+1]=anOrder[j];
alLeft[j+1]=alLeft[j];
j--;
}
anOrder[j+1]=nDev;
alLeft[j+1]=lPos;
}
DispLog( "\r\n Desktop arrangement, left to right (compare with Display Settings):\r\n" );
for( i=0; i<nCount; i++ )
DispLog( " position %d : device %d -> %s\r\n",
i+1, anOrder[i], DispLogMonitorName( g_ahDevMonitor[anOrder[i]], szMon ) );
DispLog( " Display Settings labels these with its OWN numbers, which need not match\r\n"
" either the \\\\.\\DISPLAYn names or the device indices. Identify the monitor\r\n"
" by its position and size above, then use its device index + 1 in -tmon.\r\n" );
}
//
// [tident] Paint each display with the number the operator must type into -tmon.
//
// Windows Display Settings numbers its monitors with an ordinal that no API
// exposes, so it cannot be translated to a DirectDraw device index - see the
// desktop-order block above. Windows itself solves this with an Identify button.
// This is the same answer: open every device, put its number on the glass, and let
// the operator read the mapping off the pod instead of deducing it.
//
// Deliberately uses DDSCL_NORMAL and paints the desktop through GDI. Taking
// exclusive fullscreen on several devices at once is the very thing that fails on
// modern Windows, and a diagnostic that trips over the fault it is diagnosing is
// worthless. Nothing here changes a display mode.
//
static void IdentifyDisplays()
{
IDirectDraw7* apDD[8];
IDirectDrawSurface7* apSurf[8];
DWORD iDev;
// Distinct fill per device so the panels remain tellable apart from across the
// room, and readable if the font fails to create for any reason.
static const COLORREF acrFill[8] =
{
RGB(160,0,0), RGB(0,120,0), RGB(0,0,170), RGB(150,110,0),
RGB(120,0,140), RGB(0,120,140), RGB(90,90,90), RGB(180,60,0)
};
static const char* apszRole[6] = { "main", "radar", "span", "span2", "mfd1", "mfd2" };
int anRoleDev[6];
anRoleDev[0]=Environment.FullScreenDevice;
anRoleDev[1]=g_nNonDualHead;
anRoleDev[2]=g_nDualHead;
anRoleDev[3]=g_nDualHead2;
anRoleDev[4]=g_nMFD1;
anRoleDev[5]=g_nMFD2;
DispLog( "\r\n--- -tident: display identification ------------------------------------\r\n" );
DispLog( "Painting each display with its -tmon number. No display mode is changed and\r\n"
"no device takes exclusive mode. The game exits when the display period ends.\r\n\r\n" );
for( iDev=0; iDev<NumDevices && iDev<8; iDev++ )
{
apDD[iDev]=0;
apSurf[iDev]=0;
}
for( iDev=0; iDev<NumDevices && iDev<8; iDev++ )
{
DDSURFACEDESC2 ddsd;
HRESULT hr;
HWND hCoop = hWindow ? hWindow : GetDesktopWindow();
char szMon[192];
DispLog( " device %d -> %s\r\n", (int)iDev,
DispLogMonitorName( g_ahDevMonitor[iDev], szMon ) );
hr=wDirectDrawCreateEx( &DeviceArray[iDev].DeviceGUID, (VOID**)&apDD[iDev], IID_IDirectDraw7, NULL );
if( FAILED(hr) || !apDD[iDev] )
{
DispLog( " *** DirectDrawCreateEx failed (0x%08lX) - cannot identify this one ***\r\n",
(unsigned long)hr );
apDD[iDev]=0;
continue;
}
hr=apDD[iDev]->SetCooperativeLevel( hCoop, DDSCL_NORMAL );
if( FAILED(hr) )
{
DispLog( " *** SetCooperativeLevel(NORMAL) failed (0x%08lX) ***\r\n", (unsigned long)hr );
continue;
}
memset( &ddsd, 0, sizeof(ddsd) );
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS;
ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;
hr=apDD[iDev]->CreateSurface( &ddsd, &apSurf[iDev], NULL );
if( FAILED(hr) || !apSurf[iDev] )
{
DispLog( " *** CreateSurface(primary) failed (0x%08lX) ***\r\n", (unsigned long)hr );
apSurf[iDev]=0;
continue;
}
{
HDC hdc=NULL;
const char* pszRole="unused";
int iRole;
for( iRole=0; iRole<6; iRole++ )
if( anRoleDev[iRole]==(int)iDev )
{
pszRole=apszRole[iRole];
break;
}
hr=apSurf[iDev]->GetDC( &hdc );
if( FAILED(hr) || !hdc )
{
DispLog( " *** GetDC failed (0x%08lX) ***\r\n", (unsigned long)hr );
continue;
}
{
RECT rc;
HBRUSH hBrush;
HFONT hFontBig;
HFONT hFontSmall;
HFONT hOld;
char szBig[16];
char szLine[256];
int nWidth;
int nHeight;
nWidth =GetDeviceCaps( hdc, HORZRES );
nHeight=GetDeviceCaps( hdc, VERTRES );
rc.left=0; rc.top=0; rc.right=nWidth; rc.bottom=nHeight;
hBrush=CreateSolidBrush( acrFill[iDev] );
FillRect( hdc, &rc, hBrush );
DeleteObject( hBrush );
SetBkMode( hdc, TRANSPARENT );
SetTextColor( hdc, RGB(255,255,255) );
// The number the operator types; everything else is confirmation.
sprintf( szBig, "%d", (int)iDev+1 );
hFontBig=CreateFontA( nHeight/2, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
ANTIALIASED_QUALITY, FF_DONTCARE, "Arial" );
hOld=(HFONT)SelectObject( hdc, hFontBig );
rc.top=nHeight/8;
DrawTextA( hdc, szBig, -1, &rc, DT_CENTER|DT_TOP|DT_SINGLELINE );
SelectObject( hdc, hOld );
DeleteObject( hFontBig );
hFontSmall=CreateFontA( nHeight/16, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
ANTIALIASED_QUALITY, FF_DONTCARE, "Arial" );
hOld=(HFONT)SelectObject( hdc, hFontSmall );
rc.top=nHeight*3/4;
sprintf( szLine, "use %d in -tmon", (int)iDev+1 );
DrawTextA( hdc, szLine, -1, &rc, DT_CENTER|DT_TOP|DT_SINGLELINE );
rc.top=nHeight*3/4 + nHeight/14;
sprintf( szLine, "device %d currently: %s", (int)iDev, pszRole );
DrawTextA( hdc, szLine, -1, &rc, DT_CENTER|DT_TOP|DT_SINGLELINE );
SelectObject( hdc, hOld );
DeleteObject( hFontSmall );
}
apSurf[iDev]->ReleaseDC( hdc );
DispLog( " painted: -tmon value %d, currently assigned to %s\r\n",
(int)iDev+1, pszRole );
}
}
DispLog( "\r\n Write down the number on each monitor, then set -tmon in the order\r\n"
" main,radar,mfd1,mfd2 using those numbers.\r\n" );
DispLog( " Holding the display for %d seconds...\r\n", g_nIdentSeconds );
Sleep( (DWORD)g_nIdentSeconds * 1000 );
for( iDev=0; iDev<NumDevices && iDev<8; iDev++ )
{
if( apSurf[iDev] )
apSurf[iDev]->Release();
if( apDD[iDev] )
apDD[iDev]->Release();
}
DispLog( " -tident finished; exiting.\r\n" );
}
//
// [displaylog] Final summary: the role assignment, how each role maps back to a
// physical monitor, and whether the assignment is self-consistent.
@@ -607,6 +849,7 @@ static void LogDisplayDevices()
int iRole;
DispLogDeviceTable( "FINAL" );
DispLogDesktopOrder();
anRoleDev[0]=Environment.FullScreenDevice;
anRoleDev[1]=g_nNonDualHead;
@@ -915,6 +1158,14 @@ void FindVideoCards()
// assignment. SPEW is compiled out of shipping builds, so a file is the only way
// the operator can see why a display did or did not get picked up.
LogDisplayDevices();
// [tident] Identification is a diagnostic mode, not a play mode: show the numbers
// and quit rather than carrying on with surfaces the operator has just been shown.
if( g_nIdentDisplays )
{
IdentifyDisplays();
ExitProcess( 0 );
}
// jcem
//
// Check any known video cards have up to date drivers (Only on Windows9x)
@@ -3005,6 +3005,25 @@ void HSH_EnterFullScreen2()
HSH_LogInit( "\r\nHSH_EnterFullScreen2\r\n" );
HSH_LogInit( "--------------------\r\n" );
// [panelreinit] Drop any panels left over from a previous entry before opening
// them again.
//
// EnterFullScreenMode() re-enters on every mode change and on every lost-front-
// buffer recovery, but the panel teardown was only ever wired to DirectDrawRelease()
// - i.e. full shutdown. InitFirst() below would therefore overwrite pDD with a new
// IDirectDraw7 while the previous one still held exclusive fullscreen, leaking it
// along with its exclusive claim for the lifetime of the process.
//
// It goes unnoticed on a pod whose main display is the Windows primary, because
// secondary outputs will grant exclusive mode again. The primary will not: the
// second entry fails with DDERR_EXCLUSIVEMODEALREADYSET, and every later call on
// that device with DDERR_NOCOOPERATIVELEVELSET.
if( hsh_initialized || hsh_mrdev_initialized )
{
HSH_LogInit( " re-entry: releasing panels still held from the previous entry\r\n" );
HSH_DirectDrawRelease2();
}
// [mrdiag] The cameraship Map/Armor overlays are drawn by hudchat.cpp only when
// CTCL_GetType() reports _ECTCL_CameraShip (3). If it reports anything else the
// screen still opens and still shows its background, but nothing is ever drawn on
@@ -90,6 +90,9 @@ extern int g_nTypeOfMFDs; // 0 - no MFDs, 1 - orginal(5+1=dual & single), 2 - B&
// [tmon] Display-device override in Main/Radar/MFD1/MFD2 order; -1 = auto-detect.
// Defined in CoreTech GameOS VideoCard.cpp and consumed by FindVideoCards().
extern int g_naMonitorOverride[4];
// [tident] Non-zero paints each display with its -tmon number, then exits.
extern int g_nIdentDisplays;
extern int g_nIdentSeconds;
extern int g_nRIOType;
extern DWORD g_dwRIOBaud; // [tbaud]
// [fpslog] Non-zero enables the per-second frame pacing report (gos-fps.txt).
@@ -870,7 +873,28 @@ static const char* const g_apszCommandLineHelp[] =
" value of 0 leaves that slot auto-detected.",
" Separators may be ',' '/' or ':'.",
" MFD1 and MFD2 only apply with -tmfds 4.",
"",
" These are DirectDraw device numbers. They are NOT",
" the numbers shown in Windows Display Settings, and",
" on many machines the two do not match. Do not read",
" them off the Settings screen.",
"",
" To find the right values: launch the game once,",
" then open gos-displays.txt next to MW4.exe and read",
" the 'Desktop arrangement, left to right' list. It",
" gives each monitor's position, size and device",
" number. Add 1 to the device number for -tmon.",
" Example: -tmon 1,2,4,3 swaps the two MFD panels.",
" -tident [seconds] Identify the displays, then exit. Each monitor is",
" filled with a colour and the number to use for it",
" in -tmon. Write the numbers down, then set -tmon in",
" the order main,radar,MFD1,MFD2.",
" This is the reliable way to map the monitors: the",
" numbers Windows Display Settings shows cannot be",
" read by any program, so they cannot be trusted to",
" match. Default 20 seconds; accepts 3 to 120.",
" No display mode is changed and the game does not",
" start - it exits when the time is up.",
" -tcoop <0-5> How the radar/MFD panels claim their displays.",
" 0 = legacy single call (as shipped; works on XP",
" and under dgVoodoo2, fails on Windows 10).",
@@ -1730,7 +1754,18 @@ int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int n
}
// [fpslog] Enable the per-second frame pacing report written to gos-fps.txt.
g_nFpsLog = (strstr(all_lower, "-fps") != NULL) ? 1 : 0;
g_bCanSuicideAllways = (strstr(all_lower, "-suicide") != NULL);
// [tident] Paint each display with the number to use in -tmon, then exit. Windows
// Display Settings monitor numbers cannot be read programmatically, so this is the
// only way to hand the operator an authoritative mapping.
g_nIdentDisplays = (strstr(all_lower, "-tident") != NULL) ? 1 : 0;
token = strstr(all_lower, "-tident ");
if (token && token[8])
{
int n = atoi(&token[8]);
if ((3 <= n) && (n <= 120))
g_nIdentSeconds = n;
}
g_bCanSuicideAllways = (strstr(all_lower, "-suicide") != NULL);
g_f3dtarget = (strstr(all_lower, "-3dt") != NULL);
g_bOldLOG = (strstr(all_lower, "-olog") != NULL);
#ifdef _DEBUG
+1 -1
View File
@@ -61,7 +61,7 @@ Read in `MW4Application.cpp` (`GetGameOSEnvironment`) and `MWOptions.cpp`.
| Key | Why |
|---|---|
| `videodriverindex` | The `GetEntry` call is commented out and `Environment.FullScreenDevice` is forced to `0`. Use the `-tmon` command-line switch to assign display devices instead. |
| `videodriverindex` | The `GetEntry` call is commented out and `Environment.FullScreenDevice` is forced to `0`. Use the `-tmon` command-line switch to assign display devices instead — run `MW4.exe -tident` first to find out which number is which monitor, as Windows Display Settings numbers do not match. |
**Present in the shipped file and consumed by renderer subsystem initialisers**
(`MidLevelRenderer::InitializeClasses`, `gosFX`, `ElementRenderer`, `Compost`), which are
+52
View File
@@ -168,11 +168,34 @@ with no dgVoodoo2 at all, using every available configuration. Modern Windows on
display at a time to take exclusive fullscreen control, so the extra MFD panels are refused.
Windows XP allowed it; Windows 10 does not. dgVoodoo2 works around this. There is no setting
or switch that avoids it.</p></div>
<h3><code>-tident</code> -- find out which monitor is which</h3>
<p>Before using <code>-tmon</code>, use this to find out what numbers to type:</p>
<pre><code>MW4.exe -tident
</code></pre>
<p>Every monitor fills with a colour and shows, in large text, the number to use for it. Something
like:</p>
<pre><code> 3
use 3 in -tmon
device 2 currently: main
</code></pre>
<p>Walk the pod, write down what each screen says, then set <code>-tmon</code> in the order
<strong>main, radar, MFD 1, MFD 2</strong>. The game exits on its own when the time is up -- it does not start
a mission, and it does not change any display setting. The display lasts 20 seconds by default;
<code>-tident 45</code> gives you longer, and anything from 3 to 120 seconds is accepted.</p>
<div class="note"><p><strong>Why this exists.</strong> The monitor numbers Windows shows in Display Settings <strong>cannot be read by
any program</strong> -- Windows simply does not make them available. They often happen to match the
numbers <code>-tmon</code> wants, but on some machines they do not, and there is no way for the game to
tell the difference. This is the same reason Windows gives you an <em>Identify</em> button instead of
just telling you. <code>-tident</code> is the game's own Identify.</p></div>
<p><strong>Do not read monitor numbers off the Windows Display Settings screen and type them into
<code>-tmon</code>.</strong> That is the single most common way to get a wrong or non-working display setup, and
it can look like a hardware fault. Use <code>-tident</code>.</p>
<h3><code>-tmon</code> -- fix monitors coming up in the wrong order</h3>
<p>If your panels appear on the wrong physical monitors, you can assign them explicitly, in the
order <strong>main, radar, MFD 1, MFD 2</strong>:</p>
<pre><code>MW4.exe -tmfds 4 -tmon 1,2,3,4
</code></pre>
<p>Use the numbers <strong><code>-tident</code> showed you</strong> (see above), not the numbers in Windows Display Settings.</p>
<p>Use <code>0</code> in any position to leave that one on automatic -- <code>-tmon 2,1,0,0</code> swaps only main and radar.
<strong>Leave the switch off entirely and everything is detected automatically</strong>, which is correct on
most pods.</p>
@@ -223,6 +246,18 @@ ever need to force it, ask and it can be added.</p>
<p>If a display comes up in the wrong place, check <code>gos-displays.txt</code> next to the game (see
section 8). It lists every display found, which role each was given, and whether each <code>-tmon</code>
position was accepted or ignored.</p>
<h3>Two panels on one monitor -- now reported</h3>
<p>If <code>-tmon</code> accidentally puts two things on the same monitor, the second one cannot start: the
first already has that display. Previously this produced a confusing DirectDraw error naming
neither of them. <code>gos-displays.txt</code> now says so plainly:</p>
<pre><code>*** CLASH: main and mfd2 are BOTH on device 0 ***
</code></pre>
<h3>Panels are now released correctly when the display mode changes</h3>
<p>A fault that could stop the MFDs working after the game changed display mode, or recovered from
another program taking the screen. The panels were being re-opened without the previous ones
being closed first, so they collided with themselves. Most pods never saw it; it showed up
reliably on machines where one of the panels was on the monitor Windows treats as primary.</p>
<p><strong>Applies automatically.</strong> No configuration needed.</p>
<hr>
<h2>5. 'Mechs and content</h2>
<h3>Six rookie 'Mechs instead of four</h3>
@@ -330,6 +365,16 @@ and it costs nothing when unused.</p>
<p>When display problems occur, the game now writes <strong><code>gos-displays.txt</code></strong> next to the exe listing
every monitor it found, which role each was assigned, and the exact reason any panel failed to
start. Previously such failures were completely silent. Include this file with any display fault report.</p>
<p>The log now follows the whole start-up sequence in order: what Windows reports is attached, what
the game found, how it matched the two up, which display it chose for each role and why, and what
each <code>-tmon</code> position did. Near the end it prints the monitors in left-to-right order with the
number to use for each -- the same numbers <code>-tident</code> puts on the screens:</p>
<pre><code> Desktop arrangement, left to right (compare with Display Settings):
position 1 : device 0 -&gt; \\.\DISPLAY1 800x600 at 0,0 [WINDOWS PRIMARY]
position 2 : device 3 -&gt; \\.\DISPLAY4 640x480 at 800,122
</code></pre>
<div class="note"><p>The file is rewritten every time the game starts. If you have a failure worth reporting, copy
the file before launching again.</p></div>
<hr>
<h2>9. New command line switches at a glance</h2>
<table>
@@ -347,6 +392,11 @@ start. Previously such failures were completely silent. Include this file with a
<td>Existing MFD mode unchanged</td>
</tr>
<tr>
<td><code>-tident [secs]</code></td>
<td>Show each monitor's number on the monitor itself, then exit. Use this to work out what to put in <code>-tmon</code></td>
<td>Game starts normally</td>
</tr>
<tr>
<td><code>-tmon a,b,c,d</code></td>
<td>Force monitor order: main, radar, MFD1, MFD2. Positions 3 and 4 apply only to <code>-tmfds 4</code></td>
<td>Automatic detection</td>
@@ -399,6 +449,8 @@ start. Previously such failures were completely silent. Include this file with a
mode. Only console mode, on a single display, does without it. Windows XP pods never
need it.</li>
<li>Launch and confirm the console lobby reads the new version.</li>
<li>If your pod uses MFDs or a cameraship, run <code>MW4.exe -tident</code> once and check each monitor
shows the role you expect. If any are wrong, note the numbers shown and correct <code>-tmon</code>.</li>
<li>Play one mission end to end before putting the pod back into service.</li>
<li>Optional: add <code>[automaticmode]</code> and <code>[RookieMission]</code> to <code>options.ini</code> if you want the new
Load File button or custom rookie missions.</li>
+72 -2
View File
@@ -182,6 +182,38 @@ targeting reticle is accurate.
> Windows XP allowed it; Windows 10 does not. dgVoodoo2 works around this. There is no setting
> or switch that avoids it.
### `-tident` -- find out which monitor is which
Before using `-tmon`, use this to find out what numbers to type:
```
MW4.exe -tident
```
Every monitor fills with a colour and shows, in large text, the number to use for it. Something
like:
```
3
use 3 in -tmon
device 2 currently: main
```
Walk the pod, write down what each screen says, then set `-tmon` in the order
**main, radar, MFD 1, MFD 2**. The game exits on its own when the time is up -- it does not start
a mission, and it does not change any display setting. The display lasts 20 seconds by default;
`-tident 45` gives you longer, and anything from 3 to 120 seconds is accepted.
> **Why this exists.** The monitor numbers Windows shows in Display Settings **cannot be read by
> any program** -- Windows simply does not make them available. They often happen to match the
> numbers `-tmon` wants, but on some machines they do not, and there is no way for the game to
> tell the difference. This is the same reason Windows gives you an *Identify* button instead of
> just telling you. `-tident` is the game's own Identify.
**Do not read monitor numbers off the Windows Display Settings screen and type them into
`-tmon`.** That is the single most common way to get a wrong or non-working display setup, and
it can look like a hardware fault. Use `-tident`.
### `-tmon` -- fix monitors coming up in the wrong order
If your panels appear on the wrong physical monitors, you can assign them explicitly, in the
@@ -191,6 +223,8 @@ order **main, radar, MFD 1, MFD 2**:
MW4.exe -tmfds 4 -tmon 1,2,3,4
```
Use the numbers **`-tident` showed you** (see above), not the numbers in Windows Display Settings.
Use `0` in any position to leave that one on automatic -- `-tmon 2,1,0,0` swaps only main and radar.
**Leave the switch off entirely and everything is detected automatically**, which is correct on
most pods.
@@ -217,6 +251,25 @@ If a display comes up in the wrong place, check `gos-displays.txt` next to the g
section 8). It lists every display found, which role each was given, and whether each `-tmon`
position was accepted or ignored.
### Two panels on one monitor -- now reported
If `-tmon` accidentally puts two things on the same monitor, the second one cannot start: the
first already has that display. Previously this produced a confusing DirectDraw error naming
neither of them. `gos-displays.txt` now says so plainly:
```
*** CLASH: main and mfd2 are BOTH on device 0 ***
```
### Panels are now released correctly when the display mode changes
A fault that could stop the MFDs working after the game changed display mode, or recovered from
another program taking the screen. The panels were being re-opened without the previous ones
being closed first, so they collided with themselves. Most pods never saw it; it showed up
reliably on machines where one of the panels was on the monitor Windows treats as primary.
**Applies automatically.** No configuration needed.
---
## 5. 'Mechs and content
@@ -361,6 +414,20 @@ When display problems occur, the game now writes **`gos-displays.txt`** next to
every monitor it found, which role each was assigned, and the exact reason any panel failed to
start. Previously such failures were completely silent. Include this file with any display fault report.
The log now follows the whole start-up sequence in order: what Windows reports is attached, what
the game found, how it matched the two up, which display it chose for each role and why, and what
each `-tmon` position did. Near the end it prints the monitors in left-to-right order with the
number to use for each -- the same numbers `-tident` puts on the screens:
```
Desktop arrangement, left to right (compare with Display Settings):
position 1 : device 0 -> \\.\DISPLAY1 800x600 at 0,0 [WINDOWS PRIMARY]
position 2 : device 3 -> \\.\DISPLAY4 640x480 at 800,122
```
> The file is rewritten every time the game starts. If you have a failure worth reporting, copy
> the file before launching again.
---
## 9. New command line switches at a glance
@@ -368,6 +435,7 @@ start. Previously such failures were completely silent. Include this file with a
| Switch | Purpose | If you leave it off |
|---|---|---|
| `-tmfds 4` | Two separate 640x480 MFD monitors | Existing MFD mode unchanged |
| `-tident [secs]` | Show each monitor's number on the monitor itself, then exit. Use this to work out what to put in `-tmon` | Game starts normally |
| `-tmon a,b,c,d` | Force monitor order: main, radar, MFD1, MFD2. Positions 3 and 4 apply only to `-tmfds 4` | Automatic detection |
| `-tbaud <rate>` | COM1 speed for replica RIO boards | Original board speed |
| `-fps` | Write a performance log | No log, no overhead |
@@ -402,6 +470,8 @@ start. Previously such failures were completely silent. Include this file with a
mode. Only console mode, on a single display, does without it. Windows XP pods never
need it.
5. Launch and confirm the console lobby reads the new version.
6. Play one mission end to end before putting the pod back into service.
7. Optional: add `[automaticmode]` and `[RookieMission]` to `options.ini` if you want the new
6. If your pod uses MFDs or a cameraship, run `MW4.exe -tident` once and check each monitor
shows the role you expect. If any are wrong, note the numbers shown and correct `-tmon`.
7. Play one mission end to end before putting the pod back into service.
8. Optional: add `[automaticmode]` and `[RookieMission]` to `options.ini` if you want the new
Load File button or custom rookie missions.