The interactive 2-node playtest wave -- every fix decomp-grounded and live-verified: COLLISION ECONOMY (the ram one-shot): StaticBounce mutates worldLinearVelocity per contact and ProcessCollisionList walks EVERY touched solid per frame; with 2007 terrain-as-solids the reflections compounded x4-x40 within one frame and a walking bump one-shot a pristine mech for 112,375 pts (62-pt authentic economy). Fix: frameEntryWorldVelocity restore per contact (damage always priced at the real approach speed -- all the binary's physics ever saw); Mech::Reset zeroes the mover motion (respawn = teleport); [collide-tx]/[mp-hdlr] telemetry. Gotcha #16 (engine-facility drift class). MISSILES: peer-visible salvos (the launcher record extension carries a salvo counter + aim point; ForceUpdate actually enqueues it -- the dirty flag alone never serialized), the authentic arc (authored MuzzleVelocity vector + the Seeker's 200m/0.1/300 loft + gain-4 steering, decoded from @004beae4/@004bef78), world-impact bursts (rounds detonate on cave geometry instead of phasing through), contact-only damage (flight-cap expiry = fizzle, no more teleport damage), live re-lead, and ballistic (unguided) shells for autocannons. projweap's stale BTPushProjectile extern (the /FORCE signature trap, gotcha #6 corollary) crashed the Avatar's first AFC100 shot -- fixed + sweep rule recorded. RADAR: two transcription bugs made the scope permanently empty -- FUN_0040b244 is the affine INVERSE (not a copy) and FUN_0040adec writes ONLY the 3x3 rotation (never the translation); worldToView now Invert(view) built rotation-first. CulturalIcons sorted out of the moving grid (the phantom red pips were map props), visible-radius culls on all three draw passes, live pip verified at |delta| x ppm px. Gotcha #17 (verify the FUN_ body, not its call shape). WEAPON PANELS (the frozen-dial hunt): the binary's *(subsystem+0x40) means FAILED -- the recon's 'operating' name was backwards, inverting the destroyed-X lamps, the panel look, the children enable and the ready-lamp gate (which had NEVER executed). Polarity chain corrected end-to-end (failedState, fed by real damage saturation). Root cause of the freezes: MFD page-mode gating -- the dev composite shows ALL pages at once, so off-page dials legitimately stopped; under BT_DEV_GAUGES the 15 page-plane bits stay active (the exclusive secondary trio untouched). The SEH gauge guard now names its kills; repaint-heal resets the incremental arc after panel repaints; [panel]/[arc] probes added. COMM/SCORE: MessageBoard LIVE (the engine already shipped the whole Player__StatusMessage queue; wired the binary's one producer -- the kill branch, victim's name, 6s -- plus the consumer bridge and a lazy source bind); MP DEATHS counted via the observed-death tally (each node scores every pilot from locally observed events, the same model as the KILLS credit) and the -2/-1 engine seed clamped for display. DEV UX: node-tagged window titles (-net port), gauge panel reworked (1320x480, true 4:3 MFD cells, the portrait secondary UNROTATED upright, linear filtering, BT_GAUGE_SCALE), fixed close spawns via BT_SPAWN_XZ, Boreas flies an Avatar (first second-chassis live outing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
15 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]
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]
The secondary screen's THREE views (Damage / Critical / Heat) — mode-gated [T0/T1]
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).
L4MechControlsMapper::SetControlMode @004d1ae4 is the SELECTOR — its mask table
{0x40000,0x80000,0x100000} clears bits 18-20 and sets one — despite the "control mode" name it
switches the secondary VIEW. ⚠ Desktop gap: the Keypress cases 0x13d/0x13e
(CycleControlMode/CycleDisplayMode) are the DOS Tesla extended F3/F4 codes and never fire under
the WinTesla VK map (VK_F3=0x72 collides with 'r', VK_F4=0x73 with 's' — the same collision
class as the documented 'p'/VK_F1 drop), so the desktop is PINNED on the Damage view. The
schematic shows the pilot's OWN mech only — there is no target-damage readout in the cockpit.
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.
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]
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 roleClassIndex == 0 (the ROOKIE-role lockout; task #12's
BTPlayerRoleLocksAdvanced bridge — the old "game-mode flag / likely off in a basic mission"
hedge is superseded: bring-up role = 2 = UNLOCKED, verified live: press → valveState 1→5 →
flow redistribution 1/6 → 5/10). Desktop: 'C' cycles the selected condenser (BT_VALVE_SLOT).
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 byreticlePosition= 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=1hides 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.