--- id: gauges-hud title: "Cockpit Gauges / MFD HUD system" status: established source_sections: "docs/GAUGE_COMPOSITE.md (full map); CLAUDE.md §8" related_topics: [reconstruction-gotchas, decomp-reference, subsystems, pod-hardware] key_terms: [gauge, methodDescription, attribute-pointer, GaugeRenderer, MFD, PCC] open_questions: - "Condenser valve control gates on the owning BTPlayer game-mode flag (mech+0x190), likely off in basic missions" - "Status-message queue (StatusMessagePool) is a NULL stub -> MessageBoard is empty" --- # Cockpit Gauges / MFD HUD The pod's cockpit instruments: the secondary MFD (radar/heat/comm), the 5 mono MFDs, and the engineering screens. Full map + per-widget history in **`docs/GAUGE_COMPOSITE.md`**. **The gauge system is COMPLETE** at the registration+binding level: all 50 config attribute bindings resolve (0 NULL) and every config gauge primitive is registered + built (0 parse-skips). [T2] ## Architecture (three layers) 1. **The config** `content/GAUGE/l4gauge.cfg` — a text file the engine `GaugeInterpreter` parses (`BuildConfigurationFile → Initialize → GetProcedureBody → ParsePrimitive`). Each mech's gauge tree is built from the label `GetGameModel()+"Init"` (e.g. `bhk1Init`), which invokes shared blocks (`MechInit`, `Secondary1`) that `configure` the ports and call gauge primitives. [T1] 2. **Widgets** — each `keyword(...)` in the config resolves to a `MethodDescription` in `BTL4MethodDescription[]` (btl4grnd.cpp). An UNREGISTERED keyword is **parse-skipped** (never built). Base primitives (`numeric`/`digitalClock`/`rankAndScore`/`vertBar`-base/`segmentArc`) are ENGINE (L4GAUGE.cpp); BT-specific ones (`vertBar`/`map`/`pilotList`/`GeneratorCluster`/ `sectorDisplay`/`prepEngr`/`messageBoard`/`vehicleSubSystems`/the ColorMapper family) are reconstructed + registered. [T2] 3. **Data binding** — gauges bind to game state by NAME via the engine [[attribute-pointer]] system: the config's `Subsystem/Attribute` (e.g. `HeatSink/CurrentTemperature`) resolves through `ParseAttribute → FindSubsystem (stricmp GetName()) → GetAttributePointer`. A class PUBLISHES an attribute via a `AttributeID` enum + `static AttributePointers[]` (`ATTRIBUTE_ENTRY`) + `GetAttributeIndex()` chained to its parent. ⚠ DENSE-TABLE HAZARD (see [[reconstruction-gotchas]] §11). [T2] ## Reconstructing a widget — the recipe `MethodDescription Class::methodDescription = { "keyword", Class::Make, { ParameterDescription rows } };` + a line in `BTL4MethodDescription[]` before `&BTL4ChainToPrevious`. The static `Make(int port, Vector2DOf pos, Entity*, GaugeRenderer*)` reads `methodDescription.parameterList[]`, allocs (`operator new(binSize)` or plain `new`), placement-news the ctor. Then ctor/dtor/ TestInstance/BecameActive/Execute. **Gotchas that bite EVERY widget:** - Container-Execute must override (§10 gotchas) — else `Gauge::Execute` aborts. - `/FORCE` trap — a prose-only slot AVs on first call. - Databinding trap — never raw-read owner offsets; use a bridge (`BTGetSubsystemAuxScreen`, …). - ReconStream no-op — use `DEBUG_STREAM` for logs, not `DebugStream`. - Lazy build — wait for the gauge window before concluding "not built" (`BT_GAUGE_SKIP_LOG`). [T2] ## The completed widgets (this project's gauge wave) - **Attributes published:** HeatSink table (Degradation/Failure/NormalizedPressure/CoolantMassLeakRate/ ValveSetting/…), PoweredSubsystem (InputVoltage), MechWeapon (OutputVoltage), Mech (LinearSpeed, radar Position/Quaternion), Sensor (RadarPercent), **AggregateHeatSink (AmbientTemperature=300 — the LAST NULL)**. [T2] - **Widgets reconstructed + registered:** ColorMapper family (cmHeat/cmCrit/cmArmor/multiArmor), headingPointer, vertBar (VertTwoPartBar), segmentArcRatio, oneOfSeveralPixInt, map (radar), PlayerStatus, vehicleSubSystems (the engineering cluster panels), LeakGauge, vertNormalSlider, **pilotList** (Comm KILLS/DEATHS), **GeneratorCluster**, **SectorDisplay** (radar SECTOR X/Z read-out — live), **PrepEngrScreen** (12 engineering-screen label overlays), **MessageBoard** (comm ticker — deferred-empty). [T2] - **Condenser valve gauge:** ValveSetting→coolantFlowScale reads the authentic **1/N** (`RecomputeCondenserValves`, FUN_0049f788, was a no-op stub). [T2] ## Dev composite (off-pod) `BT_DEV_GAUGES` renders the 6 pod [[MFD]] surfaces in a separate 960×384 window (bit-plane masks over one shared `SVGA16` pixelBuffer; `SVGA16::DrawDevSurface`). On the POD they come from `SETENV.BAT`/`L4GAUGE` on the real multi-adapter hardware (`FindBestAdapterIndices`/`BuildWindows`, intact). The `overlay` port (SectorDisplay lives there) shares the `sec` physical surface via a different bit-plane (0x00C0). [T2] ## Authentically-static (do NOT "fix") `currentTemperature ~77` (huge thermalMass ~1.39e6 → a 3.5-heat shot rounds to zero); Degradation/Failure temps are fixed markers; AmbientTemperature 300; CoolantMassLeakRate 0 on a pristine mech (damage-gated); cmArmor/cmCrit all-green (undamaged solo player — BT is PvP-only); RANK 1 solo; MessageBoard empty (no status messages exist in bring-up). The Heat MFD is authentically NEAR-STATIC, not a live heat-accumulation display. [T2] ## Remaining = DATA FEEDS, not widgets (deferred systems) The **condenser valve CONTROL** gates on the owning **BTPlayer**'s game-mode flag (`mech+0x190`, `GetPlayerLink()`, via `MechSubsystem::IsDamaged`/`FUN_004ac9c8` reading `player+0x274`) — NOT the 0xBD3 messmgr (a common earlier misconception; 0xBD3 is a damage/explosion hub at `Mech[0x10d]`=0x434). In a basic mission the valve/mode flags are likely off, so the dormant valve is plausibly AUTHENTIC — see [[open-questions]] `mech+0x190`. The **MessageBoard** feed needs **StatusMessagePool** (a NULL stub) + the per-player status queue. `SeekVoltageGraph`'s 4 Seek* attrs are a cluster-child (not config-called). [T2] ## Cockpit HUD reticle (main screen, inside view) — LIVE `BTReticleRenderable` (0x358 bytes, ctor @004cc40c) draws over the finished 3D frame in cockpit view only, via the recovered **dpl2d** 2D display-list API (recorders @0x487f34-0x488630; opcode map + coordinate model in `phases/phase-02-dpl2d-reticle.md`; port: `game/reconstructed/dpl2d.cpp`). Geometry is the ctor's hardcoded calibration (originX 0.35, originY 0.25, scaleY 0.5, right range ladder 0..1200 m, bottom heading tape, center cross + dot; tick ladders via FUN_004cd938). The range caret binds to the live target range (`BTSetHudTargetRange`, fed by mech4's targeting step). **Weapon pips:** the build loop (part_014.c:5386) registers EVERY subsystem `IsDerivedFrom(0x511830 = MechWeapon)` — lasers, PPCs AND missile launchers (BLH = 7 pips) — via `AddWeapon` @004cdac0 (verified store map in btl4vid.hpp). **The per-frame `Execute` @004cdcf0 is RECOVERED (task #37, capstone disasm via `tools/disas2.py` — the full annotated read: the task-37 commit + btl4vid.cpp comments) [T1], and every instrument is now live [T2]:** - **Right ladder** = range 0–1200 m: a YELLOW width-2 BAR from ladder-top to the caret + a **GREEN width-1 caret triangle** (ctor @4550-4551 sets green/1 AFTER the yellow bar call — transcription color bug caught by a period reference screenshot, 2026-07-09; same for the bottom bowtie carets @4569-4570); pegs at 1200 with no target; the DISPLAYED range slides at **500 m/s** toward the true pick range (HudSimulation :5652 [T1]). ⚠ A period pod screenshot (`C:\git\image.webp`, likely a DIFFERENT pod revision — its crosshair is ~2.5× taller with arrowhead arms, NOT our binary's ±0.04..0.16 program) structurally CONFIRMS our layout: yellow bar + green caret + colored pip dots on the right ladder, bottom tape + green bowtie, rotating compass circle bottom-left, and a mid-ladder range reading with no lock (the world-pick terrain range). Our glyph constants remain [T1] from OUR 4.10 binary. **The weapon pips sit on this same ladder at each weapon's authored max-range mark** — caret below a pip = that weapon reaches the target. BLH authored data (live dump): 3× ER-M laser red @**500 m** (two stacked at one column; PipExtendedRange=1), 2× missile amber (0.6,0.4,0) @**800 m**, 2× PPC blue @**900 m** — so the 7 pips read as 3 weapon-SYSTEM groups. (The engineering-panel "RANGE 500M" labels are panel text; the authoritative reach is the streamed WeaponRange.) - **Bottom 21-tick tape = the TORSO-TWIST indicator** (NOT a heading tape): deflection line + carets at `∓(span/2)·(RotationOfTorsoHorizontal / HorizontalTorsoLimit)` (HUD attrs 4/5/6). Fixed-torso BLH: centred (authentic static). - **Circle-with-stem = the COMPASS** (HUD attr 0xD CompassHeading, rad→deg rotation) at `(botX, botY−3·tickMajor−0.03)`; the **THREAT trail** (attr 0xC ThreatVector) draws inside its rotated frame: 0.05-unit attack-direction marks, fresh <2 s red, expiring at 6 s, 1 s blink tick. Port feed: the player's TakeDamage handler pushes the impact direction. - **Pips** (composed into subB6, master-called): hidden when the weapon's DAMAGE state == 1 (destroyed, attr 1); LIT (A) when the FIRE-CYCLE state == 2 (loaded, attr 0x1c; port source rechargeLevel ≥ 1) else dark ring (B, charging); filtered by `weaponMode & elementMask&0xF` (the weapon-GROUP bits Front/Rear/Left/Right). **Range plays NO part** — Execute never reads the stored TargetWithinRange slots. - **Lock ring** = subB9 (ring+cross) at frame centre, **SPINNING 4°/frame** while the Lock attr (0xA) is up. The Lock PRODUCER is the authentic HudSimulation rule (part_013.c:5619-5634 [T1], wired task #38): lock requires a target AND your own HUD's host zone damage < **0.75** (a shot-up targeting computer loses lock) AND the targeted zone's damage < **1.0** (whole-mech target checks zone 0 — so a wreck's dead zone can't re-lock). The hotbox stays visible without lock (box = HotBoxVector, ring = Lock — separate signals). The binary also hangs the PNAMEx.bgf player-name mesh on the 3D marker here (3D chain still deferred). - **Simple-X mode** (PrimaryHudOn off, mask 0x20): the minimal reticle — a small green cross (±0.02..0.08 arms) riding the aim translate (ctor @4689-4705 [T1]) — swapped for the full HUD by Draw's state switch. - **Target HOTBOX** (attr 0xB HotBoxVector) = a rectangle hugging the projected extents — x±4 around the hotbox point, +1/−11.5 vertical (K=2.8145 baked projection; the port uses the live per-axis projection) — switching to the left/right edge ARROW past ±1.6 or behind. - Reticle state Off/On + `PrimaryHudOn` (mask 0x20) picks full HUD vs the "simple X" list; the aim group translates by `reticlePosition` = the TORSO BORESIGHT (SetMatrix; NOT a free cursor — see [[combat-damage]] Targeting: the stick twists the torso, no mouse-aim exists). This recovery also CONFIRMS the HUD attr-table ids 4/5/6/8/0xA/0xB/0xC/0xD name↔use pairings (hud.hpp had flagged them uncertain). Deferred: PNAME1-8.bgf 3D marker chain. (The canopy shell is now authentic and shows by default — see [[cockpit-view]]; `BT_HIDE_COCKPIT=1` hides it.) ## Key Relationships - Full history: `docs/GAUGE_COMPOSITE.md`; reticle recovery: `phases/phase-02-dpl2d-reticle.md`. - Uses: [[attribute-pointer]] + [[reconstruction-gotchas]]; reads [[subsystems]] state. - Renders on: [[pod-hardware]] MFD surfaces.