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:
+4
-20
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |t−1.0| ≤ 0.05 — then `MechControlsMapper::InterpretControls` @004afd10
|
||||
computes `speedDemand@0x128 = maxSpeed(mech+0x34c) × throttlePosition(0x11c) × scale(mech+0x5c0)`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user