The pre-launch host drop/rejoin re-stream re-creates every mech at mission launch; the LOBBY-built gauge tree kept attribute pointers into the freed first-stream mechs, every widget Execute AV'd, and the BT_DEV_GAUGES SEH guard (Gauge::GuardedExecute, GAUGE.cpp:618) disabled each one PERMANENTLY (rate=0, no rebind) -- the frozen dev-gauges window of incident #12. The authentic engine flow only tears the tree down at mission transitions (Application::Shutdown -> gaugeRenderer->Shutdown() -> ShutdownImplementation -> Remove(0), APP.cpp:787 / GAUGREND.cpp:3264); the re-stream bypassed it. Fix: BTL4GaugeRenderer::TearDownForViewpointRestream() performs the ENTITY-BOUND half of that same ShutdownImplementation sequence, in order (gaugeAlarmManager->RemoveAllAlarms() -> Remove(0) -> moving/static entity grid Clear), keeping the mission-scoped state (warehouse, graphics ports, interpreter, controls-owned L4Lamps -- RemoveAllLamps would delete them behind the buttonGroup's &lamp->automaticValue registrations). Called from BTL4Application::MakeViewpointEntity before ConfigureForModel("Init") on any viewpoint RE-make -- that handler runs once per stream (the incident log's [ctrlmap] installing x2) -- so the tree rebuilds bound to the NEW mech via the renderer's existing lazy build. Sentinel: "[gauge] viewpoint re-stream: tearing the gauge tree down for rebuild". Verified with the 2-instance relay harness (port 15600, MP_RELAY.EGG, the incident sequence): join A (dev gauges) + B, kill B by exact PID at WAITING FOR OPERATOR LAUNCH, rejoin B, operator launch. Pod A re-streamed (2x ctrlmap install, 4x zonebuild), logged the sentinel, and post-launch the docked composite is fully live (mission clock/heading/radar advancing between BT_SHOT frames) with ZERO [gauge-fault] DISABLED lines (the incident had 11). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
34 KiB
id, title, status, source_sections, related_topics, key_terms, open_questions
| id | title | status | source_sections | related_topics | key_terms | open_questions | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| gauges-hud | Cockpit Gauges / MFD HUD system | established | docs/GAUGE_COMPOSITE.md (full map); CLAUDE.md §8 |
|
|
|
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)
- The config
content/GAUGE/l4gauge.cfg— a text file the engineGaugeInterpreterparses (BuildConfigurationFile → Initialize → GetProcedureBody → ParsePrimitive). Each mech's gauge tree is built from the labelGetGameModel()+"Init"(e.g.bhk1Init), which invokes shared blocks (MechInit,Secondary1) thatconfigurethe ports and call gauge primitives. [T1] - Widgets — each
keyword(...)in the config resolves to aMethodDescriptioninBTL4MethodDescription[](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] - 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 throughParseAttribute → FindSubsystem (stricmp GetName()) → GetAttributePointer. A class PUBLISHES an attribute via a<Name>AttributeIDenum +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::Executeaborts. /FORCEtrap — a prose-only slot AVs on first call.- Databinding trap — never raw-read owner offsets; use a bridge (
BTGetSubsystemAuxScreen, …). - ReconStream no-op — use
DEBUG_STREAMfor logs, notDebugStream. - 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-pointers 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::Findwalks 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 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 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 readingrechargeLevel ≥ 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 byReticle::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=1hides 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.