48 KiB
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 |
|
|
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) — ✅ COMPLETE 2026-08-06: handler (07-26) +
the master-perf posture CONSUMER + squat/rise clips + MP replication (locomotion §CROUCH;
glass key F4); the map legend's bduck.pcc widget still lights. (The old "no code consumer"
verdict was export-gap blindness — reconstruction-gotchas §20) —, 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):
- Hardware → RIO: serial
AnalogReplypacket →Ranger("Throttle", 0, 800, .05)— raw counts 0–800, 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 inRanger::Update. - Manager:
LBE4ControlsManager::Execute→scalarGroup[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). - Streamed
.CTLmapping (the "handled elsewhere"):MechRIOMapper's ctor @004d266c binds NO throttle — the bind comes from the type-19ControlMappingStreamresource named "L4" (child of the per-mech type-6ControlMappingsList; installed byBTL4APP MakeViewpointEntityviaCreateStreamedMappings@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 ourMechControlsMapperchain 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 drovepedalsPosition). Ids are now PINNED to the binary numbering with an id-2 pad +static_assertlocks (mechmppr.hpp/.cpp); audit toolBT_CTRLMAP_LOG=1. ⚠ The streamed BUTTON mappings (like record [2]) are NOT CONSUMED yet — the SCALAR records drive their attributes, butMechControlsMapper::AddOrErase(both overloads, @004b02b0/ @004b02d4) is still the unreconstructedFail("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 ofreverseThrustwas the keyboard bridge'skey_throttle < 0test — 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, sinceL4PADRIOclamps the Throttle channel to [0,1] (low = 0.0f). Fixed by publishing the 0x3F hold state fromPadRIO::EmitButton(the single chokepoint keyboard/pad/joystick/glass clicks all share) and applying it inInterpretControls, gated onBTPadRIOActive()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. - Interpretation:
L4MechControlsMapper::InterpretControls@004d196c applies the ONLY software detent — snap to 1.0 when |t−1.0| ≤ 0.05 — thenMechControlsMapper::InterpretControls@004afd10 computesspeedDemand@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
0x4a9b5a–0x4ab188 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.02–30.87 = throttle 36–50%). 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 RGB SPLIT — how ONE VGA port drives THREE mono MFDs (decoded 2026-08-06) [T0 engine source + T1 authentic pod config]
The five monochrome MFDs are NOT five video outputs. Each VGA port's R, G and B analog lines are split to three separate monochrome monitors, and the software puts a different MFD in each colour channel of one shared palettized framebuffer. Mechanism, end to end:
- Every surface is a bit-plane + a CHANNEL.
content/GAUGE/L4GAUGE.CFG(the authentic 1996 pod config) configures each port asconfigure(idx, port, rotation, bitMask, clut, COLOUR, palette):port panel mask clut channel Commupper right 0x8000 clut2 red Mfd2(Engineering)upper centre 0x0400 clut2 green Heatupper left 0x4000 clut2 blue Mfd1lower left 0x0100 clut1 red Mfd3lower right 0x1000 clut1 green sec(+overlay0x00C0)secondary/radar 0x003F clut0 rgb (full colour, rotation 270 — the physically ROTATED portrait CRT) Eng1/2/3are the engineering-page twins of Mfd1/2/3: same monitor, second bit-plane, switchedby reconfigure(...)giving one plane the channel and the otherblank. - The channel assignment is literally a palette write.
L4GraphicsPort::BuildSecondaryColor(L4VB16.cpp) walks the palette entries owned by the port's bit group (BitWrangler(byteMask,8)) and writes ONE component:RedChannel -> triplet->Red,GreenChannel -> ->Green,BlueChannel -> ->Blue,AllChannels -> the whole triplet.BlankColorblanks the group (BlankPalette()), which is how a page swap silences the plane it replaces. The*TransparentZerovariants skip colour 0 so zero reads as transparent for that group. - So the DAC output carries three independent pictures, one per analog line, and the splitter hands each line to its own mono monitor. Three MFDs per VGA port; the pod's two MFD ports are the 1280x480 "horizontally spanned" surface (2 x 640x480 halves) the Displays section describes — clut2 = the upper row (Comm/Mfd2/Heat), clut1 = the lower row (Mfd1/Mfd3, blue spare). The radar rides its own port in real colour.
Why this matters for the port [T2]: our modern path renders each surface as its own mono-tinted window on its own Windows display (see §MFD PANELS ON REAL HARDWARE), which is right when every panel has its own output. On splitter-wired glass it is wrong — three monitors would share one Windows display and each would show only its channel's share of a single tinted image.
BT_POD_RGB=1 — the CHANNEL-COMPOSITE mode (built + field-verified 2026-08-06) [T2]. It
collapses the six panel windows into three: two VGA-port windows plus the radar. Each port
window composites its group's planes into ONE 640×480 BGRA image, each member ORed into its own
channel mask (0x00FF0000 / 0x0000FF00 / 0x000000FF), so the analog splitter downstream hands
each mono CRT exactly its picture. Grouping follows L4GAUGE.CFG: VGA Port A = Heat + Comm +
Mfd2, VGA Port B = Mfd1 + Mfd3; the radar keeps real colour on its own port.
SVGA16::ExpandPlaneToBGRA does the per-plane extraction, and BT_POD_RGB implies
BT_POD_SURFACES. Two companion gates came out of the bring-up: BT_POD_CHANMAP=<Port>=<colour>,…
remaps a surface's colour line when a cab's harness disagrees with the 1995 config (it did — see
§ALPHA-MR), and BT_POD_IDENT / BT_POD_CHANTEST paint identifying/cycling test patterns
so you can tell which physical CRT is which without trusting the desktop layout.
SETTLED: Nick's cart IS splitter-wired — its three 640×480 "displays" are VGA outputs, each
feeding a three-monitor splitter, and the composite is what lit the glass correctly.
MFD PANELS ON REAL HARDWARE — the bring-up path (2026-08-06) [T2 local / T4 on-pod]
Nick's crash cart (pod hardware + Chrome Remote Desktop on a burner account) is the first chance
to drive the real panels. The 1995 display path is NOT the way in. That rig spanned the five
MFDs as ONE 1280×480 surface via NVIDIA Horizontal Span, which every driver after XP dropped;
SVGA16::BuildWindows also wants an exclusive-fullscreen D3D device per adapter, which is
fragile on modern drivers and over a remote session. Both are still in-tree and still the
authentic reference — they are just not the bring-up route.
The route is the glass per-display windows (glass-cockpit): one window per surface, which maps 1:1 onto one physical panel per MFD, needs no special driver, and is already field-proven on desktops. Two pieces were added for the cab:
BT_POD_SURFACES=1(pod surface mode, L4GLASSWIN) — crops every window to its SURFACE (MFDs exactly 640×480, radar 480×640 portrait), drops the on-screen RIO button banks (the cab's buttons are PHYSICAL — drawing fake ones over a real panel is precisely wrong), goes frameless, and does not create the Flight Controls pad at all (6 windows, not 7). Per-window equivalent: append,bareto a line inglass_layout.cfg(mixed rigs).- Placement receipts — every window logs
[glasswin] '<title>' surface=<port> at X,Y WxH bare -> monitor \.\DISPLAYn (origin WxH, PRIMARY). On a cab nobody can see seven surfaces at once, and over CRD you cannot see the panels at all: the log IS the confirmation that a picture landed on the right glass.
Boot-STABLE panel binding — monitor:id: (DEPLOYED + POD-VERIFIED 2026-08-10)
Live on ALPHA-MR since 2026-08-10 — mapping eye-verified over livestream, then the master
C:\bt411\glass_layout.cfg flipped to the id form (old cfg saved as
glass_layout.cfg.displaynbak) and the relaunch bound all three windows to the SAME rects with
zero mismatch shouts [T2]. The shipped binding:
Secondary / Radar=monitor:id:Default_Monitor,bare # colour radar LCD, USB adapter
VGA Port A=monitor:id:UID224795,bare # RAR0005: Heat + Comm + Mfd2 CRTs
VGA Port B=monitor:id:UID200195,bare # RAR0005: Mfd1 + Mfd3 CRTs
⚠ The radar LCD's USB display adapter presents NO EDID — its only stable identity is the
Default_Monitor placeholder (…#Default_Monitor#…&UID256#). Unique today; a second EDID-less
panel would collide, and the per-boot [glassid] dump (BT_GLASS_IDS=1, now PERMANENT in
podprofile.ini) plus the mismatch shout are the diagnosis tools. The Dell (DEL4025) is the
PRIMARY main-view panel (BT_FIT owns it; not in the layout cfg) — the 08-08 note calling it a
"1908FP" service monitor mis-guessed its role.
Windows renumbers displays. Nick, after re-cabling + a reboot: "the order changed … sometimes
they change when one gets turned off and back on, at least how windows SEEs them, even if the
visual desktop tool looks the same." So the two original binding forms are both boot-fragile —
monitor:2 is an enumeration index and monitor:\.\DISPLAY4 is a GDI name Windows reassigns.
(Same trap next door in GameOS: its -tmon takes DirectDraw device indices, which are neither
Windows monitor numbers nor stable, and a NULL-device merge shifts every index down by one on top
— see Nick's gos-displays.txt.)
The fix: bind to the panel's own hardware identity. EnumDisplayDevices on a display's MONITOR
child returns a DeviceID embedding the EDID manufacturer + product code and the connector
instance — neither moves across a reboot or a power-cycle.
- Discover: run once on the pod with
BT_GLASS_IDS=1. Every attached panel logs itsstable-idand a ready-to-pastecfg form:The volatile identifiers print beside the stable one deliberately: run it twice across a power-cycle and[glassid] index=0 device=\\.\DISPLAY1 PRIMARY rect=0,0 1920x1080 stable-id = \\?\DISPLAY#AUO10ED#4&31323a6c&1&UID265988#{e6f07b5f-…} cfg form = monitor:id:AUO10EDindex/devicemove whilestable-iddoes not — that IS the proof. - Bind:
Heat MFD=monitor:id:AUO10ED,bareinglass_layout.cfg. - Identical panels (the pod's mono MFDs are likely one model, so EDID codes collide): use a
longer fragment from
stable-id— theUID…/instance tail differs per connector, somonitor:id:UID265988picks exactly one. - An
id:that matches nothing WARNS and falls back to computed placement:[glasswin] monitor id 'X' matched NO attached panel. Silence there would put a picture on the wrong glass and look exactly like the bug this form exists to prevent. - Nothing changes by default. No env, no
id:→ identical behaviour;monitor:<name|index>and rawx,ykeep working. Playtester glass builds are untouched. [T2 verified on a 1-monitor dev box — discovery, binding, centring and the mismatch warning; T3 on the pod, which was offline: multi-panel disambiguation is unproven.]
Runbook (tools/podprobe.ps1, PowerShell, no install/admin — run it ON the pod PC):
- Probe: GPUs, every monitor's virtual-desktop rect, EDID make/model (identifies the original
panels), serial ports (the RIO board), session type, and a PROPOSED
glass_layout.cfgthat assigns the six surfaces to the non-primary monitors top-to-bottom/left-to-right, centred. - Drop the cfg in the game's working directory; run with
BT_GLASS_PANELS=1 BT_POD_SURFACES=1 BT_GLASS_LAYOUT=load(=saveto persist drags instead). - Read the receipts; re-assign titles to monitors in the cfg until each picture is on its panel. Surface→panel roles: Heat MFD = upper left (coolant), Engineering = upper centre, Comm MFD = upper right (hot box), Left/Right Weapons = lower left/right, Secondary/Radar = the secondary screen (portrait). Main 3D view stays the game's own window on the main-view monitor.
Known constraints / open on-pod questions: whether the panels are attached to this PC at all (the probe answers it); whether Windows offers a 640×480 mode on them (if not, the surface renders at native size CENTRED, not scaled — a scale-to-fit option is the obvious follow-up); whether Chrome Remote Desktop holds the CONSOLE session (it should — an RDP session would get a virtual display and light nothing); and the RIO serial input, which is a separate task from the displays.
ALPHA-MR — the verified cart config, FROZEN (2026-08-06) [T2, field-verified by eye]
The bring-up above landed: real pictures on real pod glass, then user-confirmed correct ("ok this is correct"). What that took, and where it now lives permanently.
The rig. Win10 IoT Enterprise LTSC on the cart; Intel HD630 + a "Trigger 6 External Graphics" USB adapter. Four console displays: DISPLAY3 800×600 PRIMARY at 0,0 (main view), plus DISPLAY4 @800,122, DISPLAY2 @1440,123, DISPLAY1 @2080,124 (each 640×480). Physical cab: five mono MFD CRTs (two left stacked, two right stacked, one centre loose on the desk) plus the colour radar LCD in the yellow-button frame.
The mapping (glass_layout.cfg, monitor:<name> binding — see glass-cockpit):
Secondary / Radar = monitor:DISPLAY4,bare # colour LCD, yellow-button frame
VGA Port A = monitor:DISPLAY2,bare # RGB triple: Heat + Comm + Mfd2
VGA Port B = monitor:DISPLAY1,bare # RGB triple: Mfd1 + Mfd3
Three windows, not six — under BT_POD_RGB each VGA-port window carries three MFDs on its three
colour planes (§THE RGB SPLIT). Desktop-coordinate order says NOTHING about physical cab position:
the first assignment, made by sorting monitors left-to-right, was wrong. Only eyes on the glass
settle it.
Two deviations from the 1995 config, both real, both this cab's wiring:
BT_POD_CHANMAP=Comm=blue,Heat=red— kills/deaths and coolant loops came up on each other's panel.L4GAUGE.CFGhas Comm on red and Heat on blue; THIS cab's splitter harness wires them the other way. The env override remaps a port's colour channel without touching the authentic config file. [T2]BT_GAUGE_SEC_ROT=0— the radar here is a LANDSCAPE LCD, so the 640×480 source needs no rotation. The 1995 pod's 270° rotation existed because its secondary CRT was mounted PORTRAIT; it is a mounting fact, not a format fact. (En route: 180° looked plausible and was upside down.)
Frozen in content/environ.ini, not in a launcher — scratchpad/pod/podprofile.ini is the
block, scratchpad/pod/mergeprofile.ps1 merges it idempotently between markers. environ.ini is
read before anything touches the environment (build-and-run), and the real environment still
WINS, so a .bat can override any of it for a one-off. The cart's runpod.bat now sets only
BT_LOG/BT_GLASS_LOG and the -egg; runpod_env.bat keeps the all-inline version as the
fallback. Two gates were added for this:
BT_FIT=1— the env spelling of-fit, so the borderless main view survives however the game is started (shortcut, scheduled task, autostart) instead of riding on one launcher's command line. It fitsMONITOR_DEFAULTTOPRIMARY= DISPLAY3, which is the main-view panel.L4PLASMA=NONE(alsoOFF/0) — no marquee at all. Needed because the GLASS profile force-defaultsL4PLASMA=SCREEN, so simply leaving it unset still puts a desktop plasma window on the pod's glass. The boot banner reportsplasma off [L4PLASMA]when it takes. Verified on build 4.11.813 with the launcher carrying none of it:[boot] environ.ini: 9 setting(s) applied,[cockpit] -fit: borderless 800x600,[glasswin] radar rotation 0 (none), and all three surfaces on their intended\\.\DISPLAYn.
Surviving a NEW BUILD, not just a reboot. Both frozen files live INSIDE the versioned install
(content\environ.ini, content\glass_layout.cfg) and NEITHER ships in the zip — environ.ini is
generated on first run. So extracting a new build gives a cab that comes up wrong with no error
anywhere. The masters therefore live at the stable C:\bt411\ (podprofile.ini,
glass_layout.cfg, podkit.ps1), and setup_pod.bat pushes them into the newest BT411_*
folder — run it once after each extract. runpod.bat resolves the newest install and applies
the kit itself before launching, so the remote path needs no hand-editing per build. Re-tuning the
cab means editing the MASTER at C:\bt411\, not the install's copy: the apply overwrites it, on
purpose, so there is one place to look when a panel moves. Repo copies: scratchpad/pod/.
The tester launchers work on the cab unchanged (verified 2026-08-06) [T2]. play_solo.bat,
join.bat, join_lan.bat, play_steam.bat and joyconfig.bat set BT_PLATFORM=glass,
BT_START_INSIDE=1, BT_DEV_GAUGES=1 plus their own mode flag — and not one pod key between
them. So each inherits the whole rig from environ.ini without knowing the pod exists. This is the
argument against a separate pod-only settings file: a second file would need every launcher to opt
in, which is the shape of the "competing keymaps" bug that made glass the default profile.
Observed: play_solo.bat on the cart logs environ.ini: 7 setting(s) applied — 7 not 9, because
the bat sets BT_PLATFORM and BT_START_INSIDE itself and the real environment wins.
Steam MP needs the client installed and logged in (AppID 480/Spacewar, so any account, no
purchase); installed on the cart 2026-08-06. ⚠ #68: a failed JOIN/HOST exits the process
SILENTLY — "Steam not running" and a real crash look identical to a player.
The HARDWARE RIO is live on the cart (2026-08-06) [T2]
L4CONTROLS=RIO:COM1,KEYBOARD in the frozen profile — the real cockpit board instead of PadRIO.
The whole stock path above the seam (mapper, lamps, streamed .CTL mappings) runs unchanged; the
109-mapping L4 control table installs exactly as it does on a desktop. This is independent of
the platform profile — the cab keeps the GLASS display stack (panels + RGB split) and only the
input device changes, which is what the glass/PadRIO seam was built for. Do NOT reach for
BT_PLATFORM=pod to get it.
- Wiring:
RIO:COMn→\\.\COMnat 9600 8N1 (L4SERIAL); bareRIOdefaults to COM1. Reads are non-blocking (ReadIntervalTimeout=MAXDWORD, totals 0), so an absent or dead board cannot hang the game — it just goes quiet. - RIO and PAD are MUTUALLY EXCLUSIVE — both assign
rioPointerin theL4CONTROLSparser, so the last token wins. Selecting the hardware RIO turns the XInput pad OFF on that machine.KEYBOARDis safe alongside: it only setsflags.keyboardExistsand never touchesprimaryControlType. - The board's own self-test is the diagnostic.
RIO::RIOruns it (perform_tests = Trueby default) and writescontent\FAILURE.LOGpagesRIOBoardErrors/RIODeadLamps/RIOErrors, deleting them first so the file is always current. On the cart, reproducibly: 4 missing boards (Slot=3:Address=0,3:2,4:0,5:0) and 16 dead lamps (AuxUpperRight1-8,AuxUpperCenter1,TeslaRelay1/3,PanicButton,IcomAmpEnableRelay,IcomIncRelay,FloorEntry,undefined_0x17) — the crash cart is a PARTIAL cab, and those are the boards it does not have. A specific 4-of-many inventory is itself the proof the link is good: a dead serial line reports the whole address space missing, not four slots. - Input reaching the mech was seen live as
[ctrlmap] push stick x=0.0595238 y=0with nobody at the cab — the physical stick sitting just outside the 5% deadband. NB when the stick is centred this readsx=0, which is indistinguishable from no data: a centred-stick zero proves nothing either way. ⚠ Not yet checked by hand: that stick/throttle/pedal travel and the physical buttons drive the mech correctly, and whether the cab's pots match theRangercalibration baked intoRIO::RIO(JoystickX -96..96,JoystickY -97..108,Throttle 0..800, pedals0..470). A mismatch shows up as drift or short travel, not as an error. - The boot banner names the resolved device —
GLASS (hardware RIO; plasma off [L4PLASMA]). It used to hardcode "PadRIO", which is a lie on a wired cab and exactly the line you read to check. - ⚠ THE COUPLING BUG this exposed (fixed 2026-08-06) [T2].
BTGlassPanels_Create()was called ONLY from the end of the PadRIO constructor — the panels began life as the frames around the on-screen RIO button banks, so "the buttons ride the device". Selecting the hardware RIO means PadRIO is never constructed, so every MFD window silently failed to appear: the mission came up on the main view with all the pod glass dark, and not one[glasswin]line in the log. The MFD panels are a DISPLAY concern, so creation moved toLBE4ControlsManagerafter theL4CONTROLSparse, where every device branch converges — plus a symmetricBTGlassPanels_Destroy()in its destructor, because~PadRIOtore them down and~RIOknows nothing about them (windows outliving the surfaces they blit would crash on the next mission cycle). Both calls are idempotent (Createreturns ongWinCount != 0), so the PadRIO path is unchanged and simply arrives first.
Working on the cart, remotely. SSH over Tailscale lands in session 0, which has a dummy
"WinDisc" display and CANNOT see or enumerate session 1's windows — GUI work must go through
schtasks /run /tn BT411Run (task registered /IT), and window enumeration over SSH silently
returns nothing rather than failing. Kill btl4.exe BEFORE scp'ing a new exe or the file is
locked. The receipts in podrun.log are the remote eyes; a missing DEBUG_STREAM line in an
otherwise-logging run is real evidence that code path did not execute.
The remote runbook — READ THIS BEFORE IMPROVISING (2026-08-08)
Every line below cost real time to rediscover on a live stream. Follow it in order.
1. Connect. ssh bt411-pod (alias in ~/.ssh/config) → bt411-pod.tail840fa4.ts.net, user
user, key ~/.ssh/bt411_pod.
⚠ The key has existed since 2026-08-06 but ssh will NOT offer it without the config entry — it
only tries default names (id_rsa/id_ed25519). Without it you get
Permission denied (publickey,password,keyboard-interactive), which reads exactly like "auth was
never set up". It was. Also: the Tailscale node KeyExpiry (2027-02-02) is what keeps the cab on
the tailnet — it is not an SSH credential, and Tailscale SSH is not enabled on the pod (the
peer advertises no sshHostKeys), so port 22 is the pod's own Windows OpenSSH.
2. Absolute paths for System32 tools. Over this SSH+cmd session, bare taskkill / setx
return The system cannot find the path specified. and silently do nothing. Use
C:\Windows\System32\taskkill.exe. (tasklist, schtasks, dir, copy happen to resolve.)
3. Deploy — the SAME procedure a tester uses. No pod-special exe drops.
python tools/mkdist.py # -> dist/BT411_4.11.NNN.zip
scp dist/BT411_4.11.NNN.zip bt411-pod:C:/bt411/
ssh bt411-pod "powershell -NoProfile -Command \"Expand-Archive C:\bt411\BT411_4.11.NNN.zip -DestinationPath C:\bt411 -Force\""
ssh bt411-pod "powershell -NoProfile -ExecutionPolicy Bypass -File C:\bt411\podkit.ps1 -Content C:\bt411\BT411_4.11.NNN\content"
runpod.bat auto-resolves the newest BT411_* by date, so nothing else needs pointing.
Local config is never clobbered: mkdist.py packs git-TRACKED content only, and
bindings.txt/environ.ini/glass_layout.cfg are all gitignored. podkit.ps1 pushes the frozen
rig masters (podprofile.ini, glass_layout.cfg, PODTEST.EGG) from C:\bt411\ into the install.
⚠ PODTEST.EGG is not in the repo — only podkit carries it. If podkit fails, the launcher runs
and nothing appears, with no error.
4. Launch. schtasks /run /tn BT411Run → runs C:\bt411\runpod.bat as user,
LogonType=Interactive, so it lands in session 1 and is visible on the panels. Log:
<install>\content\podrun.log.
5. ⚠ THE PODKIT INFINITE LOOP — the trap that ate an evening. podkit.ps1 line 30 was
while ($keep.Count -gt 0 -and $keep[-1].Trim() -eq '') { $keep = $keep[0..($keep.Count-2)] }
When $keep trims to ONE blank line, $keep.Count-2 is -1 and PowerShell's $keep[0..-1]
returns two elements instead of shrinking — infinite loop, RSS climbing past 60 MB. It fires
whenever everything outside environ.ini's marker block is blank, i.e. any environ.ini that was
already kitted — exactly what you get carrying it forward from the previous install. Patched to
-gt 1 on the pod 2026-08-08 (podkit.ps1.bak is the original); this fix is NOT in any repo,
so a restored/replaced podkit brings the bug back.
Signature: a blank cmd console on the pod, task stuck Status: Running, no btl4.exe, no
podrun.log. The cascade: each hung run holds environ.ini, so every later attempt blocks too —
and over SSH your client times out while the REMOTE powershell keeps running, so "it returned
instantly and did nothing" actually means "it is still hung". Recover with
taskkill /F /PID <pid> (absolute path) on the session-1 cmd+powershell pair, then
schtasks /end /tn BT411Run before re-running — a task already Running refuses /run with
2147946720 (0x800710E0, "operator refused the request").
6. Panel identity (measured 2026-08-08, over SSH — WMI is session-independent so this works
without a GUI): DISPLAY\RAR0005\…UID224795, DISPLAY\DEL4025\…UID249395 (DELL 1908FP),
DISPLAY\RAR0005\…UID200195. The two RAR panels share one EDID code and have blank serials, so
monitor:id:RAR0005 is ambiguous — they must be bound by the per-connector form
(monitor:id:UID224795 / monitor:id:UID200195). RESOLVED 2026-08-10: the full map is
UID224795 = VGA Port A (Heat+Comm+Mfd2), UID200195 = VGA Port B (Mfd1+Mfd3), Default_Monitor
(EDID-less USB adapter, UID256) = the colour radar LCD, DEL4025 = the PRIMARY main-view panel
(not a service monitor). The master cfg now ships the id form (see the binding section above);
glass_layout.cfg.displaynbak preserves the old DISPLAYn cfg.
7. ⚠ cmd redirect trap when scripting over SSH: echo VAR=1>> file appends VAR= — cmd
parses the digit-adjacent 1>> as a stream-handle redirect and eats the value (and MSVC putenv
treats an EMPTY value as UNSET, so the flag silently never arms). Space it (echo VAR=1 >>file,
trailing-space payload) or use PowerShell Add-Content. Bit us arming BT_GLASS_IDS 2026-08-10.
8. Tester-procedure deploy VERIFIED end-to-end (2026-08-10, build 876): scp zip →
Expand-Archive → podkit → schtasks /run came up clean on the first try — rig config survived,
newest-install auto-resolve worked, no hand-edits. The cab genuinely upgrades like a tester box.
9a. ⚠ THE RIO's "COM1" IS A USB-SERIAL ADAPTER — the axis-dropout / frozen-boot fault
(2026-08-10, minidump-proven) [T1]. \Device\VCP0 = an FTDI adapter (FTE7PGA5A); COM2 is a
second FTDI (A995JTOYA); two ghost Prolific PL2303 entries also claim "COM1" from earlier
plug-ins (clean them in Device Manager). When the adapter's USB side drops mid-operation:
brief drop = analog polls go mute (the field "encoder dropouts", revived by hand-jostle /
re-enumeration — NOT by button protocol); drop with a write in flight = the 1995 code's
unbounded GetOverlappedResult wait in PCSerialPacket::SendPacket parked the whole game at
0 CPU forever (three boots on 2026-08-10; stack: NtWaitForSingleObject ← GetOverlappedResult ←
SendPacket ← RIO ctor). Hardened in 4.11.881: 500 ms bounded write + CancelIo + 3 s sick
window (fast-drop, game keeps frame rate, keyboard fallback lives); txTO= in the [rio] health
line counts the events — proven the same evening (txTO=1 rRetry=4 on a boot that SURVIVED).
USB selective suspend disabled system-wide on the cab the same day (powercfg) — prime suspect
for the random-period idle dropouts. Hardware to-do: FTDI adapter into a REAR motherboard USB
port, reseat its USB end. ⚠ A first "print-deadlock" diagnosis of the boot wedge was WRONG
(confounded A/B) and is retracted — the dump is the authority.
Remote minidump procedure (worked over SSH, no tools install): 32-bit dump of the wedged pid
via C:\Windows\SysWOW64\rundll32.exe C:\Windows\SysWOW64\comsvcs.dll, MiniDump <pid> C:\bt411\w.dmp full → scp home → local x86 cdb (where.exe cdb resolves the bt411-tools copy)
with -y C:\git\bt411\dist (the archived per-build PDBs) → ~*k. A 64-bit comsvcs dump of the
WOW64 process shows only 64-bit frames — use the SysWOW64 pair.
9. ⚠ schtasks /end ORPHANS the game — the dead-RIO trap (2026-08-10): /end kills the
batch, but runpod.bat start ""s the exe DETACHED, so the old btl4.exe survives, keeps COM1
open, and the NEXT launch boots with a dead cockpit board: InitSerialPort() failed [GetLastError() = 5] (access denied) → RIO initialization failed! → buttons dead while the
banner still says "hardware RIO" (the banner is the PROFILE, not the init result). Diagnosis:
tasklist /FI "IMAGENAME eq btl4.exe" showing ≥2 instances. FIXED at the root the same day:
runpod.bat now taskkill /F /IM btl4.exes FIRST (original saved as runpod.bat.bak), making
the BT411Run task an idempotent restart — but any hand-rolled launcher on a future cab needs the
same line, and a mid-mission /run now RESTARTS the game rather than failing quietly.
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").
- MYOMER SEEK / "SUPER CHARGE" — ⚠ the "4.0→4.10 drift" verdict below was WRONG and is
RETRACTED (same day). 4.10 HAS the seek→speed coupling — in the un-exported master-perf
gap (@0x4a9cf2:
speedDemand *= max myomer speedEffect, turn-freeze at dead drive; see subsystems WAVE 6 correction). The manual is ACCURATE for 4.10; Oracle's pod memory (seek-4 humanoid = 182 kph = the printed figure) confirmed it and prompted the re-audit. The paragraph below is retained as the record of the error (its BINARY facts — the two exported AvailableOutput callers, the single attr ref — were correct but incomplete: export-gap functions are invisible to decomp text sweeps).ORIGINAL (WRONG) VERDICT:Manual p20/p22: seek "translates to a lower or higher top speed", gear 4 is named Supercharge (screenshot caption "Seek Level Is Set to Supercharge"), expert-only ("in standard simulation mode, you are not able to supercharge"), and "overheated myomers can reduce your 'Mech's speed to zero". Stat sheets print THREE top speeds (Loki/Thor: Normal 143 / Super Charged 182 / Gimped 40 kph). The 4.10 BINARY dropped the runtime coupling entirely: the mapper demand @004afd10 has no myomer/seek term,AvailableOutputhas exactly two callers (assembly cap + graph), and the SpeedEffect attribute string has one reference (its own table row). Even the printed ratio disagrees with 4.10's authored gears (182/143 = 1.27 vs 0.9999/0.7 = 1.43) — the gear table and the assembly cap are vestiges. In 4.10 the seek dial's live effects are heat (ratio² ≥ 1), the generator brown-out threshold (whose real stake is TORSO TWIST — see subsystems WAVE 6), and the ENG graph. "Top Speed Gimped 40" also confirms the limp-gait speed cap as an authored spec. The port follows the BINARY (house rule); resurrecting manual-4.0 supercharge would be an opt-in deviation for the operator to decide. NEW LEADS (manual describes, port lacks input/UI):CROUCH✅ CROUCH COMPLETE 2026-08-06 (locomotion §CROUCH),EJECTEJECT WIRED 2026-08-02 (core:Mech::EjectPilotMessageHandlerid 0x19 @0049f854 + the crippled-mech permission evaluator @0049fa1c; input = binding-engine "Eject" action, Backspace / pad LeftThumb; the punch-out kills via graphicAlarm 10 ≥ 9 — KillBonus authors 0 in ALL shipped content, so the zero charge is authentic; tails: console relay notice, RIO 0x38 panic control, alarm-10 audio/canopy presentation,SpecialCaseDeathPenaltyrole+0x20 consumer), 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
- Renders: gauges-hud (the MFD surfaces), rendering (the main 3D view).
- Input: locomotion (the mapper).
- Bring-up: Phase 8 (get pod specifics from Nick — open-questions).