Files
BT411/context/pod-hardware.md
T
CydandClaude Opus 5 1debbeb240 Mech::DuckRequest -- the CROUCH button, and a dead-button census that lied
The 2026-07-20 audit listed 8 panel buttons dispatching a streamed message with
no reconstructed handler.  Only ONE was still missing: generator on/off,
ToggleSeekVoltage, EjectAmmo, ToggleCooling and BalanceCoolant had all landed
between 07-20 and 07-25 while pod-hardware.md and open-questions.md still called
them dead.  docs/INPUT_PATH_AUDIT.md had already flagged the census as "stale in
both directions" and was right -- swept both files, and recorded the rule: check
the code, not the census.

DuckRequest @0049fa00 (id 0x1a, RIO 0x13 -- the manual gives crouch a whole
section).  The binary's entire body:

    if (0 < *(int *)(param_2 + 0xc)) { *(undefined4 *)(param_1 + 0x398) = 1; }

Press-only; sets duckState (mech+0x398, attribute 0x37).  A one-shot REQUEST
flag, not a posture toggle -- the handler never clears it and the only other
writer in the whole binary is the mech reset (part_012.c:9439, the same reset
that zeroes incomingLock, which is how that region was already mapped).

WHY THE FLAG HAS NO READER, AND WHY THAT IS CORRECT.  duckState has ZERO readers
anywhere in the decomp, because it is published as an ATTRIBUTE and consumed
through DATABINDING: content/GAUGE/L4GAUGE.CFG runs a 3-frame bduck.pcc
oneOfSeveralPixInt bound to DuckState -- "crouch mode: button 4" on the map's
legend column.  Verified live by capturing the Secondary/Radar window before and
after a 0x13 press: the crouch icon goes grey -> orange.  So the handler is
COMPLETE.  A crouch pose invented here would be a stand-in for data we have not
found (no SQUAT clip name survives in the decomp or in content/, only
DuckServo01.wav in AUDIO1.RES).

Bonus: those gauge widgets sit at offsets 537/430/322/215/108 -- a 107 pitch,
independently corroborating the map legend grid measured from pixels.

Still open from that census: MechRIOMapper's own Keypress @004d2514 (id 0x19).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 02:23:58 -05:00

19 KiB
Raw Blame History

id, title, status, source_sections, related_topics, key_terms
id title status source_sections related_topics key_terms
pod-hardware Pod Hardware — the fixed target (7 monitors, D3D9, RIO) established PROGRESS_LOG.md §3; the PLATFORM PROFILE + GAUGE DEV-COMPOSITE notes
gauges-hud
rendering
project-overview
pod
RIO
MFD
IG-board
SVGA16

Pod Hardware (the fixed target)

The port must run on (a) a dev box and (b) the fixed arcade pod. The pod is a hard constraint that bounds the graphics API. Full detail: docs/PROGRESS_LOG.md §3.

Displays

  • 2 video cards → 7 monitors: main 3D view 800×600; radar 640×480; five monochrome MFDs driven as one 1280×480 horizontally-spanned surface. [T1]
  • Requires old NVIDIA drivers for the MFD horizontal spanning — a hard constraint that bounds the graphics API → Target Direct3D 9 (lowest common denominator that runs on old-driver pods AND modern cards; one codepath). NOT Vulkan/D3D11+. CUDA is irrelevant (compute, not display). [T1]

Cockpit I/O (RIO)

Joystick X/Y, throttle, pedals, buttons over serial COM (L4RIO, L4SERIAL). Must be remapped to keyboard/gamepad on a dev box; real wiring is a pod bring-up task (Phase 8). The engine is a PUSH model — LBE4ControlsManager groups are fed by all devices (RIO on the pod, DirectInput on dev); the MechControlsMapper interprets them (locomotion). [T2]

The button space + lamps [T0, L4CTRL.h enum]

buttonGroup addresses 0x00-0x47: AuxLowerRight/Left 1-8 (0x00-0x0F), Secondary1-12 (0x10-0x1B), AuxUpperCenter/Left/Right 1-8 (0x20-0x37; 0x30-0x37 = the target HOTBOX, pilot select), icom/door 0x39-0x3C, Panic 0x3D (the config-mode lamp), Throttle1 0x3F (throttle-head = REVERSE THRUST), joystick cluster 0x40-0x47 (Main trigger 0x40, hat 0x41-0x44, Pinky/ThumbLow/ThumbHigh 0x45-0x47 — the four MAPPABLE fire buttons). "Lamp buttons" are literal: physical illuminated pushbuttons in panels around the screens; the RIO LampRequest protocol lights them (MakeLinkedLamp), reports burned-out bulbs (RIODeadLamps failure page, per-lamp names). A lit button = active. Keypads are NOT buttons: keyboardGroup[KeyboardPilot/ KeyboardExternal] carry key VALUES ('0'-'9','A'-'F', L4CTRL.cpp:2526). RIO event convention: press = buttonGroup[a].Update(a+1, modeMask) (mask saved), release = Update(-a-1, savedMask) (L4CTRL.cpp:2470-2520). Bank→MFD assignment (Gitea #9, from the streamed "L4" .CTL EventMapping dump [T1]): each preset-able MFD owns the 8-button bank AROUND it, mode-mask-gated per page — Mfd1 (lower left) = 0x08-0x0F (AuxLowerLeft), Mfd2 (upper center) = 0x20-0x27 (AuxUpperCenter), Mfd3 (lower right) = 0x00-0x07 (AuxLowerRight): Quad page → the outer buttons direct-select the populated Eng pages; Eng page → one button back to Quad, the rest drive the shown subsystem (generator select A-D msg 4-7, gen mode msg 8, weapon configure msg 9; also msg 0x3 = ToggleCooling — WIRED 2026-07-20: the button→msg-3 route was always authored (BT_CTRLMAP_LOG shows ~a dozen EVENT msg 0x3 entries, e.g. Eng-page aux elem 0x21 mask-gated per ModeMFD page to each weapon), but was "silently dead" only for lack of the id-3 handler. Reconstructed HeatSink::ToggleCoolingMessageHandler (@004ad6f8) + restored the PoweredSubsystem→HeatSink handler chain, so the Eng-page "Coolant" button now toggles the shown weapon's coolant flow. msg 0xb = ToggleSeekVoltage (energy) / EjectAmmo (ammo weapons) still unreconstructed). Always-active records IDENTIFIED 2026-07-20 [T1] (roster: sub 3 = Reservoir, 4-9 = Condenser1-6, 10-13 = GeneratorA-D): 0x2C = the COOLANT FLUSH button (Reservoir InjectCoolant, hold-to-flush — works), 0x2F/0x2E/0x2D/0x2B/0x2A/0x29 = the per-condenser VALVE buttons (MoveValve, Cond1-6 — work), 0x1A-0x1D = Generator A-D ON/OFF (ToggleGeneratorOnOff id 4, binary table @0050fb90 fn @004b1ed0 — WIRED, powersub.cpp). Newly decoded from the binary message tables: 0x13 → Mech DuckRequest (0x1a @0049fa00 — the manual's CROUCH button) WIRED 2026-07-26, the last handler in this census; press-only, sets duckState and the map legend's bduck.pcc widget lights (see open-questions for why it has no code consumer) —, 0x28 → Mech BalanceCoolant (0x16 @0049f728) WIRED 2026-07-21 (#20), 0x12 → ThermalSight ToggleLamp (id 3, table @0x51120C fn @004b860c) and 0x14 → Searchlight + Searchlight2 ToggleLamp (id 3, table @0x51117C fn @004b838c) BOTH WIRED 2026-07-25 (#61), previously default-constructed blackholes; verified live (0x14 → lightState 0→1, 0x12 → thermalActive 0→1). ⚠ The fn addresses were swapped in this file before #61 (@004b860c is ThermalSight's) — see decomp-reference for the table evidence — and that swap had propagated a false "1995 latent bug" (retracted, subsystems WAVE 4). An unhandled message is SILENTLY ignored (Receiver::Receive finds no handler and does nothing — dead buttons produce NO log). 0x10/0x11 = map ZoomIn/Out, 0x15 = CycleDisplayMode, 0x18 = CycleControlMode (#6). Buttons with NO streamed mapping authored (authentically inert): 0x16/0x17/0x19/0x1E/0x1F/0x38-0x3E. Full decode: gauges-hud §preset pages; the complete 72-button census + verification: docs/GLASS_COCKPIT.md §2026-07-20.

Desktop input remap — CONTROLS.MAP + XInput (2026-07-18) [T2 live]

game/reconstructed/btinput.cpp + content/CONTROLS.MAP (WASD-classic default, compiled-in twin; content/CONTROLS_NUMPAD.MAP = the corrected community numpad profile). Grammar: key <name> button <addr>|axis <axis> deflect|rate <n>|keypad pilot|external <0-15>|pckey <char>|action <name>; pad <button> same targets; padaxis <src> axis ... [invert] [deadzone d] [rate n]. Axes: Throttle (lever, rate-walks + sticks), LeftPedal/RightPedal/Pedals (turn, springs), JoystickX (torso twist), JoystickY (torso ELEVATION — pitch aim; every 1995 control mode routes it to Torso::SetAnalogElevationAxis; keys R/F + pad LeftStickY in the default map; verified to the authentic VerticalLimitTop clamp, 20° on the Blackhawk; aims the GUNS — reads on the HUD elevation tape, the eyepoint stays level). AXIS SIGN CONVENTION (2026-07-18, user-verified) [T1]: the sim uses MATH convention — positive turnDemand / twist / free-aim slew is CCW (LEFT). On the pod the RIO Ranger owned the hardware sign; the desktop key bridge negates ONCE per channel (key_turn = -gBTDrive.turn; stickPosition.x = -gBTTwistAxis; the SetFreeAimSlew call-sites pass the bridge value un-negated) so input-right = turn/twist RIGHT (manual p8: "pulling your joystick to the right torso twists your 'Mech to the right"). Forced/BT_GOTO harness demands are already sim-frame and stay un-negated. Lesson: large-rotation screenshot comparisons are AMBIGUOUS — trust tracked landmarks, numeric yaw telemetry with an established convention, or the user's live observation. Emissions mirror the RIO conventions above exactly; the four fire buttons (0x40/45/46/47) publish as levels to the bring-up fire channels instead of buttonGroup (avoids double-fire once the streamed ChooseButton mappings are exercised). XInput loads dynamically (xinput1_4 → 9_1_0; disconnected-pad probing rate-limited to 1 Hz). Keys claimed by a binding are SUPPRESSED from the legacy WM_CHAR/KEYUP feed (BTInputSuppressKey, hook at L4CTRL.cpp keyboard read) — this ended the historic DOUBLE-DISPATCH: 'w' drove AND selected pilot 0; F5's key-up value 0x74 aliased to the 't' hotkey; letter key-ups (uppercase) fed the developer fake-event dispatcher. Unbound keys keep their authentic 1995 dispatcher meaning (reachable from any key via pckey). The old dual-use 'V' (view toggle + look-behind) is split: V = ViewToggle, B = LookBehind. Verified live: bindings load, W/NumPad8 drive (spd rises), X all-stop, aux-button emission ([input] 0x2f PRESS), suppression both directions ('r' delivered / 'w' swallowed), full-keyspace WM_CHAR+WM_KEYUP fuzz survived. Diag: BT_INPUT_LOG.

The pod throttle is ANALOG-CONTINUOUS, not notched (verified end-to-end) [T1]

The authentic pod throttle path — traced 2026-07 (task #50 throttle-fidelity question):

  1. Hardware → RIO: serial AnalogReply packet → Ranger("Throttle", 0, 800, .05) — raw counts 0800, 5% deadband, auto-ranging offset, output a CONTINUOUS Scalar; the sign is inverted ("Throttle counts BACKWARDS", engine/MUNGA_L4/L4RIO.cpp:1374-1377, Ranger @L4RIO.cpp:461-701). No quantization/notching anywhere in Ranger::Update.
  2. Manager: LBE4ControlsManager::ExecutescalarGroup[ScalarThrottle].Update(&rioPointer-> Throttle, mode_mask) on every AnalogReply (L4CTRL.cpp:1379-1382). ScalarThrottle = index 0 → manager+0x24 (scalarGroup base 0x24, 0x20/entry; buttonGroup base 0x1c0, keyboardGroup 0x160).
  3. Streamed .CTL mapping (the "handled elsewhere"): MechRIOMapper's ctor @004d266c binds NO throttle — the bind comes from the type-19 ControlMappingStream resource named "L4" (child of the per-mech type-6 ControlMappingsList; installed by BTL4APP MakeViewpointEntity via CreateStreamedMappings @0047703c). BTL4.RES "L4" record [1]: Scalar Throttle → subsystemID 0 (ControlsMapper slot), DirectMapping, attr 4, mask 0xffffffff. Attr 4 = "ThrottlePosition" @ mapper+0x11c (binary IndexEntry table @0050efd8: id 4 → offset 0x11d-1 = 0x11c, name "ThrottlePosition" @0050f28f). Reverse = record [2]: Button Throttle1 (0x3F, on the throttle handle) → attr 6 ReverseThrust@0x124. Turn = pedals value-bound in the ctor (manager+0x44/+0x64 → mapper+0x1b4/+0x1b8). Step-2a fix (2026-07-17) [T2]: these ids are POSITIONAL (Find(id) = index[id-1]) and our MechControlsMapper chain started one low (Subsystem::NextAttributeID==2 vs the binary's 3) — every streamed record landed one member late (a real-pod landmine: the RIO throttle scalar drove pedalsPosition). Ids are now PINNED to the binary numbering with an id-2 pad + static_assert locks (mechmppr.hpp/.cpp); audit tool BT_CTRLMAP_LOG=1. ⚠ The streamed BUTTON mappings (like record [2]) are NOT CONSUMED yet — the SCALAR records drive their attributes, but MechControlsMapper::AddOrErase (both overloads, @004b02b0/ @004b02d4) is still the unreconstructed Fail("Unhandled mapping!") stub, so a streamed button never reaches its attribute. Consequence found the hard way (user report 2026-07-24, "i cant seem to reverse"): REVERSE THRUST was dead on the desktop. LALT→0x3F was bound and PadRIO pushed the RIO ButtonPressed event correctly, but nothing applied it to attr 6, and the only writer of reverseThrust was the keyboard bridge's key_throttle < 0 test — which is (a) bypassed whenever a RIO is operational (the pad RIO always is, so the bridge is OFF on the desktop) and (b) unreachable anyway, since L4PADRIO clamps the Throttle channel to [0,1] (low = 0.0f). Fixed by publishing the 0x3F hold state from PadRIO::EmitButton (the single chokepoint keyboard/pad/joystick/glass clicks all share) and applying it in InterpretControls, gated on BTPadRIOActive() so pod hardware is untouched — marked [T3], delete when streamed button mappings land. Verified: hold LALT ⇒ rev=1, speedDemand 61.5; release ⇒ forward. Reverse is a HOLD, per the manual. [T2] Details: docs/GLASS_COCKPIT.md §2a; see glass-cockpit.
  4. Interpretation: L4MechControlsMapper::InterpretControls @004d196c applies the ONLY software detent — snap to 1.0 when |t1.0| ≤ 0.05 — then MechControlsMapper::InterpretControls @004afd10 computes speedDemand@0x128 = maxSpeed(mech+0x34c) × throttlePosition(0x11c) × scale(mech+0x5c0) (forward) or maxSpeed × throttlePosition (reverse flag 0x124).

Consequence: the original pod produced a continuously varying speedDemand while the lever moved (updated per serial AnalogReply, NOT per render frame). There are NO "5 throttle notches" anywhere in the software path. (Keyboard keys '1'-'5' in @004d1bf0 set controls-manager MODE masks; the '+'/'-' pair steps a [0,5] value that drives pow(2,x) into mech+0x404 = HUD zoom 1×–32× — neither is a speed setting.) The mech's throttleState@0x4a4 writer remains un-exported (likely in the 0x4a9b5a0x4ab188 gap) — open-questions.

MECHANICAL-notch hypothesis (2026-07-16) [T4, hardware]: the "5 speeds" pod lore may still be true — as detents in the throttle QUADRANT hardware, invisible to the software (the pot reads continuous counts regardless of where the lever mechanically rests). Independent corroboration from the gait math (locomotion): the gait SM has NO stable state for a demand between the walk cap (walkStrideLength@0x534) and the run engage speed (reverseSpeedMax@0x538, the walk→run transition clip's exit speed) — a demand PARKED in that band hunts walk→shift-up→shift-down forever, firing the authored EngineShiftFwd/Rev sounds each swing (binary-verified, all-authentic data: Blackhawk band = demand 22.0230.87 = throttle 3650%). A design like that only ships if the hardware discourages parking in the band. Port accommodation: the keyboard lever snaps out of the dead band AT REST (mech4.cpp "GAIT DETENT"; sweeping through while held stays continuous = authentic moving lever). Ask Nick: did the pod throttle quadrant have mechanical detents (how many / positions)? — open-questions.

Multi-surface gauge path (intact, pod-only by default)

The pod multi-surface path EXISTS and is intact: DPLRenderer::FindBestAdapterIndices (multi- adapter selector, honors PRIMGAUGE/SECGAUGE/MFDGAUGE/SPANDISABLE), SVGA16::BuildWindows (a fullscreen D3D device + window per gauge/MFD surface, MFD span = width×2), L4GaugeRenderer (gated on L4GAUGE). content/SETENV.BAT is the authentic pod env preset (L4CONTROLS=RIO,KEYBOARD; L4GAUGE=640x480x16; L4PLASMA=com2). [T2]

Platform profile switch

-platform pod|dev (or BT_PLATFORM env; default DEV) selects a runtime env preset WITHOUT forking the codepath. DEV = keyboard + single 800×600 window. POD = RIO input (keyboard fallback off serial) + multi-surface gauges (from SETENV.BAT/L4GAUGE on real hardware). -platform pod does NOT auto-enable L4GAUGE (each surface needs its own fullscreen device on the pod's 2 cards). Off-pod, the 6 MFD surfaces render in a dev window via BT_DEV_GAUGES (gauges-hud). [T2]

MFD surface model

All cockpit surfaces are bit-plane MASKS over ONE shared SVGA16 pixelBuffer: sec=palette low byte; Heat=0x4000, Mfd2=0x0400, Comm=0x8000, Mfd1=0x0100, Mfd3=0x1000; Eng1-3 = engineering-mode alt planes; overlay=0x00C0 (shares the sec surface). See gauges-hud. [T2]

The 1995 player manual — alignment audit (2026-07-18) [T1, primary source]

reference/manual/Tesla40_BT_manual.pdf (34pp, from Nick). CONFIRMS the reconstruction on every checked control behavior:

  • Control modes are named BAS / MID / ADV (basic/middle/advanced; our Basic/Standard/Veteran names came from the RP analog — mechanics identical): BAS = joystick turns the mech, pedals inactive, NO torso twist; MID = pedals turn + auto-slow for tightest radius, joystick = torso; ADV = as MID but NO auto-slow (wider radius at speed) — exactly our mapper's turn-clamp difference. Mode button = top right of the secondary screen.
  • Stick right = torso right in MID/ADV (p8) — the corrected sign. Blackhawk and Owens are the fixed-torso exceptions (named in print!); twist arc "about 120°" (per-mech: Loki/Thor tables say Torso Limit 110°, Torso speed 60°/s).
  • Throttle is a continuous lever (push=accelerate, pull=stop; inertia); reverse = HOLD the red throttle button (release = forward) — our 0x3F hold-state model.
  • Hot Box Selector MFD = callsign buttons + a CLOSEST button (lower right = our hotbox button 8 → ChooseNearestPilot); radar = travel-oriented, 60° vision wedge sweeps with twist, red hot box + callsign on contacts; map zoom ± buttons flank the secondary screen.
  • Experience gates the hunting aids: viewscreen hot box + hunting-guide arrow appear in STANDARD sim mode only (not veteran/expert); expert mode adds movement heat. (2026-07-18 [T1]: 4.0→4.10 drift — in OUR 4.10 binary the HUD reads NO experience flag (hotbox/arrows are lock-gated in every mode) and movement heat is veteran+expert; full wiring in experience-levels.)
  • Cockpit: 5 MFDs (Coolant UL, Engineering top, Hot Box UR, Weapon ×2) + secondary screen with side button columns + EJECT button beside the joystick + per-mech COOLANT LOOP tables (weapons+generators per loop 1-6) and stat sheets (tonnage/armor/reservoir liters/heat sinks/top speeds incl. "Super Charged"). NEW LEADS (manual describes, port lacks input/UI): CROUCH (button by the secondary screen; Mech::duckState attr 0x37 + SQUAT clips exist, nothing drives them), EJECT (sounds exist: EjectButton01_z*.wav), hot-box viewscreen framing (the deferred PNAME marker chain). Per-mech stat tables = a systematic cross-check source for our streamed subsystem resources.

Coolant-loop cross-check RESULT (2026-07-18) [T1] -- STRUCTURE strongly faithful

Dumped each mech's subsystem->loop (subsystem's linked-condenser number, BTCoolingLoopFrame) and diffed vs the manual's COOLANT LOOPS tables. The 4.0->4.10 drift shows here too, but the STRUCTURE is remarkably intact:

  • Backbone identical on ALL 6 mechs: Generator A/B/C/D on loops 1/2/3/5, Sensors (our "Avionics") on loop 2, Myomers on loop 5, LRMs on loops 1&3, autocannon on loop 4, the large energy weapon on loop 6 -- match the manual exactly.
  • Weapon LOADOUT identical on 5 of 6 (Thor, Vulture, MadCat, Owens, Blackhawk carry the exact same weapon set 4.0->4.10). Loki is the one full rework (4.0 PPC x2 / AFC100 / SRM6 / Medium Laser -> 4.10 AFC50 x2 / ER Medium x2 / SRM4).
  • The consistent 4.0->4.10 change: the small energy weapons were REDISTRIBUTED across loops -- the 2 ER Small Lasers moved off the sensor/heavy loops (4.0) onto the energy-weapon loops 4&6 (4.10) on Thor/MadCat/Vulture; SRM/laser placement reshuffled on Blackhawk. Owens is a near-PERFECT match (loops 1&3 exact; only a 4.0 "second myomer set" on loop 2 is absent).
  • Loop 0 = correctly UNCOOLED infrastructure (condensers, reservoir, gyro, torso, HUD, searchlight, thermal sight, ammo bins, message manager, mechtech, controls mapper) -- not a loop. CONCLUSION: the coolant-loop reconstruction is faithful; every difference is 4.0->4.10 tuning (loadout rework on Loki, small-laser redistribution elsewhere), NOT a bug. BT_SPEC_LOG (mech4) re-dumps on demand.

Key Relationships