platform profile + gauge dev-composite (Milestone A: gauge renderer woken)

PLATFORM PROFILE (-platform pod|dev / BT_PLATFORM / run.cmd pod; default dev):
an env-preset selector, NOT a code fork -- it picks which environment the
existing video/gauge/input code reads.  dev = single 800x600 window + keyboard
(the working build); pod = RIO input + (on real hardware) the multi-surface
gauges/MFDs, mirroring content/SETENV.BAT (the pod path -- FindBestAdapterIndices
/ SVGA16 -- is left untouched).  The multi-surface needs the pod's 2 video cards,
so -platform pod does NOT auto-enable L4GAUGE on a dev box (stays bootable,
single-window).  Also fixes a real engine bug: SVGA16::BuildWindows
PostQuitMessage'd on a CreateDevice failure but fell through to a null-device
deref (segfault) -- now breaks (inert on the pod).

GAUGE DEV-COMPOSITE, Milestone A (option B; opt-in BT_DEV_GAUGES, default OFF):
wake the (dormant) gauge renderer so its CPU-rastered pixelBuffer can later be
composited into the dev window, WITHOUT touching the pod SVGA16 output path.
SVGA16 does no per-surface D3D in this mode (a DevGaugeComposite() gate forces
the no-surface path + short-circuits Update/Refresh).  Waking the never-exercised
gauge subsystem exposed 4 latent reconstruction bugs, each guarded:
SVGA16::Update / Refresh (empty mSurfaces[]), LBE4ControlsManager::MakeLinkedLamp
(NULL lamp manager), L4GaugeRenderer::NotifyOfNewInterestingEntity (garbage
warehouse chain).  It now boots STABLY; the gauge reconstruction is incomplete
(shadowed lamp-manager + warehouse members) -- Milestone B (the composite pass)
will reveal whether the content is real.  See docs/GAUGE_COMPOSITE.md.

Verified: default DEV un-regressed (combat DESTROYED, 0 crashes); pod path
untouched; BT_DEV_GAUGES boots stable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-06 10:18:40 -05:00
co-authored by Claude Opus 4.8
parent 8b36440d05
commit c13e72d0ba
7 changed files with 314 additions and 11 deletions
+26
View File
@@ -619,6 +619,32 @@ Old roadmap below is superseded for Phases 13 (engine/renderer/HAL/audio = do
- **Phase 8 — Pod bring-up (with Nick):** 7-monitor layout, old-driver compatibility, RIO cockpit I/O - **Phase 8 — Pod bring-up (with Nick):** 7-monitor layout, old-driver compatibility, RIO cockpit I/O
wiring, on-pod testing. wiring, on-pod testing.
**✅ PLATFORM PROFILE scaffold DONE (`-platform pod|dev`, or `BT_PLATFORM` env, or `run\run.cmd [EGG] pod`;
default DEV).** The gate before Phase-4 HUD/MFD work: a boot switch selecting a runtime profile WITHOUT forking
the codepath — it just picks WHICH environment preset the existing video/gauge/input code reads (btl4main.cpp
env-defaults block). **KEY FINDING (a workflow map): the pod multi-surface path already EXISTS and is intact**
`DPLRenderer::FindBestAdapterIndices` (multi-adapter/monitor selector; honors PRIMGAUGE/SECGAUGE/MFDGAUGE/
SPANDISABLE), `SVGA16::BuildWindows` (L4VB16.cpp — a fullscreen D3D device + window per gauge/MFD surface, MFD
span = width×2), `L4GaugeRenderer` (gated on `getenv("L4GAUGE")`, btl4app.cpp:353), and **`content/SETENV.BAT`
IS the authentic pod env preset** (L4CONTROLS=RIO,KEYBOARD; L4GAUGE=640x480x16; L4PLASMA=com2). So the profile
is an env-preset selector: **DEV (default)** = keyboard + single 800×600 window (no L4GAUGE → MakeGaugeRenderer
NULL → single surface); **POD** = RIO cockpit input (→ keyboard fallback when no serial) + the multi-surface
gauges/MFDs. Verified: dev un-regressed (boots/ticks/combat DESTROYED); POD boots on a dev box single-window,
0 crashes; both the CLI (`-platform pod` — ParseCommandLine tolerates it) and env (`BT_PLATFORM=pod`) forms work.
⚠ The multi-surface is a POD-HARDWARE capability (each surface needs its OWN fullscreen device on one of the
pod's 2 video CARDS — monitor count alone is NOT a valid proxy), so `-platform pod` does NOT auto-enable L4GAUGE:
it stays bootable everywhere as single-window + pod input; on the real pod the gauges come from SETENV.BAT (or an
explicit L4GAUGE), running the UNTOUCHED FindBestAdapterIndices/SVGA16 path. Also fixed a real engine bug
(L4VB16.cpp:201): `SVGA16::BuildWindows` PostQuitMessage'd on `CreateDevice` failure but fell through to a null
`mDevice[j]` deref (segfault) → now `break`s (inert on pod where CreateDevice succeeds). **FOLLOW-UPS** (the deep
work this scaffold gates): (1) **MFD compositing on a dev box** — render the gauges/MFDs into the single window /
extra windows so POD mode is VISUALLY testable off-pod (the real "dev test of the pod HUD"); (2) **graceful
multi-surface fallback** — the gauge renderer returns NULL instead of crashing when its surfaces can't be built,
so a forced L4GAUGE on non-pod hardware degrades cleanly; (3) **pod main-view fullscreen/adapter tuning**
(btl4vid.cpp:863 still binds the main 3D view to 800×600 windowed on `::GetActiveWindow()` — fine for dev + the
pod's 800×600 main monitor, but pod fullscreen-on-a-chosen-adapter is a Phase-8 detail). The Phase-4 HUD/MFD
render work now targets the profile's LOGICAL surfaces, not a hardcoded single window.
**Alternate route considered:** run original `btrel410.exe` under DOSBox — **blocked**: it needs the **Alternate route considered:** run original `btrel410.exe` under DOSBox — **blocked**: it needs the
VPX/IG board DOSBox can't emulate; bypassing it = the shim work anyway. VPX/IG board DOSBox can't emulate; bypassing it = the shim work anyway.
+122
View File
@@ -0,0 +1,122 @@
# Gauge / MFD render pipeline — map + dev-composite plan
**Status:** pipeline MAPPED (workflow `gauge-pipeline-map`, 2026-07); the dev composite is **planned, not
yet built**. This is the execute-ready plan for making the pod's gauges/MFDs render + be VISUALLY testable
on a dev box — the "MFD compositing on dev" follow-up the platform profile gates (see §8 / the PLATFORM
PROFILE entry in CLAUDE.md). Companion: the platform-profile scaffold (`-platform pod|dev`).
## The draw model — CONFIRMED (the good news)
**CPU-raster → texture-upload, DEVICE-INDEPENDENT.** Every gauge widget (the radar included) is a *software
rasterizer* that writes 16-bit R5G6B5 pixels into ONE shared CPU buffer (`Video16BitBuffered::pixelBuffer`,
a `PixelMap16`, `L4VB16.h:140`). Logical surfaces are packed by **BIT-PLANE** into that one 640×480×16 buffer
(each MFD = one bit mask; the radar/secondary = the low byte, palette-indexed). D3D is touched **ONLY** in
`SVGA16::Update` (`L4VB16.cpp:4041`): LockRect a `D3DPOOL_SYSTEMMEM` staging texture → a CPU expand loop
(palette-LUT for the radar, case 0; bit-plane→RGB-channel demux for the MFDs, case 1/2) → UnlockRect →
`UpdateTexture` staging→`D3DPOOL_DEFAULT` → ONE fullscreen textured quad (`TRIANGLEFAN`) → `Present`, **per
surface on its own per-adapter device**. So redirecting the content to a texture on the MAIN device is a
small change: the entire raster stays byte-identical; only the upload target + the final blit move.
## The 3 gotchas (why it's a multi-step build, not a one-liner)
1. **DORMANT in BT.** `BTL4GaugeRenderer` passes `L4GaugeRenderer(false, NULL,NULL,NULL)` (`btl4grnd.cpp:151`)
`NUMGAUGEWINDOWS=0`; and `MakeGaugeRenderer` only builds anything if `getenv("L4GAUGE")` (`btl4app.cpp:353`).
Both must change to wake it. With 0 surfaces, `SVGA16::Update` may index an empty `mSurfaces[]` → guard needed.
2. **RP↔BT PORT-NAME MISMATCH.** `SVGA16::Update` hardcodes the Red-Planet MFD port names
`auxUL2/auxC/auxUR2/auxLL/auxLR` (`L4VB16.cpp:4056-4061`), but BT's `content/GAUGE/L4GAUGE.CFG` names them
`Heat/Comm/Mfd1/Mfd2/Mfd3` (:4395-4410). `GetGraphicsPort` returns NULL for the RP names → the MFD branch
early-outs ("No MFDs to draw"). **ONLY the `sec`/radar surface matches today** — the 5 MFDs render nothing
until the names are reconciled.
3. **CROSS-DEVICE + STATE.** Folding onto the main device means the gauge blit shares the main render state
(save/restore around it) + an ORDERING concern: is `l4_application->GetVideoRenderer()->GetDevice()` valid
when the gauge renderer builds during `Application::Initialize`? Verify build order or defer texture creation.
## Architecture options (dev)
- **(A) `SVGA16` dev-composite branch.** In `SVGA16::BuildWindows`/`Update`, when a dev-composite flag is set,
use the MAIN device for the staging/default textures, skip the per-adapter device/window/`Present`; the main
renderer composites the DEFAULT textures as insets. Reuses the expand loops verbatim; **branches the pod
code** (guard on the flag → inert on pod).
- **(B) Bypass `SVGA16`.** Leave the `SVGA16` pod path untouched; add a composite pass in the main renderer
that reads the shared `pixelBuffer` + palette directly and runs the expand + inset blit itself. **Best
protects the pod path**; re-implements the expand + needs `pixelBuffer`/palette accessors on
`Video16BitBuffered`/`SVGA16`.
- **RECOMMEND (B)** for the beachhead (pod path untouched); fall back to (A) if reusing the expand loops
wholesale is preferred. Decide this with the operator before building — it's the main design fork.
## Beachhead (Step 1) — one surface visible on dev
Goal: the **secondary/radar** surface composited as an **INSET** in the existing 800×600 window (no 2nd
window yet). It's the one surface that works WITHOUT the port-name fix, so it proves the whole chain end-to-end.
1. **Wake** it via a dev-composite opt-in (e.g. `BT_DEV_GAUGES` env, default OFF so DEV/pod defaults are
unaffected; or gate on `-platform pod` on a dev box). When on: set `L4GAUGE` (so `MakeGaugeRenderer` builds)
+ set the composite flag. Guard `SVGA16::Update` for 0 surfaces (no crash).
2. **Raster**: the widgets already raster into `pixelBuffer` (driven by `GaugeRenderer::ProcessOneActiveGauge`).
3. **Composite**: each frame, in the main renderer's present, upload the secondary-expanded pixels to a
main-device texture + draw an inset quad (`XYZRHW|TEX1`), main render state saved/restored. Reach the buffer
via `GetGaugeRenderer()`.
- **Verify**: the radar/secondary inset appears; DEV default (no `BT_DEV_GAUGES`) un-regressed; pod path
untouched; 0 crashes / 0 heap.
## Follow-ups (after the beachhead)
- **Step 2** — reconcile the MFD port names (rename in `L4GAUGE.CFG` or in `SVGA16::Update`) → the 5 MFDs render.
- **Step 3** — the 2-window layout (3D-view window + an instrument-panel window tiling radar + the 5 MFDs).
- Confirm the MFD widget CONTENT is actually fed each frame (entity→gauge routing
`BTL4GaugeRenderer::NotifyOfNewInterestingEntity` → the `L4Warehouse` gauge-image bin).
## Key files / hooks
- `engine/MUNGA_L4/L4VB16.cpp`: `SVGA16::Update:4041` (the CPU→texture→blit crux; expand loops :4124-4191),
`SVGA16::BuildWindows:101` (per-surface device/window/texture — the pod path), `Video16BitBuffered::Draw*`
(:761/847/1607/1839, the software raster into `pixelBuffer`), `pixelBuffer` `L4VB16.h:140`, RP port names :4056-4061.
- `engine/MUNGA_L4/L4GREND.cpp`: `L4GaugeRenderer` ctor:57 (builds `SVGA16` from `L4GAUGE.INI`),
`ExecuteBackgroundDisplayUpdate:369` (calls `graphicsDisplay->Update`), `BuildGraphicsPort:485` (every port
shares ONE `pixelBuffer`, bit-plane per port).
- `engine/MUNGA/GAUGREND.cpp`: `ExecuteForeground:3556` / `ProcessOneActiveGauge:3836` (draw-all-gauges into `pixelBuffer`).
- `game/reconstructed/btl4grnd.cpp`: ctor:151 (the un-dormant point — NULL indices),
`NotifyOfNewInterestingEntity:250` (routes `GaugeImageStream` type-0x12 entities to moving/static bins).
- `game/reconstructed/btl4rdr.cpp`: `MapDisplay` (the radar; draws into the secondary surface).
- Main device: `l4_application->GetVideoRenderer()->GetDevice()` (`L4VIDEO.h:376`).
- Config: `content/GAUGE/L4GAUGE.CFG` (port→bitmask map, MechInit :4395-4410), `L4GAUGE.INI` (640×480×16 page).
## Surface → content map (for the 2-window layout)
- **Surface 0 = SECONDARY / radar** (640×480, palette-indexed low byte `sec` 0x003F + `overlay` 0x00C0):
the radar `MapDisplay` + secondary instruments (message board / heading / speed arc / armor-critical-heat maps).
- **Surface 1(+2) = the 5 MFDs** (bit-planes 0x01000x8000 of the shared buffer, demuxed to R/G/B channels;
pod spans them as one 1280×480): `Mfd1`=LL, `Mfd2`=UC, `Mfd3`=LR, `Heat`=UL, `Comm`=UR (+ `Eng1-3`).
- `Plasma` (CFG port 10, `L4PLASMA=com2`) is an EXTERNAL serial annunciator — not a D3D surface.
## Progress log
**Milestone A DONE (2026-07) — the gauge renderer is woken + boots STABLY on a dev box** (opt-in
`BT_DEV_GAUGES`, default OFF; default DEV + pod paths verified un-regressed — DEV combat DESTROYED,
0 crashes). Option **B** chosen. `BT_DEV_GAUGES` sets `L4GAUGE` (so `MakeGaugeRenderer` builds the
renderer) and `SVGA16` does NO per-surface D3D (a file-static `DevGaugeComposite()` gate forces the
no-surface path in `BuildWindows` + short-circuits `Update`/`Refresh`). ⚠ Reality-check: `FindBestAdapterIndices`
hands the gauge renderer **3 non-null indices even on a dev box** (all the primary adapter), so the earlier
"passes NULL → 0 surfaces" assumption was wrong — the gate is keyed on `BT_DEV_GAUGES`, not the count.
Waking the (never-exercised) gauge subsystem exposed a **cascade of 4 dormant-path bugs**, each guarded:
1. `SVGA16::Update` (L4VB16.cpp) — indexed empty `mSurfaces[]`; guarded `DevGaugeComposite()||NUM<=0`.
2. `SVGA16::Refresh` — same; guarded.
3. `LBE4ControlsManager::MakeLinkedLamp` (L4CTRL.cpp:2057) — `GetGaugeRenderer()->GetLampManager()`
returns NULL (recon gap in `BTL4GaugeRenderer`); guarded (skip the linked lamp when null).
4. `L4GaugeRenderer::NotifyOfNewInterestingEntity` (L4GREND.cpp:440) — `warehousePointer->gaugeImageBin`
has a garbage internal chain (recon shadow of the warehouse); guarded (skip the per-mech gauge-IMAGE
cache under `BT_DEV_GAUGES`).
**KEY FINDING: the gauge subsystem's RECONSTRUCTION IS INCOMPLETE.** It was dormant/never-exercised, so
these latent recon bugs (lamp-manager offset, warehouse init) only surfaced on waking it. It now BOOTS
stably, but whether the widgets raster MEANINGFUL content into `pixelBuffer` is UNVERIFIED until the
composite (Milestone B) draws it. The per-mech gauge IMAGES (armor diagrams via the warehouse) are
currently skipped; the radar/secondary map + instruments read the mech directly and *may* render partially.
So the beachhead's real question shifted from "composite a working buffer" to "how complete is the gauge
recon." FAITHFUL FOLLOW-UPS: fix the `BTL4GaugeRenderer` lamp-manager + warehouse reconstruction (shadowed
members) so per-mech gauges + lamps work (needed for real MFD content anyway).
**Milestone B (NEXT) — the composite pass:** a new `SVGA16` method (has access to `pixelBuffer` + the `sec`
port + palette) creates a texture on the MAIN device (`l4_application->GetVideoRenderer()->GetDevice()`),
runs the secondary palette-expand (L4VB16.cpp:4124-4147) into it, and the main renderer draws it as an inset
quad (state save/restore) after the 3D pass. This is what reveals whether the content is real or garbage.
+7
View File
@@ -2054,6 +2054,13 @@ Lamp *
L4LampManager L4LampManager
*lamp_manager = (L4LampManager *) application-> *lamp_manager = (L4LampManager *) application->
GetGaugeRenderer()->GetLampManager(); GetGaugeRenderer()->GetLampManager();
// DEV-COMPOSITE GUARD: when the gauge renderer is woken (BT_DEV_GAUGES) but its
// reconstructed lamp manager isn't wired (GetLampManager returns NULL -- a recon
// offset gap in BTL4GaugeRenderer), skip the linked cockpit lamp rather than
// constructing an L4Lamp with a NULL manager (crashes in LampManager::AddLamp).
// The lamp is a secondary cockpit indicator; inert on the pod (manager valid).
if (lamp_manager == NULL)
return NULL;
Check(lamp_manager); Check(lamp_manager);
Lamp Lamp
+12 -3
View File
@@ -437,9 +437,18 @@ void
//---------------------------------------------- //----------------------------------------------
Check(warehousePointer); Check(warehousePointer);
((L4Warehouse*)warehousePointer)->gaugeImageBin.Get( // DEV-COMPOSITE GUARD: the reconstructed BTL4GaugeRenderer's warehouse / gaugeImageBin
interesting_entity->GetResourceID() // is not fully wired (its internal chain reads garbage -> WarehouseBin::Find AVs).
); // Skip the per-entity gauge-IMAGE caching when the gauge renderer is woken on a dev
// box (BT_DEV_GAUGES); the radar/secondary composite doesn't need per-mech images.
// Inert on the pod (the real warehouse is valid). FAITHFUL FOLLOW-UP: fix the
// BTL4GaugeRenderer warehouse reconstruction (likely a shadowed warehousePointer).
if (getenv("BT_DEV_GAUGES") == NULL)
{
((L4Warehouse*)warehousePointer)->gaugeImageBin.Get(
interesting_entity->GetResourceID()
);
}
//---------------------------------------------- //----------------------------------------------
// Update gaugeRenderer // Update gaugeRenderer
//---------------------------------------------- //----------------------------------------------
+47
View File
@@ -98,6 +98,17 @@
// ); // );
// DEV-COMPOSITE (option B): opt-in via BT_DEV_GAUGES. When set, SVGA16 does NO
// per-surface D3D output (no per-adapter devices/windows/present) -- it only holds
// the CPU pixelBuffer the gauge widgets raster into; the MAIN renderer composites
// that buffer as a window inset. The pod multi-surface path is left untouched.
static bool DevGaugeComposite()
{
static int v = -1;
if (v < 0) v = (getenv("BT_DEV_GAUGES") != NULL) ? 1 : 0;
return v != 0;
}
void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed, int *secondaryIndex, int *aux1Index, int *aux2Index) void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed, int *secondaryIndex, int *aux1Index, int *aux2Index)
{ {
NUMGAUGEWINDOWS = 0; NUMGAUGEWINDOWS = 0;
@@ -117,8 +128,22 @@ void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed
NUMGAUGEWINDOWS++; NUMGAUGEWINDOWS++;
} }
if (DevGaugeComposite())
{
// DEV COMPOSITE (option B): force the "no D3D output surfaces" path. Do NOT
// create per-surface devices/windows -- FindBestAdapterIndices hands us the
// primary adapter (up to 3x) on a dev box, but a 2nd/3rd fullscreen device
// can't be created there. The base Video16BitBuffered pixelBuffer still
// exists and the widgets raster into it; the main renderer composites it.
NUMGAUGEWINDOWS = 0;
}
if (NUMGAUGEWINDOWS == 0) if (NUMGAUGEWINDOWS == 0)
{ {
// (defensive) NULL the per-surface arrays so ~SVGA16 delete[] is safe on the
// no-surface path (they are not allocated below).
gaugeWindows = NULL; mDevice = NULL; mVertBuffers = NULL;
mSurfaces = NULL; mLockFlags = NULL; mPresentParams = NULL; mSurfaceRects = NULL;
return; return;
} }
@@ -202,6 +227,15 @@ void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed
{ {
DEBUG_STREAM<<"Couldn't create Direct3D device!"<<std::endl<<std::flush; DEBUG_STREAM<<"Couldn't create Direct3D device!"<<std::endl<<std::flush;
PostQuitMessage(1); PostQuitMessage(1);
// GUARD (platform profile): mDevice[j] is null/uninitialised on
// failure, and the code below (GetDeviceCaps @next line, CreateTexture,
// CreateVertexBuffer) derefs it -> segfault. This is hit when the POD
// profile runs on a box WITHOUT the pod's extra adapters/monitors (a
// second fullscreen gauge device can't be created). PostQuitMessage
// already asked to bail; break so we exit the surface loop cleanly
// instead of crashing. INERT on real pod hardware (CreateDevice
// succeeds there) -- does not change the pod multi-surface behavior.
break;
} }
D3DCAPS9 caps; D3DCAPS9 caps;
@@ -4040,6 +4074,13 @@ Logical SVGA16::Update(Logical forceAll)
return False; // Do no more! return False; // Do no more!
} }
// DEV-COMPOSITE GUARD (option B): dev-composite SVGA16 does NO per-surface D3D
// (BuildWindows forced NUMGAUGEWINDOWS=0 + allocated nothing); the mSurfaces[0]
// path below would index an absent array. The main renderer composites the shared
// CPU pixelBuffer as a window inset instead. Inert on the pod.
if (DevGaugeComposite() || NUMGAUGEWINDOWS <= 0)
return False;
if (++mDisplayToUpdate >= NUMGAUGEWINDOWS) if (++mDisplayToUpdate >= NUMGAUGEWINDOWS)
mDisplayToUpdate = 0; mDisplayToUpdate = 0;
@@ -4221,6 +4262,12 @@ Logical SVGA16::Update(Logical forceAll)
void SVGA16::Refresh() void SVGA16::Refresh()
{ {
// DEV-COMPOSITE GUARD (option B): dev-composite does NO per-surface D3D; skip the
// per-surface clear/present (the main renderer composites the CPU pixelBuffer
// inset). Inert on the pod.
if (DevGaugeComposite() || NUMGAUGEWINDOWS <= 0)
return;
HRESULT hr; HRESULT hr;
V( mDevice[mDisplayToUpdate]->Clear(0, NULL, D3DCLEAR_TARGET, 0xFF000000, 1.0f, 0) ); V( mDevice[mDisplayToUpdate]->Clear(0, NULL, D3DCLEAR_TARGET, 0xFF000000, 1.0f, 0) );
+75 -6
View File
@@ -217,18 +217,87 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
return 0; return 0;
} }
// Environment defaults (dev box: keyboard controls, single window). // ---------------------------------------------------------------------------
if (getenv("L4CONTROLS") == NULL) putenv("L4CONTROLS=KEYBOARD"); // PLATFORM PROFILE -- dev (default) vs pod. This is NOT a code fork: the
// Renderer config: the real BattleTech DPL environment (paths/fog/lights/ // profile just selects WHICH environment preset the existing video/gauge/input
// ambient/projection + cached shapes). RP set L4DPLCFG=RPDPL.INI; BT's is // code reads (the pod multi-surface path already exists -- FindBestAdapterIndices
// BTDPL.INI (in the pod BT dir). Without this, DPLReadEnvironment falls back // / SVGA16::BuildWindows / L4GaugeRenderer -- gated on these env vars).
// to dpldflt.ini and the cavern world never gets its environment. // dev = single 800x600 window + keyboard (no L4GAUGE -> MakeGaugeRenderer
// returns NULL, btl4app.cpp:353). The working dev build.
// pod = multi-surface gauges/MFDs (L4GAUGE set) + RIO cockpit I/O, mirroring
// content/SETENV.BAT (the authentic pod preset). The `if getenv==NULL`
// guard lets a real pod environment (SETENV.BAT) override every value.
// Select with env BT_PLATFORM=pod|dev (robust; primary) or a `-platform pod`
// launcher arg. ⚠ A FULL pod run needs the pod's 2 adapters / 7 monitors + RIO
// & plasma serial + GAUGE\L4GAUGE.INI (Phase-8, on real hardware). On a dev box
// `-platform pod` ATTEMPTS the path (FindBestAdapterIndices degrades to the
// primary adapter) -- selectable for bring-up, not fully validated off-pod.
// ---------------------------------------------------------------------------
int gBTPlatformPod = 0;
{
const char *pe = getenv("BT_PLATFORM");
if (pe && _stricmp(pe, "pod") == 0) gBTPlatformPod = 1;
// Convenience: also accept a raw `-platform pod` on the command line,
// parsed off lpCmdLine independently of L4Application::ParseCommandLine.
const char *pf = lpCmdLine ? strstr(lpCmdLine, "-platform") : 0;
if (pf)
{
char pv[16] = { 0 };
if (sscanf(pf + 9, " %15s", pv) == 1 && _stricmp(pv, "pod") == 0)
gBTPlatformPod = 1;
}
}
if (gBTPlatformPod)
{
// POD profile. RIO cockpit input, which falls back to KEYBOARD when the
// serial port isn't present (e.g. a dev box: "RIO initialization failed!
// Shutting Down Serial Port!" -> keyboard).
if (getenv("L4CONTROLS") == NULL) putenv("L4CONTROLS=RIO,KEYBOARD");
// The multi-surface gauges/MFDs are a POD-HARDWARE capability: each surface
// needs its OWN fullscreen D3D device on one of the pod's 2 video cards
// (separate adapters), which can't be created on a typical dev box -- the
// L4GaugeRenderer/SVGA16 path bails there. So `-platform pod` does NOT
// auto-enable them; it stays bootable everywhere as single-window + the pod
// INPUT profile. On the REAL pod the gauges come from content/SETENV.BAT
// (which sets L4GAUGE + the gauge/adapter env); the existing, untouched
// FindBestAdapterIndices / SVGA16 / L4GaugeRenderer path then runs.
// To force-test the surfaces off-pod, set L4GAUGE explicitly (needs >=2 real
// display ADAPTERS, not just monitors). FOLLOW-UP for a dev-visible pod HUD:
// composite the MFDs/gauges into the single window ("MFD compositing on dev").
if (getenv("L4GAUGE") != NULL && getenv("L4PLASMA") == NULL)
putenv("L4PLASMA=com2"); // only when the pod env already asked for gauges
}
else
{
// DEV profile (default): keyboard controls, single 800x600 window.
if (getenv("L4CONTROLS") == NULL) putenv("L4CONTROLS=KEYBOARD");
}
// Shared renderer/config defaults (both profiles). L4DPLCFG=BTDPL.INI gives
// DPLReadEnvironment the real BT environment (paths/fog/lights/ambient) instead
// of the dpldflt.ini fallback.
if (getenv("L4DPLCFG") == NULL) putenv("L4DPLCFG=BTDPL.INI"); if (getenv("L4DPLCFG") == NULL) putenv("L4DPLCFG=BTDPL.INI");
if (getenv("DPLARG") == NULL) putenv("DPLARG=1"); if (getenv("DPLARG") == NULL) putenv("DPLARG=1");
if (getenv("MULTISAMPLE") == NULL) putenv("MULTISAMPLE=0"); if (getenv("MULTISAMPLE") == NULL) putenv("MULTISAMPLE=0");
if (getenv("TARGETFPS") == NULL) putenv("TARGETFPS=60"); if (getenv("TARGETFPS") == NULL) putenv("TARGETFPS=60");
if (getenv("MAXPARTICLES")== NULL) putenv("MAXPARTICLES=8192"); if (getenv("MAXPARTICLES")== NULL) putenv("MAXPARTICLES=8192");
// DEV-COMPOSITE GAUGES (opt-in, default OFF): BT_DEV_GAUGES wakes the (otherwise
// dormant) gauge renderer so the pod's gauges/MFDs render + can be composited into
// the dev window. Setting L4GAUGE makes MakeGaugeRenderer build the renderer; on a
// dev box the pod multi-surface (SVGA16 per-adapter fullscreen devices) stays at 0
// surfaces (guarded in SVGA16::Update), and a composite pass in the main renderer
// blits the CPU-rastered pixelBuffer as a window inset. See docs/GAUGE_COMPOSITE.md.
if (getenv("BT_DEV_GAUGES") != NULL && getenv("L4GAUGE") == NULL)
putenv("L4GAUGE=640x480x16");
std::cout << "[boot] platform profile: "
<< (gBTPlatformPod ? "POD (RIO cockpit input; multi-surface gauges/MFDs via pod hardware or explicit L4GAUGE)"
: "DEV (single window + keyboard)")
<< std::endl << std::flush;
// DEBUG (bring-up, default OFF): force the player mech to walk full-ahead with // DEBUG (bring-up, default OFF): force the player mech to walk full-ahead with
// no keypress so locomotion + camera-follow can be verified in a headless run. // no keypress so locomotion + camera-follow can be verified in a headless run.
// BT_FORCE_THROTTLE may carry a VALUE: +1 full run, ~0.3 walk, -1 reverse (default 1.0). // BT_FORCE_THROTTLE may carry a VALUE: +1 full run, ~0.3 walk, -1 reverse (default 1.0).
+25 -2
View File
@@ -2,10 +2,33 @@
rem Launch btl4.exe against the bundled content tree. rem Launch btl4.exe against the bundled content tree.
rem The game requires its working directory to be the content dir (BTL4.RES, rem The game requires its working directory to be the content dir (BTL4.RES,
rem VIDEO\, BTDPL.INI, *.EGG are all resolved relative to cwd). rem VIDEO\, BTDPL.INI, *.EGG are all resolved relative to cwd).
rem Usage: run.cmd [EGG] (default DEV.EGG = grass/day) rem
rem Usage: run.cmd [EGG] [pod|dev] (args order-independent)
rem EGG a mission descriptor in content\ (default DEV.EGG = grass/day)
rem pod | dev platform profile (default dev):
rem dev = single 800x600 window + keyboard (the dev test build)
rem pod = RIO cockpit input + (on real pod hardware) the
rem multi-surface gauges/MFDs. On a dev box pod boots
rem single-window (the multi-surface needs the pod's 2
rem video cards); it still exercises the pod input/config.
rem (Equivalent: set BT_PLATFORM=pod, or pass -platform pod straight to the exe.)
setlocal setlocal
set CFG=Debug set CFG=Debug
set EGG=%1 set EGG=
set PROF=dev
:parse
if "%~1"=="" goto done
if /i "%~1"=="pod" (set PROF=pod& shift& goto parse)
if /i "%~1"=="dev" (set PROF=dev& shift& goto parse)
set EGG=%~1
shift
goto parse
:done
if "%EGG%"=="" set EGG=DEV.EGG if "%EGG%"=="" set EGG=DEV.EGG
if /i "%PROF%"=="pod" set BT_PLATFORM=pod
echo [run] egg=%EGG% profile=%PROF%
cd /d "%~dp0..\content" cd /d "%~dp0..\content"
"%~dp0..\build\%CFG%\btl4.exe" -egg %EGG% "%~dp0..\build\%CFG%\btl4.exe" -egg %EGG%