Files
BT411/docs/GAUGE_COMPOSITE.md
T
arcattackandClaude Opus 4.8 c13e72d0ba 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>
2026-07-06 10:18:40 -05:00

9.4 KiB
Raw Blame History

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.