Files
BT411/context/gauges-hud.md
T
arcattackandClaude Fable 5 70eea6c1a4 Boresight parallax FIXED + the #4/#5 verdict instrumentation (Gitea #16, #4, #5)
PARALLAX (#16): the pick/fire ray was anchored at mech.y+5.0 (a port
improvisation) while the sight line ran from the eyepoint (y=6.23) --
two parallel rays whose constant offset grew into the reported low-miss
as range closed (measured ry +0.072 @50u -> +1.54 @2.7u).  The decomp's
sight and pick share the eye origin (HudSimulation @4b7830 chain).  Fix:
the viewpoint mech's cockpit eye owns the aim-camera publish in BOTH
views, origin = its own eye translation; leveling + deliberate elevation
untouched; chase view now converges to cockpit ballistics (V cannot
change where shots land).  After: pick pinned to the crosshair (ry <=
5e-6) from 50u to point-blank; 26 laser + 8 missile center-mass hits at
3/4-screen.  Awaiting the reporters' approach-test.

VERDICT INSTRUMENTATION (#4 closed authentic, #5 verdict posted):
BT_RANGE_LOG per-frame pick tracing + BTGroundRayHitExact analytic
cross-check (0/8000 arena fall-throughs; cavern 6/8400 single-frame
grazes -- the 'crazy sliding' is the authentic world-pick + 500 m/s
slide over depth discontinuities); BT_AUD_TAIL StopNote/fade timing +
BT_FIRE_PULSE single-shot driver (the energy 'buzz' is the AUTHORED
charging loop: crescendo through recharge, 1.309s authored release,
measured within one frame).  CAVERN.EGG: solo cavern test egg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 14:51:54 -05:00

429 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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:
- "MessageBoard LIVE 2026-07-12 (kill ticker); remaining: only strip 0 (kill) is produced -- survey the other btsmsgs.pcx strips for authentic producers"
- "HUD attr table RESOLVED 2026-07-19 (Gitea #10 audit): re-dumped @5110b8 -- the table starts one record earlier (id 3 FlickerRate@0x1D8), every hud.hpp offset was shifted one slot; hud.hpp/CLASSMAP corrected, no behavioral impact (the reticle feeds via port globals). See GAUGE_COMPOSITE.md AUDIT finding B"
- "SeekVoltageGraph RECONSTRUCTED 2026-07-19 (Gitea #11, was #10 finding A): full widget landed (see §SeekVoltageGraph below) -- ghosts gone steady-state (BT_PRESET_HOLD verification); remaining polish: a 1-frame transition artifact when a BT_SHOT lands on the exact page-switch frame (label BecameActive vs the graph's next rated Execute -- same lag class as the binary; self-heals next frame)"
- "Secondary-view cycling RESOLVED 2026-07-19 (Gitea #6): the selector is the DISPLAY mode (CycleDisplayMode -> vtbl+0x4C override @4d1ae4), NOT CycleControlMode; desktop 'N' / pad RightThumb wired; pixel-verified dama->crit->heat"
- "Upper-MFD PRESET pages RESOLVED 2026-07-19 (Gitea #9): SetPresetMode table @0051dbf0 re-decoded (little-endian -> ModeMFD bits 0-14), per-MFD pod button banks identified from the .CTL dump, desktop J/K/L cycle wired"
- "Always-active msg-4 records IDENTIFIED 2026-07-20 (glass input audit): 0x2C = Reservoir InjectCoolant (the flush button), 0x2F/0x2E/0x2D/0x2B/0x2A/0x29 = Condenser1-6 MoveValve, 0x1A-0x1D = GeneratorA-D ToggleGeneratorOnOff (@0050fb90, unreconstructed); plus 0x13 = Mech DuckRequest (crouch), 0x28 = Mech BalanceCoolant, 0x12/0x14 = ThermalSight/Searchlight toggles -- see pod-hardware.md + docs/GLASS_COCKPIT.md 2026-07-20"
- "MP DEATHS resolved 2026-07-12 (observed-death tally + display clamp); remaining: verify multi-death tallies stay in sync across a long session (GAUGE_COMPOSITE.md)"
---
# 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]
**AUDIT 2026-07-19 (Gitea #10):** the full per-widget verdict table (45 rows: 34 CORRECT / 1
WRONG-filed / 6 AUTH-STATIC / 6 DEFERRED-FEED) is in `docs/GAUGE_COMPOSITE.md` §"AUDIT
2026-07-19" — the one WRONG was the missing SeekVoltageGraph reconstruction (the emitter/myomer
eng pages' POWER curve + the authentic top-box eraser; caused the stale-ammo "SYSTEM 10 PPC"
ghost). **RESOLVED (Gitea #11): full reconstruction landed — §SeekVoltageGraph below.** [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 `<Name>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<int> 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]
## MP gauge-window FREEZE = dangling bindings + permanent SEH disable (Gitea #12, 2026-07-19) [T2 log-convicted]
The live-MP "dev-gauges window froze entirely mid-session" incident (issue #12) is NOT a
swap-chain/present bug: `scratchpad/incident_2157/operator_1.log` shows a burst of
`[gauge-fault] '<Widget>' Execute FAULTED -> gauge DISABLED` (PilotList, HeadingPointer,
MapDisplay, SectorDisplay, TwoPartBars, NumericDisplayScalar …) exactly while ALL THREE mechs
were being RE-CREATED at mission launch (second `[zonebuild]`/`[cyl]` set at new addresses —
the session had a pre-launch host drop/rejoin; a normal 2-player LAN launch on the same build
did not re-stream and did not freeze). The gauge tree had been built (lazily) during the
LOBBY phase against the FIRST-stream mechs; the re-stream freed them; every gauge Execute then
AV'd on its dangling [[attribute-pointer]]s and the `BT_DEV_GAUGES` SEH guard
(`Gauge::GuardedExecute`, GAUGE.cpp:618) DISABLED each one — `Disable(True)` sets `rate=0`
PERMANENTLY, nothing re-enables or rebinds → the whole window static for the rest of the
session (= symptoms 1+3). The AUTHENTIC engine flow handles mission transitions by
`Application::Shutdown → gaugeRenderer->Shutdown() → ShutdownImplementation → Remove(0)` (all
gauges deleted, tree lazily rebuilt next mission, APP.cpp:787/GAUGREND.cpp:3264) — the port's
in-session re-stream path bypasses it. **FIX LANDED (2026-07-19, awaiting human MP verify):**
`BTL4GaugeRenderer::TearDownForViewpointRestream()` (btl4grnd.cpp) performs the ENTITY-BOUND
half of the engine ShutdownImplementation sequence, same order (RemoveAllAlarms → Remove(0) →
entity-grid Clear), deliberately KEEPING warehouse/graphics-ports/interpreter/controls-L4Lamps
(RemoveAllLamps mid-session would delete the CONTROLS-owned L4Lamps behind the buttonGroup's
`&lamp->automaticValue` registrations — a dangling mapping; the mech-state "lamps" like
AnimatedSubsystemLamp are Gauges and go with Remove(0)). Called from
`BTL4Application::MakeViewpointEntity` before `ConfigureForModel("Init", entity)` whenever the
viewpoint is a RE-make (that handler empirically runs once per stream — twice in the incident
log, `[ctrlmap] installing` ×2) — the tree then rebuilds bound to the NEW mech. Log sentinel:
`[gauge] viewpoint re-stream: tearing the gauge tree down for rebuild`. Residual exposure:
`Remove(0)` flushes each gauge (`Update(gaugeRate_A)`, GAUGREND.cpp:3465) before deleting it, so
gauge CONNECTIONS may read the freed mech once more (plain reads; Execute stays SEH-guarded).
## The secondary screen's THREE views (Damage / Critical / Heat) — mode-gated [T0/T1/T2]
The `sec` port stacks three mode-gated mech-schematic layers at offset (50,0) over the
always-on radar/heading/speed/messageBoard (`Secondary1`): **Damage** (`ModeSecondaryDamage`,
`<mech>dama.pcc` + `colorMapArmor`/`colorMapperMultiArmor` — 4 silhouettes front/left/right/back,
pixel-plane ids 60-63, per-DAMAGE-ZONE `dz_*` tint through the adpal→adpal2 ramp), **Critical**
(`ModeSecondaryCritical`, `<mech>crit.pcc` + the cmCrit per-SUBSYSTEM list), **Heat**
(`ModeSecondaryHeat` + cmHeat). Mode bits (BTL4MODE.HPP, `nextModeBit`=0 [T0]): Mapping=0x8000,
NonMapping=0x10000, Intercom=0x20000, **SecondaryDamage=0x40000, SecondaryCritical=0x80000,
SecondaryHeat=0x100000** (bits 18-20). `ModeInitial` includes **SecondaryDamage** → the ARMOR
view is the default-on layer (our port creates `BTL4ModeManager(ModeInitial)`, btl4app.cpp:303).
The schematic shows the pilot's OWN mech only — there is no target-damage readout in the cockpit.
**The selector is the DISPLAY mode, not the control mode (Gitea #6, RESOLVED 2026-07-19) [T1→T2].**
The L4 vtable @0051e440 pins the slots: **+0x4C = @004d1ae4** — dispatched by
`CycleDisplayModeMessageHandler` (FUN_004afcac) with the new `displayMode` (0/1/2) — is the
`NotifyOfDisplayModeChange` override that clears bits 18-20 and sets the mask from the table
@0051dbe4 {0x40000,0x80000,0x100000}. (The old "SetControlMode @004d1ae4 switches the secondary
VIEW" claim was the mislabel that kept the port's copy a never-called non-virtual.) **+0x48 =
@004d1acc** — dispatched by `CycleControlModeMessageHandler` (FUN_004afbe0) — just forwards to
the base RET no-op @004b048c: a BAS/MID/ADV control-mode change NEVER touches the secondary
view (empirically confirmed: M cycles the CONTROL MODE gauge lamp, mask bits 18-20 unchanged,
schematic stays on ARMOR DAMAGE). **Authentic pod inputs** (streamed type-6 `.CTL`
EventMappings, dumped live via `BT_CTRLMAP_LOG`): secondary-panel button **0x15 → msg 0x15
CycleDisplayMode** (the manual-p13 "'Mech status Info center", bottom left of the secondary
screen: Armor/Critical/Heat Damage Schematic cycle), button **0x18 → msg 0x14 CycleControlMode**
(the manual-p6 mode button, top right), buttons 0x10/0x11 → ZoomIn/ZoomOut 0x12/0x13 (the map
zoom ± pair). The DOS keyboard fallbacks (Keypress `0x13d`/`0x13e` = extended F3/F4) never fire
under the WinTesla VK map (VK_F3=0x72 collides with 'r', VK_F4=0x73 with 's' — the 'p'/VK_F1
collision class), so the desktop was PINNED on Damage. **Port wiring (mirrors the M/ModeCycle
pattern): 'N' / pad RightThumb → action DisplayCycle → gBTDisplayCycle → `CycleDisplayModeNow()`**
(mechmppr.cpp; the same body the pod button message drives). Pixel-verified live (docked gauges +
BT_SHOT): ARMOR DAMAGE silhouette → CRITICAL DAMAGE subsystem list → HEAT DAMAGE colored list,
mask 0x450421→0x490421→0x510421. Diags: `BT_MODE_LOG`, `BT_VIEWCYCLE_TEST=<frame>`,
`BT_MODECYCLE_TEST=<frame>`.
## The upper-MFD PRESET pages — 3 MFDs × 5 pages (Gitea #9, RESOLVED 2026-07-19) [T0/T1/T2]
The three preset-able MFDs are **Mfd1 (lower left) / Mfd2 (upper center) / Mfd3 (lower right)**,
each a PAIR of bit-planes on one physical monitor: the base **Quad** plane (`Mfd1/2/3`, masks
0x0100/0x0400/0x1000, `btquad.pcx`) and the **engineering-page** plane (`Eng1/2/3`, masks
0x0200/0x0800/0x2000, `bteng.pcx`). Mode bits (BTL4MODE.HPP [T0], bits 0-14):
`ModeMFD{1,2,3}{Quad,Eng1-4}` = `1<<(group*5+item)`**fully disjoint** from Mapping/NonMapping
(15/16), Intercom (17) and the #6 Secondary* trio (18-20); `ModeInitial` puts all three MFDs on
Quad. **What the pages show:** Quad = up to four `vehicleSubSystems` cluster mini-panels (the
quadrants, geometry table @0x51bf34); Eng item i = the FULL-SCREEN engineering detail of the
subsystem streamed onto aux screen `group*4+i` (`sub+0x1dc`; `prepEngr` screens 1-12: "SYSTEM NN"
+ per-class label cells + the cluster's eng child — GENERATOR SELECT A-D, POWER graph, COOLING
loop, DAMAGE (MJ), ammo count …). Unpopulated screens are authored-empty per mech (Blackhawk:
9 of 12 — scr 1/2/4=PPC/Streak6/ERMed, 5-8=Sensors/Myomers/ERMed/ERMed, 9/10=Streak6/PPC;
screens 3, 11, 12 empty).
**`SetPresetMode(group,item)` @004d1b24** swaps the page: table @0051dbf0 = 15 `{clear,set}`
pairs — set = the page's ModeMFD bit, clear = the group's other four (item 0) or all five
(items 1-4). ⚠ The old reconstruction had transcribed the little-endian set column as
BIG-endian dwords (0x01→0x01000000 …), so a preset press set a garbage high bit — and for
group 1 items 3-4 / group 2 items 0-2 STOMPED the live NonMapping/Intercom/Secondary* bits —
while the page bits never moved (the "presets unwired" defect). Fixed against
section_dump.txt:72901-72908; the "does group 2 duplicate #6's secondary views?" concern is
resolved: **no** — group 2 = MFD3, bits 10-14.
**Authentic dispatch (streamed type-19 "L4" .CTL, 121 records dumped via `BT_CTRLMAP_LOG`):**
every MFD has its own 8-button RIO bank whose meanings are MODE-MASK-gated — **Mfd1 =
buttons 0x08-0x0F (AuxLowerLeft), Mfd2 = 0x20-0x27 (AuxUpperCenter), Mfd3 = 0x00-0x07
(AuxLowerRight)**. On a Quad page the bank's buttons DIRECT-SELECT the populated Eng pages
(mapper EventMappings → `MechRIOMapper` messages `Aux1Eng1-4`=0x4-0x7, `Aux2Eng1-4`=0x9-0xC,
`Aux3Eng1-4`=0xE-0x11 → SetPresetMode; no button is streamed for an empty screen); on an Eng
page one button returns to Quad (`Aux1/2/3Quad` = 0x3/0x8/0xD) and the rest remap to the SHOWN
subsystem (per-subsystem msgs: 0x4-0x7 = SelectGeneratorA-D, 0x8 = ToggleGeneratorMode, 0x9 =
ConfigureMappables, 0x3/0xb = unjam/eject-class functions). The `MechRIOMapper` keyboard cases
in @004d1bf0 mirror it as three key rows: `1-4`=MFD1 Eng1-4/`5`=Quad, `a s d f`/`g`,
`z x c v`/`b` — mostly claimed by the port's WASD bindings, hence dead on desktop.
**Port wiring:** the streamed records install and fire on desktop (btinput passes the LIVE
manager mask on button press, so the NUMPAD profile's 0x20-0x27 keys page MFD2 authentically);
the default WASD profile adds **J/K/L → actions Mfd1/2/3Cycle → gBTPresetCycle →
`CyclePresetModeNow(group)`** (btl4mppr.cpp; a port cycle-key shim — 24 mode-dependent pod
buttons don't fit a keyboard — that visits exactly the pod-reachable set: Quad + populated Eng
pages; the body is the authentic SetPresetMode).
**Dev-composite change:** `BTDrawGaugeSurfaces` (L4VB16.cpp) now draws the Eng1-3 planes into
their sibling's cell and SKIPS any mono plane whose port channel is currently `BlankColor`
honoring the mode-driven `reconfigure` (RemapGraphicsPort) so each dev cell shows the ACTIVE
page, like the pod monitor. This SUPERSEDED the 2026-07-12 "frozen-dial" scaffold
(GAUGREND.cpp force-activated all 15 page bits under BT_DEV_GAUGES — removed; it made every eng
screen paint over the shared Eng plane, pinning it on the highest screen). Pixel-verified live
(BT_PRESET_TEST): all three MFDs page Quad → eng details → back to Quad in lockstep with the
[mode] mask log; N/M un-regressed. Diags: `BT_MODE_LOG` ([mode] preset lines),
`BT_PRESET_TEST=<frame>`, `BT_CTRLMAP_LOG`.
## pilotList (Comm KILLS/DEATHS) row semantics + the 1 [T1/T2]
One ROW PER PILOT in the mission (2-player MP = 2 rows — not duplicate displays). KILLS =
`killCount` (the victim's ScoreMessageHandler credits the shooter cross-player, works for both
rows); DEATHS = `Player::deathCount`: engine-inits to **2** (PLAYER.cpp:759), the LOCAL
vehicle-acquire branch zeroes it (btplayer.cpp:1118), then VehicleDead(-1) ++s per death. A
REMOTE player's Player object never runs the local acquire → 2 +1 spawn increment = **1
locked**. **RESOLVED 2026-07-12 [T2]:** deaths now tally per node from LOCALLY OBSERVED events
(the same model as the cross-pod KILLS credit) — a replicant's once-per-death transition calls
`BTPlayerCountObservedDeath` on its owning player's local copy (replicant-gated: the master's
own VehicleDead path counts its node), and `BTPilotDeaths` clamps the 2/1 pre-acquire seed
to 0 for display. Own row counts correctly as before.
## Launcher-panel recharge dial — CORRECTION 2026-07-19: it IS live (slot 17 @004b9c9c) [T1]
The weapon panels' SegmentArc270 tick ring reads MechWeapon::rechargeLevel (+0x320).
**The old "authentically STATIC on projectile weapons" claim was WRONG** — the writer census
missed the anonymous vtable slot 17 (vtbl+0x44) body **@004b9c9c**:
`rechargeLevel = (rechargeRate@0x3DC recoil@0x3E8) / rechargeRate` (capstone disasm, issue
#12; Ghidra never emitted it). The recovered `ProjectileWeaponSimulation` @004bbd04 calls it
every frame in the Loading(3) and unavailable(7) alarm cases — so the launcher dial
authentically ANIMATES 0→1 through each reload. (The census's four other writers stand: ctor
@004b99a8, stream init @004b8fec, ResetToInitialState @004b96ec, and
`Emitter::ComputeOutputVoltage` @004ba738 — slot 17 is the Emitter override of the SAME slot;
@004b9c9c is the MechWeapon/projectile base body.) The launcher panel's other live indicators:
AMMO DIGITS (AmmoBin::ammoCount via the complete-type bridges), jam/fire lamps, eject wipe.
## ConfigMapGauge (the weapon panel's regroup lamp column) — authentically DORMANT
The per-weapon btjoy.pcc joystick image + 4 cm_* state lamps (off/other/only/both) showing,
for each mappable fire button (Pinky/ThumbLow/Trigger/ThumbHigh), whether THIS panel's weapon
is bound to it. **The shipped binary never enables it [T1, task #6]:** no caller of SetColor
@004c6ee0 exists, so color==0 and Execute early-outs. The state loop is reconstructed
(btl4gau2.cpp; table DAT_00518eb4 PE-recovered; sampler = LBE4ControlsManager::
buttonGroup[btn].GetMapState — NOT the ModeManager, the old guard note was wrong) behind the
PORT dev enable `BT_CONFIGMAP=1`. The regroup MECHANISM itself (ConfigureMappables/
ChooseButton, task #6) is live regardless of the gauge.
## Authentically-static (do NOT "fix")
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).
**CORRECTION (task #10, 2026-07-11):** the old "Heat MFD is authentically NEAR-STATIC /
currentTemperature ~77 rounds to zero" claim was computed at the DEGENERATE bring-up heat scale.
At the authentic 1e7-unit economy (tasks #9-#10) the Heat MFD is fully dynamic: weapons run
77→2000 (the authored failure threshold), condensers/generators run 100-1400 under sustained
fire, and the bank plateaus ~600 — temperatures, heatLoad and the heat alarms all animate. [T2]
## Radar view-wedge tracks the torso twist (Gitea issue #1 — FIXED 2026-07-17) [T2]
The SECTOR radar's view cone (`MapDisplay::DrawViewWedge` @004c2484, btl4rdr.cpp) reads
`viewHorizontalRotation@0x31C`, fed by a per-frame `GaugeConnectionDirectOf<Radian>` the ctor
wires from the mech's Torso. The two helpers were NULL stubs, so the connection was never
created and the wedge sat at heading 0 (the reported "cone does not track the twist").
Reconstructed from the binary: `FindSubObject` (FUN_0041f98c) = a SUBSYSTEM-ROSTER walk
(count @+0x124 / array @+0x128) matching the streamed name (sub+0xd4) case-insensitively
(FUN_004d4b58 = tolower-strcmp) — the "Torso" sub-object IS the roster Torso subsystem;
`GetHorizontalRotation` = torso+0x1D8 = `Torso::currentTwist` (layout-locked), reached via the
existing task-#56 bridge `BTGetTorsoTwistAddr` (Radian ≡ {Scalar angle}, exact reinterpret).
VERIFIED live (MadCat, Standard mode Q/E): wedge rot tracks twist 0→−2.21 rad in lockstep, and
the SEMANTIC test passes — body turned away, torso twisted back onto the enemy → reticle goes
green AND the wedge points at the enemy's blip while the scope stays body-fixed. ⚠ Test with a
TWISTING mech: the Blackhawk's torso is FIXED (limits ±0.01°) — its wedge authentically never
moves. Diag: `BT_RADAR_LOG` ([radar] ctor probe + 1 Hz [radar-wedge] rot trace). [T2]
## Remaining = DATA FEEDS, not widgets (deferred systems)
**✅ The condenser valve CONTROL is LIVE (task #13, 2026-07-11) [T2]:** `MoveValve` (id 4, the
Condenser handler table @0x50E52C — exactly one entry) registered + guarded by the REAL
FUN_004ac9c8 = `player+0x274 == 0` (2026-07-18 correction: +0x274 = the egg EXPERIENCE level,
so this is the NOVICE lockout, not a "ROOKIE role" — see [[experience-levels]]; task #12's
`BTPlayerRoleLocksAdvanced` bridge; bring-up seeding = 2 ≈ veteran = UNLOCKED, verified live:
press → valveState 1→5 → flow redistribution 1/6 → 5/10). Desktop: 'C' cycles the selected
condenser (BT_VALVE_SLOT).
**✅ The COOLANT FLUSH is LIVE (Gitea #7, 2026-07-19) [T2]:** `InjectCoolant` (id 4, the Reservoir
handler table @0x50e680, handler @4aee70) — HOLD flushes reservoir coolant through the loops
(InjectCoolant @4aefa4, set%-biased via each sink's flowScale); the coolant **vertBar C**
(`Reservoir/CoolantMass` = coolantLevel@0x12C, l4gauge.cfg:4526) drops live as the tank drains
(BLH: 6.0 → 0 in ~0.6 s held ≈ the manual's 3-4 punches); the bluish FLUSH.PFX condensation
cloud (psfx 19) spawns on the ReservoirState 0→1 edge. Desktop: **'H' HELD** (action Flush).
Full chain + the two ctor decode corrections it surfaced: [[subsystems]] §coolant FLUSH.
Diags: `BT_FLUSH_LOG`, `BT_FLUSH_TEST=<frame>`.
The **MessageBoard** feed needs **StatusMessagePool** (a NULL stub) + the per-player status
queue. [T2]
## SeekVoltageGraph — the eng-page POWER graph AND the top-box eraser (Gitea #11, 2026-07-19) [T1→T2]
The emitter/PPC + myomer engineering pages' "POWER" box widget (btl4gau2.cpp/.hpp; ctor
@004c6798, BecameActive @004c6920, Execute @004c6934; a CLUSTER-CHILD — built by
EnergyWeaponCluster @004c93b0 / MyomerCluster @004c8df4, not a config keyword). Fully
reconstructed from the capstone disasm (Ghidra dropped every x87 arg; tools/disas2.py):
- **View/geometry:** localView = the page's top data box (0x97,0x80)-(0x17d,0x13b) — exactly
230×187, the plot scales; view coords are view-relative (GRAPH2D.h `origin =
areaWithinPort.bottomLeft`). Ctor sets **SetOperation(Xor)** — ticks/cursor erase by redraw.
- **The eraser role:** BecameActive poisons the cached sample (previousVoltage@0xAC = 9999);
the next Execute's change-test (sample the response at **12000 V**) then runs the CLEAR
@004c6be4 (SetOperation(Replace), color 0, filled 1000×1000 clipped to the box) before
replotting — THIS is what erases the sibling pages' stale pixels on the shared Eng bit-plane
(the #10 ghosts). Every topBox=0 PrepEngr page owns a graph; the box erase design is coherent.
- **Plot math (recovered):** polyline v = 0..12000 step 1200 (_DAT_004c6bdc/_be0):
x = Round(response(v)·230), y = Round(v·(1/12000)·187) — the 80-bit consts @004c6bd0/@004c6d74
are EXACTLY 1/12000. Ticks (@004c6c6c): per gear i in [*min..*max], the current gear draws a
full L (axis→point→axis), others 10-px axis stubs; XOR pair moves the highlight. Cursor
(@004c6c30, emitter pages only): 8×8 XOR square at the LIVE voltage (the ctor's
"OutputVoltage" attr pointer). Destroyed subsystem (simulationState==1 via the
BTSubsystemDestroyed bridge, powersub.cpp): centred edestryd.pcc once; revive calls own
BecameActive (vtbl+0xC, slot 3 of PTR_0051a1fc — verified by vtable dump).
- **The sampler == subsystem vtbl+0x3C (slot 15), reached via the BTSeekVoltageSample dispatch
bridge (emitter.cpp → myomers.cpp; databinding rule):** Emitter @004bb42c =
`sqrt(SeekPower(v)/2.0e8)` with SeekPower @004bb3f4 = `damageFraction·v²·0.5·energyCoefficient`
(a LINE in v — the emitter graph is authentically straight); Myomers @004b8f94 =
`sqrt(AvailableOutput(v)·3.6/350)` (steep near-vertical curve at BLH values).
**FUN_004dd138 = sqrt** (part_015.c:4026) — the old "fabs/fp-magnitude" reading was wrong;
Myomers' old best-effort name "GetSpeedReading" renamed SeekVoltageResponse.
- **The attributes:** Myomers already published all 4 Seek* (@0x320-0x330). Emitter's AUTHENTIC
table was recovered (binary @0x511dd4, ids 0x1D-0x25) and published (emitter.cpp):
LaserOn@0x418, LaserScale@0x42C, LaserRotation@0x41C, Current/Recommended/Min/Max
SeekVoltageIndex@0x3F0-0x3FC, SeekVoltage@0x400, **OutputVoltage@0x414 = currentLevel (RAW
volts — the cursor feed)**. ⚠ The MechWeapon 0x1D "OutputVoltage" PORT ALIAS (→ rechargeLevel
0..1) was RETIRED: the binary MechWeapon table ends at 0x1C, and `AttributeIndexSet::Find`
walks lowest-id-first, so the alias SHADOWED the authentic Emitter row. Emitter renames:
@0x3F8 minSeekVoltageIndex / @0x3FC maxSeekVoltageIndex (were seekStepCounter/seekVoltageCount
guesses; the attr table proves the identities). PPC chains Emitter::GetAttributeIndex().
- **Verified live (BLH, autofire):** page-cycling + BT_PRESET_HOLD steady-state shots — both #10
repro pairs ghost-free (SYSTEM 09 Streak → SYSTEM 10 PPC held clean; SYSTEM 02 → SYSTEM 04
clean; SYSTEM 05 → 06 Myomers clean); curves draw with moving cursors (charge cycling);
exactly ONE replot per activation ([seek] log). Diags: `BT_SEEK_LOG` ([seek]
BecameActive/replot), `BT_PRESET_HOLD=<n>` (freeze the #9 preset cycler after n pulses —
steady-state pixel verification; the 120-frame cycle phase-locks with BT_SHOT's 90).
## 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 TORSO-TWIST tape (NOT a heading tape — stale wording swept task #58; see
the tape entry below), 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 01200 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]).
**VERDICT (Gitea #4, 2026-07-20): the "range slides in/out crazily while walking" report is
AUTHENTIC behavior, not a bug [T2 measured].** Per-frame `BT_RANGE_LOG` traces (mech4.cpp, with
an independent Möller-Trumbore cross-check `BTGroundRayHitExact` in btvisgnd.cpp) on scripted
walks: ARENA1 garage cluster — every frame a real structure-face pick, true range legitimately
hopping 40↔600 m at silhouette edges, the caret in motion 74% of frames / ~24 direction
reversals per 10 s; CAVERN butte field — 806 instant >50 m jumps, 776 butte↔1200-default
boundary crossings, 106/139 s windows swinging >300 m (max 1143 m), caret reversing ~54×/10 s.
Every value tracks real geometry; the 500 m/s slide (authentic) then never rests. Elevation was
NOT the driver (arena floor is flat y=0) — depth discontinuities are. Cyd's "ray falls through
geometry" was checked: 0 fall-through frames on arena, 6/8400 (0.07%, single-frame silhouette
grazes) on cavern. Bounded infidelities noted for follow-up, NOT the reported symptom: (a) butte
collision (stepped YCyl tiers + cone, buttee_c.sld) is NARROWER than the sculpted buttee.bgf
rock — measured a 0.27 m graze past the r=14.6 tier while visibly inside rock (terrain-march
tier backstops it, error a few meters at point-blank); (b) the cavern's `butteeu` upper butte
sections (buttee.bgf re-instanced at y=50, VideoModel-only, NO solid stream) are
pick-transparent — aiming steeply up at a rock tower's top half reads through it; (c) boot
transient: before the aim camera is valid, the max-range fallback designates from uninitialized
ray floats → `shown` overshoots (~3400) and slides down for the first ~7 s (cosmetically hidden
by the 1200 caret peg). Traces: scratchpad i4_*_walk*.log; issue #4 comment has the summary.
⚠ 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, botY3·tickMajor0.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 WeaponState**
@0x350 == 2) 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. **FIX 2026-07-18 [T2]:** the pip's "loaded" was a port
approximation reading `rechargeLevel ≥ 1` — correct for emitters (charge-driven) but
**statically 1.0 on projectile weapons in the PORT** (the port sim never called the slot-17
updater @004b9c9c — see the §Launcher-panel CORRECTION), so missile/AC pips were
**permanently lit and never blinked on fire**.
Now reads the AUTHENTIC attr 0x1C = the weaponAlarm StateIndicator level (`WeaponStatePtr`
`GaugeAlarm54::LevelPtr`), compared `== stateConst2` (the const the binary itself stores in
AddWeapon). The state cycles Loaded(2)↔Firing(0)/Loading(3)/Jammed(5) for BOTH families, so a
missile pip now momentarily drops when fired, exactly like the emitter's. Verified live
(Blackhawk, `BT_AUTOFIRE=1 BT_AF_MISSILE=1`): both SRM6 pips toggle 2↔0/5 on each salvo.
(The recharge DIAL is driven by slot 17 @004b9c9c — §Launcher-panel CORRECTION.)
- **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 SetMatrix-translates by `Reticle::reticlePosition` (screen 1..+1 [T0]) every
frame — but **NOT by torso twist** (task #58 CORRECTION): the VIEW is torso-mounted (the eye
hangs off jointtorso → jointeye → siteeyepoint), so the crosshair stays SCREEN-CENTERED through
a twist — screen center IS the boresight; the twist reads on the bottom tape/compass/radar
wedge instead. The reticlePosition writer is un-exported (one xref binary-wide: the read-side
lookup part_014.c:5132); its coherent use is the FIXED-torso free-aim channel (mech+0x36c
[T4]). The old "translates to the torso boresight" wording here was the falsified
body-mounted-view model — see [[combat-damage]] Targeting for the full re-correction.
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; the OFFSETS were re-based 2026-07-19 — the table @5110b8
starts with id 3 FlickerRate@0x1D8, so Rotation=0x1DC…CompassHeading=0x214 Scalar; hud.hpp/
CLASSMAP corrected, names/ids unchanged). 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.)
Player CALLSIGN labels (kill/damage feed, radar/target tags, score display) are 1bpp name
BITMAPS from the egg, not text — format, renderers, and the operator-console generator:
[[multiplayer]] §OPERATOR-SET CALLSIGNS.
## 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.