Controls: the .CTL positional-id off-by-one -- CONFIRMED live and FIXED (step 2a)

The streamed L4 mapping resource carries the binary's positional attribute ids
(stick=3, throttle=4) but MechControlsMapper chained from Subsystem::NextAttributeID
== 2 -- every streamed record resolved ONE MEMBER LATE (attr 4 -> pedalsPosition,
verified via the new permanent BT_CTRLMAP_LOG diagnostic in CreateStreamedMappings).
A latent real-pod bug: the serial RIO throttle would drive the pedals member; the
dev keyboard bridge masked it. Fix per the mechweap/mech attrPad idiom: ids pinned
to the binary numbering, id-2 gap padded, static_assert-locked. Torso + weapon
chains verified already aligned. Regression: BT_FORCE_THROTTLE headless walk clean
(speedDemand=61.501 through authentic InterpretControls, gait cycles, no faults).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-17 21:59:51 -05:00
co-authored by Claude Fable 5
parent 3f49915d76
commit b26e8205e3
7 changed files with 113 additions and 24 deletions
+4 -20
View File
@@ -1,8 +1,8 @@
[mission]
adventure=BattleTech
map=cavern
map=grass
scenario=freeforall
time=night
time=day
weather=clear
temperature=27
length=600
@@ -156,9 +156,8 @@ x=128
y=32
width=8
[pilots]
pilot=127.0.0.1:1502
pilot=127.0.0.1:1602
[127.0.0.1:1502]
pilot=200.0.0.96
[200.0.0.96]
hostType=0
advancedDamage=1
loadzones=1
@@ -172,20 +171,6 @@ dropzone=one
vehicle=bhk1
vehicleValue=1000
color=White
[127.0.0.1:1602]
hostType=0
advancedDamage=1
loadzones=1
name=Boreas
bitmapindex=2
experience=expert
badge=VGL
patch=Yellow
role=Role::Default
dropzone=one
vehicle=madcat
vehicleValue=1000
color=Crimson
[largebitmap]
bitmap=BitMap::Large::Aeolus
[BitMap::Large::Aeolus]
@@ -242,4 +227,3 @@ width=4
model=dfltrole
[Role::NoReturn]
model=noretun
+10 -3
View File
@@ -5,7 +5,7 @@ status: living
source_sections: "plan 2026-07-17; CMakeLists.txt gates; BT412 prior art (reference only)"
related_topics: [pod-hardware, build-and-run, multiplayer, gauges-hud]
key_terms: [PadRIO, miniconsole, glass-cockpit]
open_questions: [".CTL positional-attribute off-by-one — audit pending (step 2a)"]
open_questions: []
---
# Glass Cockpit — the desktop developer layer
@@ -39,8 +39,15 @@ copied from them.
## Status
- **Step 1 (gates + scaffolding): IN PROGRESS 2026-07-17.** CMake options + defines land with
this file; gated `target_sources` blocks are added as each TU lands.
- **Step 1 (gates + scaffolding): DONE 2026-07-17.** CMake options + defines; 3-config
configure matrix + negative test + defaults build verified.
- **Step 2a (.CTL id audit): DONE 2026-07-17 [T2].** The streamed `.CTL` positional ids landed
every `MechControlsMapper` record ONE MEMBER LATE (our chain starts at 2, the binary's at 3)
— a latent REAL-POD bug (RIO throttle would drive pedalsPosition), masked on dev boxes by the
keyboard bridge. Fixed ungated: ids pinned to binary numbering + id-2 pad + static_assert
locks (`mechmppr.hpp/.cpp`); torso/weapon chains were already aligned. Permanent env-gated
diagnostic: `BT_CTRLMAP_LOG=1` dumps every streamed record's resolved member offset
(`L4CTRL.cpp CreateStreamedMappings`). Full evidence: `docs/GLASS_COCKPIT.md` §2a.
- Step 2 (PadRIO + displays): pending. Secondary displays reuse the EXISTING dev-gauge modes
(dock-bottom `BT_DEV_GAUGES=1`, combined separate window `BT_DEV_GAUGES_WINDOW=1`, overlay
inset `BT_DEV_GAUGES_DOCK=1` — see [[gauges-hud]] + `docs/GAUGE_COMPOSITE.md`); NO new MFD
+6
View File
@@ -43,6 +43,12 @@ The authentic pod throttle path — traced 2026-07 (task #50 throttle-fidelity q
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`.
Details: `docs/GLASS_COCKPIT.md` §2a; see [[glass-cockpit]].
4. **Interpretation:** `L4MechControlsMapper::InterpretControls` @004d196c applies the ONLY software
detent — snap to 1.0 when |t1.0| ≤ 0.05 — then `MechControlsMapper::InterpretControls` @004afd10
computes `speedDemand@0x128 = maxSpeed(mech+0x34c) × throttlePosition(0x11c) × scale(mech+0x5c0)`
+39
View File
@@ -19,3 +19,42 @@ first); this file carries the step-by-step detail, evidence, and verification tr
(purity build); `-DBT_GLASS=ON` and ON/ON configures print correct gate state; the
BT_STEAM-without-BT_GLASS negative test FATAL_ERRORs. Full 3-config BUILD matrix becomes
meaningful from step 2 (first gated code) and is re-run there.
## 2026-07-17 — Step 2a: the .CTL positional-attribute off-by-one — CONFIRMED and FIXED (ungated)
**The bug [T2, live-verified]:** the streamed `.CTL` "L4" mapping resource carries the 1995
binary's positional attribute ids (stick=3, throttle=4, ...), resolved at install time by
`Simulation::GetAttributePointer(id)``AttributeIndexSet::Find(id)` = `attributeIndex[id-1]`
(positional). `MechControlsMapper` chained its enum on `Subsystem::NextAttributeID == 2`, but
the binary's chain reaches 3 (one base attribute we don't publish) — so EVERY streamed mapper
record landed one member late.
**Evidence (new `BT_CTRLMAP_LOG=1` diagnostic in `LBE4ControlsManager::CreateStreamedMappings`,
`engine/MUNGA_L4/L4CTRL.cpp` — permanent, env-gated, default off).** Before the fix
(`build-pod` Debug, `-egg DEV.EGG`, dev platform):
```
rec 0 subsys 0 group 1(Joy) elem 0 attrID 3 -> +0xe4 (= throttlePosition: WRONG, stick intended)
rec 1 subsys 0 group 0(Scalar) elem 0 attrID 4 -> +0xe8 (= pedalsPosition: WRONG, throttle intended)
rec 2 subsys 0 group 3(Button) elem 63 attrID 6 -> +0xf0 (= speedDemand: WRONG, reverse intended)
rec 4-6 attrID 10/11/12 -> lookRight/lookBehind/lookDown (one late; LookLeft/Right/Behind intended)
```
Spacing attr3→attr4 was 4 bytes (Scalar) where stick→throttle must be 8 (ControlsJoystick).
The weapons chain (attrID 19 → +0x31c TriggerState) was already correct — mechweap was
pad-aligned in task #16 (gotcha #11). Torso (subsys 17) also aligned (MechSubsystem publishes
binary id 2). ONLY the mapper chain (subsys 0) was off. On the dev box the keyboard INPUT
BRIDGE (writes the mapper members directly, after the engine push) masked it; on a REAL POD the
serial RIO throttle scalar would have driven `pedalsPosition` and the stick x/y would have
driven throttle+pedals — the Phase-8 landmine prior art predicted.
**The fix (ungated, pod-correctness — user-approved 2026-07-17):** `mechmppr.hpp` pins the enum
to the binary numbering (`MechControlsMapperPadFirstAttributeID = 2`, stick=3 ... PilotArray=
0x16); `mechmppr.cpp` pads id 2 with a valid never-bound entry (the mech.cpp attrPad idiom) and
locks the base + pinned ids with `static_assert`s. `L4MechControlsMapper::TargetRangeExponent`
(chained) moves 0x16→0x17, matching the binary's own arithmetic; it is bound by name (audio
zoom-blip), so no consumer shifts.
**After the fix:** `attrID 3 -> +0xdc` (stick), `attrID 4 -> +0xe4` (throttle, 8-byte spacing
✓), `attrID 6 -> +0xec` (reverse), `10/11/12 -> +0xfc/+0x100/+0x104` (the look trio); torso +
weapons unchanged. Regression: headless forced-walk run (`BT_FORCE_THROTTLE=1`) drives normally
`[mppr] thr=1 -> speedDemand=61.501`, gait cycles, position advances, 31/31 subsystems
simulated, no faults.
+23
View File
@@ -2124,6 +2124,29 @@ void
if (mapping->mappingType == ControlsMapping::DirectMapping)
{
attribute = subsystem->GetAttributePointer(mapping->attributeID);
//
// BT_CTRLMAP_LOG=1: dump each streamed .CTL record and the member
// OFFSET its attributeID resolved to on the live subsystem. The
// stream carries the 1995 binary's positional attribute ids, so a
// resolved offset that differs from the binary's member offset for
// that id means the reconstruction's id chain is misaligned (the
// gotcha-#11 class). Audit trail: docs/GLASS_COCKPIT.md step 2a.
//
{
static const int s_ctrlmapLog =
(getenv("BT_CTRLMAP_LOG") != 0 && *getenv("BT_CTRLMAP_LOG") != '0');
if (s_ctrlmapLog)
{
DEBUG_STREAM << "[ctrlmap] rec " << i
<< " subsys " << mapping->subsystemID
<< " group " << (int)mapping->controlsGroup
<< " elem " << element
<< " attrID " << (int)mapping->attributeID
<< " -> +0x" << std::hex
<< (attribute ? (int)((char*)attribute - (char*)subsystem) : -1)
<< std::dec << "\n" << std::flush;
}
}
switch (mapping->controlsGroup)
{
case ScalarGroup:
+19
View File
@@ -249,9 +249,28 @@ MechControlsMapper::MessageHandlerSet&
// Attribute table @0050efd0. (Recorded offsets carry the engine scalar tag,
// e.g. 0x115 -> stickPosition @0x114.)
//
// Step-2a fix (2026-07-17): ids are pinned to the binary numbering (stick=3)
// and the id-2 chain gap is padded -- see the enum note in mechmppr.hpp.
// Locked so a future engine-base attribute publish shows up here instead of
// silently double-assigning ids:
static_assert((int)Subsystem::NextAttributeID
== (int)MechControlsMapper::MechControlsMapperPadFirstAttributeID,
"MechControlsMapper pad base != Subsystem::NextAttributeID -- re-count the pad");
static_assert((int)MechControlsMapper::StickPositionAttributeID == 3
&& (int)MechControlsMapper::PilotArrayAttributeID == 0x16,
"MechControlsMapper ids drifted off the binary .CTL numbering");
const MechControlsMapper::IndexEntry
MechControlsMapper::AttributePointers[]=
{
//
// The PAD fills the chain-vs-binary id gap (id 2) with a valid, named,
// never-bound entry (unique name, harmless target) so Find()'s strcmp
// walk never reads an uninitialized slot.
//
{ (int)MechControlsMapper::MechControlsMapperPadFirstAttributeID,
"MechControlsMapperPad02",
(Simulation::AttributePointer)&MechControlsMapper::throttlePosition },
ATTRIBUTE_ENTRY(MechControlsMapper, StickPosition, stickPosition), // 0x114
ATTRIBUTE_ENTRY(MechControlsMapper, ThrottlePosition, throttlePosition), // 0x11C
ATTRIBUTE_ENTRY(MechControlsMapper, PedalsPosition, pedalsPosition), // 0x120
+12 -1
View File
@@ -132,7 +132,18 @@ class Pilot;
//
public:
enum {
StickPositionAttributeID = Subsystem::NextAttributeID, // 3
// Ids are PINNED to the binary table's numbering (stick=3 ...):
// the streamed .CTL mappings carry these ids POSITIONALLY, but our
// parent chain ends at Subsystem::NextAttributeID == 2 -- the
// binary's reaches 3 (one base attr we don't publish). Chained
// (un-pinned) ids landed every streamed record ONE MEMBER LATE --
// live-verified 2026-07-17 via BT_CTRLMAP_LOG: attr 4 (binary
// ThrottlePosition) resolved to pedalsPosition; on a real pod the
// RIO throttle would drive the pedals. The id-2 gap is padded in
// the table (the mech.cpp attrPad idiom, gotcha #11) and locked by
// static_asserts in mechmppr.cpp. See docs/GLASS_COCKPIT.md §2a.
MechControlsMapperPadFirstAttributeID = 2, // == Subsystem::NextAttributeID (locked)
StickPositionAttributeID, // 3
ThrottlePositionAttributeID, // 4
PedalsPositionAttributeID, // 5
ReverseThrustAttributeID, // 6