The cart's display config was carried by a launcher .bat, so it only came up
right if the game was started one particular way. Move it into the file the
engine already reads before anything touches the environment.
content/environ.ini <- scratchpad/pod/podprofile.ini, merged idempotently
between markers by scratchpad/pod/mergeprofile.ps1
Two gates were missing for that to be enough:
BT_FIT=1 the env spelling of -fit, so the borderless main view does not
depend on one launcher's command line (shortcut, scheduled
task and autostart all have to produce the same rig)
L4PLASMA= NONE / OFF / 0 -> no marquee at all. Leaving it unset does
NOT work: the GLASS profile force-defaults it to SCREEN, which
drops a desktop plasma window on the cab's glass. The boot
banner now reports the live state instead of always claiming
"plasma window".
Also lands the bring-up engine work this depended on: monitor:<name|index>
layout binding (device-bound, not pixel-bound -- desktop rects move when a
display re-enumerates), ",bare" implying frameless, rotation-aware radar
surface sizing, BT_GAUGE_SEC_ROT accepting 0-3 (it silently forced 3 for
anything but 1), 180-degree ExpandPlaneToBGRA, and the BT_POD_CHANMAP /
BT_POD_IDENT / BT_POD_CHANTEST identification gates.
Verified on the cart, build 4.11.813, launcher carrying none of it:
[boot] environ.ini: 9 setting(s) applied
[boot] platform profile: GLASS (PadRIO; plasma off [L4PLASMA])
[cockpit] -fit: borderless 800x600
[glasswin] radar rotation 0 (none)
... all three surfaces on their intended \.\DISPLAYn
No [plasmawin] line in an otherwise-logging run = the ctor never ran.
KB: pod-hardware.md gains the ALPHA-MR section (mapping, the two wiring
deviations, the frozen profile, the session-0 remote-work traps) and its RGB
SPLIT "OPEN: which way is the cart wired" is now SETTLED -- it is splitter
wired, the composite is what lit it. glass-cockpit.md documents monitor:
binding, BT_FIT and L4PLASMA=NONE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rw7No5wLTpkaUgA3ANbtZZ
409 lines
32 KiB
Markdown
409 lines
32 KiB
Markdown
---
|
||
id: pod-hardware
|
||
title: "Pod Hardware — the fixed target (7 monitors, D3D9, RIO)"
|
||
status: established
|
||
source_sections: "PROGRESS_LOG.md §3; the PLATFORM PROFILE + GAUGE DEV-COMPOSITE notes"
|
||
related_topics: [gauges-hud, rendering, project-overview]
|
||
key_terms: [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)** — ✅ **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):
|
||
1. **Hardware → RIO:** serial `AnalogReply` packet → `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 in `Ranger::Update`.
|
||
2. **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).
|
||
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 |t−1.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
|
||
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:
|
||
|
||
1. **Every surface is a bit-plane + a CHANNEL.** `content/GAUGE/L4GAUGE.CFG` (the authentic 1996
|
||
pod config) configures each port as `configure(idx, port, rotation, bitMask, clut, COLOUR, palette)`:
|
||
| port | panel | mask | clut | channel |
|
||
|---|---|---|---|---|
|
||
| `Comm` | upper right | 0x8000 | clut2 | **red** |
|
||
| `Mfd2` (Engineering) | upper centre | 0x0400 | clut2 | **green** |
|
||
| `Heat` | upper left | 0x4000 | clut2 | **blue** |
|
||
| `Mfd1` | lower left | 0x0100 | clut1 | **red** |
|
||
| `Mfd3` | lower right | 0x1000 | clut1 | **green** |
|
||
| `sec` (+`overlay` 0x00C0) | secondary/radar | 0x003F | clut0 | **rgb** (full colour, rotation 270 — the physically ROTATED portrait CRT) |
|
||
`Eng1/2/3` are the engineering-page twins of Mfd1/2/3: same monitor, second bit-plane, switched
|
||
by `reconfigure(...)` giving one plane the channel and the other `blank`.
|
||
2. **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`. `BlankColor` blanks the group
|
||
(`BlankPalette()`), which is how a page swap silences the plane it replaces. The
|
||
`*TransparentZero` variants skip colour 0 so zero reads as transparent for that group.
|
||
3. **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 `,bare` to a line in `glass_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.
|
||
|
||
**Runbook** (`tools/podprobe.ps1`, PowerShell, no install/admin — run it ON the pod PC):
|
||
1. 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.cfg` that
|
||
assigns the six surfaces to the non-primary monitors top-to-bottom/left-to-right, centred.
|
||
2. Drop the cfg in the game's working directory; run with `BT_GLASS=1 BT_GLASS_PANELS=1
|
||
BT_POD_SURFACES=1 BT_GLASS_LAYOUT=load` (`=save` to persist drags instead).
|
||
3. 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.CFG` has 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 fits `MONITOR_DEFAULTTOPRIMARY` = DISPLAY3, which is the main-view panel.
|
||
- **`L4PLASMA=NONE`** (also `OFF`/`0`) — no marquee at all. Needed because the GLASS profile
|
||
force-defaults `L4PLASMA=SCREEN`, so simply leaving it unset still puts a desktop plasma window
|
||
on the pod's glass. The boot banner reports `plasma 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`.
|
||
|
||
**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 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, `AvailableOutput`
|
||
has 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), ~~EJECT~~ **EJECT WIRED
|
||
2026-08-02** (core: `Mech::EjectPilotMessageHandler` id 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, `SpecialCaseDeathPenalty` role+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]]).
|