Files
BT412/context/gauges-hud.md
T
arcattackandClaude Fable 5 4e63a7b6c3 Heat: THE AUTHENTIC ELECTRICAL MODEL -- weapons recharge from generators (task #10)
Task #10 set out to fix "scrambled linked-sink routing"; a [heat-link] attach
log proved the routing was NEVER scrambled (every subsystem links its authored
condenser exactly; the "pools in Condenser1" read was the diagnostic-sampler
aliasing trap).  The real defect: emitter.cpp's local FUN_00417ab4 stub
returned NULL, so the whole electrical model was inert and the E7 force-charge
recharged every emitter in ONE frame (~0.3s cycle, 1501 fires/90s, ~1.7e9
heat/s -- the "runaway").

Landed authentically [T1: disasm + byte-verified constants]:
- Emitter ctor @004bb120: seekVoltage = authored fraction x generator
  ratedVoltage (10000); EC = energyTotal/(seekV^2 x 0.5); voltageScale@0x310 =
  (RechargeRate / -ln(1 - 1e-4 x seekV)) / EC -- charge reaches seekV[rec] in
  EXACTLY the authored RechargeRate (PPC 5s, ERL 4s, SRM 3s, ERM 2s) cold.
  Owner-flags ctor gate (the usual gotcha; the this-flags read never armed).
- PoweredSubsystem::ChargeTimeScale (@004b0d50, was a =1.0 stub): voltageScale
  x (1 + thermalResistivity x srcTempRise) -- hot generators charge slower.
  ("voltageScale is never read back" was wrong; corrected + swept.)
- TrackSeekVoltage @004ba838: charging I^2R -> the GENERATOR's pendingHeat
  (~3.5e8/full PPC charge) -- generators self-heat, conduct to their authored
  condensers, and throttle further charging.  The feedback economy closes.
- FailureHeat consumers found: this+0x184 == 2 gates BOTH weapon families
  (@004baa88 emitter: reset firing + hold charge 0; @004bbd36 ballistic:
  recoil=rechargeRate + alarm 7).  Emitter::GetFaultState un-stubbed.
- ProjectileWeaponSimulation @004bbd04 opens with call 0x4b0bd0 (disasm) --
  launchers now run the powered/heat step (their firing heat previously
  accumulated in pendingHeat forever).
- heat.cpp: per-instance BT_HEAT_LOG census (the old shared-static 1-Hz
  sampler aliased); [heat-link] attach log; the stolen-else Verify restored.

Verified live (120s max-rate autofire): PPC ~470-500 (was 55,000), bank
plateaus ~600 and sheds to ambient, generators 1100-1400, ERMLaser
self-regulates at the authored 2000 failure threshold (shutdown-cool-resume).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 16:59:29 -05:00

12 KiB
Raw Blame History

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
reconstruction-gotchas
decomp-reference
subsystems
pod-hardware
gauge
methodDescription
attribute-pointer
GaugeRenderer
MFD
PCC
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 <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]

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]

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 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]). ⚠ 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; 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