Brings Cyd's experience-level / simulation-mode research onto master (the KB-decode portion of glass-cockpit 4e01e83; the bundled BT410 source manifest is left for the full glass-cockpit merge). - context/experience-levels.md (NEW): the egg per-pilot 'experience' field (novice/standard/veteran/expert) is the pod's SIMULATION-FIDELITY tier, decoded end-to-end. FUN_004c0bc8 reads btMission->experienceLevel(+0xe4) and fans it into the +0x25c/+0x260/+0x26c/+0x270/+0x274 flag block: +0x25c 'sim live' (novice lockout: jams/searchlight/powersub), +0x260 the HEAT-MODEL master switch (veteran+expert; FUN_004ad7d4), +0x274 raw level (FUN_004ac9c8 = ==0 novice predicate; the valve/advanced-cockpit lockout). 4.0->4.10 drift: the viewscreen hunting-aid gate is GONE in 4.10 (HUD reads none of these flags), and movement heat is veteran+expert not expert-only. - btplayer.cpp/.hpp: CORRECTED comments/labels -- the +0x25c..+0x274 block is seeded from btMission experienceLevel/advancedDamageOn, NOT the scenario role's returnFromDeath; the 'roleClassIndex/showKills' names are scoring-era mislabels. Code logic UNCHANGED (comment-only): the ctor still reads scenarioRole (STAND-IN, defaults 2 = veteran) pending the wiring task. - Corrections swept into gauges-hud.md (ROOKIE->novice lockout), open-questions.md (player+0x260/0x274 semantics now PINNED), pod-hardware.md, subsystems.md, decomp-reference.md; CLAUDE.md router row. No code-logic change -> no rebuild needed (comment + markdown only). The runtime wiring (seed from BTMission::ExperienceLevel()) remains TODO. Co-Authored-By: Cyd <cyd@falloutshelterarcade.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
15 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 — the preset/eng "display eng data" banks; 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).
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. 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 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::duckStateattr 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
- 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).