Merge remote-tracking branch 'origin/master' into glass-cockpit

This commit is contained in:
Cyd
2026-07-20 08:59:35 -05:00
26 changed files with 962 additions and 232 deletions
+54 -2
View File
@@ -199,8 +199,36 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
GENERATOR's `pendingHeat@0x1c8` (~3.5e8/full PPC charge). `ComputeOutputVoltage` @004ba738
snaps rechargeLevel@0x320 to 1.0 within 0.01. **FailureHeat consumers:** `this+0x184`
(own heatAlarm level) == 2 -> emitter @004baa88 resets firing + holds charge 0; ballistic
@004bbd36 pins `recoil=rechargeRate` + alarm 7. @004bbd04 (ProjectileWeaponSimulation)
opens with `call 0x4b0bd0` (the powered/heat step), like @004baa88. Cockpit power-routing
@004bbd36 pins `recoil=rechargeRate` + alarm 7.
- **@004bbd04 ProjectileWeaponSimulation FULLY RECOVERED (capstone disasm, Gitea #12,
2026-07-19) [T1] — PORT NOW IMPLEMENTS IT (landed 2026-07-19, projweap.cpp; solo-verified:
missiles fly, denial blip stays Loaded, recoil clamped, slot-17 dial animates; MP re-verify
pending)** — the old RivetGun-modeled body diverged; the authentic machine:
PoweredSubsystemSimulation@4b0bd0; trigger=CheckFireEdge@4b9608; UpdateEject@4bbb50;
**gate 1 @4bbd36: `simulationState@0x40==1` (destroyed — NOT simulationFlags@0x28!) ||
heatAlarm@0x184==2 || FUN_49fb54(owner disabled) → recoil=rechargeRate + alarm 7**;
gate 2 @4bbd71: bin=Resolve(this+0x43c)@417ab4; bin alarm LEVEL@+0x1A8 ∈{2,3} || bin
simulationState@0x40==1 → alarm 7 (re-pinned per frame while dry). switch(alarm@0x364):
**Loaded(2) @4bbec2**: on trigger — `viewFireEnable@0x3E0==0 || ownerTarget@[owner+0x388]==0
→ SetLevel(4);SetLevel(2)` (denial BLIP, stays Loaded, NO ammo pull); else
`bin->FeedAmmo@4bd4f4` (fail → stays Loaded silently) → SetLevel(0 Firing) →
**`updateModel@0x18 |= 1`** → vtbl+0x48 FireWeapon → (bin level==2 → SetLevel(7)) else
(CheckForJam@4bbfcc → SetLevel(5)) else SetLevel(3) → `updateModel|=1`
recoil=rechargeRate. **Loading(3) @4bbdd2**: trigger → blip 4→3; if
electricalState@0x278==4 { DrawFiringCharge@4bbc78; recoil<0 → clamp 0 + (bin level==1 →
SetLevel(2)) }; always vtbl+0x44. **Jammed(5) @4bbe8e**: trigger → 6→5;
recoil=rechargeRate. **7 (unavailable/NoAmmo) @4bbe4d**: trigger → blip 1;
re-SetLevel(7) unconditionally (roach motel — permanent once entered);
recoil=rechargeRate; vtbl+0x44. States 0/1/4/6 = transient audio blips (no case body).
**vtbl+0x44 (slot 17) @004b9c9c = `rechargeLevel@0x320 = (rechargeRate@0x3DC
recoil@0x3E8)/rechargeRate`** — the projectile recharge-dial writer (Emitter overrides this
slot with ComputeOutputVoltage@4ba738). **MissileLauncher::FireWeapon @004bcc60 carries NO
view/target/ammo gating and NO recoil set** (part_013.c:8741+: heat add + spawn descriptor
only) — ALL gating lives in the caller's Loaded case. ⚠ The DIRTY-for-replication bit is
`Simulation::updateModel@0x18 |= 1` (== engine `ForceUpdate()`), NOT
`simulationFlags@0x28 |= 1` — bit 0 of simulationFlags is the engine **DelayWatchersFlag**
(SIMULATE.h): setting it permanently skips the subsystem's ExecuteWatchers (audio watchers).
Cockpit power-routing
handlers @004b099c/@004b09e4/@004b0a2c/@004b0a74 (bind generator by authored id 1-4 via
@004b0b18) + @004b0abc (auto/manual, modeAlarm@0x2CC); attach/detach @004b0dd8/@004b0e30
(generator client capacity @0x1e4/count @0x1e8).
@@ -211,6 +239,29 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
- MechWeapon handler table @0x511860: {9,"ConfigureMappables"→@004b9550}, {10,"ChooseButton"→@004b95b8}
(strings @0x511994/@0x5119A7). PoweredSubsystem table @0x50F4EC: ids 4-8 = SelectGeneratorA-D /
ToggleGeneratorMode → @004b099c/@004b09e4/@004b0a2c/@004b0a74/@004b0abc.
### Binary message tables decoded 2026-07-20 (glass input audit) [T1]
16-byte HandlerEntry {id, namePtr, fnPtr, 0} rows in section_dump. Per-receiver-class id spaces.
- **Mech (entity)** @0x50BDF8..: 0x12 TakeDamage@004a0230, 0x14 PlayerLink@0049f624, 0x15
RealMaxSpeed@0049f604, **0x16 BalanceCoolant@0049f728**, 0x17 SetBurningState@0049f674, 0x18
ClearBurningState@0049f700, **0x19 EjectPilot@0049f854**, **0x1a DuckRequest@0049fa00** (the
manual's CROUCH; streamed button 0x13 sends it — unreconstructed).
- **HeatableSubsystem** @0x50E41C: {3, "ToggleCooling"→@004ad6f8} (the Eng-page msg-3 button on
every weapon/avionics — unwired in the reconstruction).
- **Generator** @0x50FB90: {4, "ToggleGeneratorOnOff"→@004b1ed0} (streamed buttons 0x1A-0x1D →
GeneratorA-D — unreconstructed).
- **Searchlight** @0x51120C: {3, "ToggleLamp"→@004b860c}; PowerWatcher/ThermalSight-side variant
@0x51117C: {3, "ToggleLamp"→@004b838c}. (Reconstruction has Searchlight::ToggleLamp but the
MessageHandlers set is default-constructed EMPTY — unwired.)
- **Myomers-class** @0x51158C: {9, "ToggleSeekVoltage"→@004b8a48}; **energy-weapon** variant
@0x511DB8: {0xb, "ToggleSeekVoltage"→@004ba478}; **ammo-weapon** @0x512210: {0xb,
"EjectAmmo"→@004bb9b8}.
- **MechRIOMapper** @0x51DD30: RE-REGISTERS the base ids 3..0x13 (Aux1Quad..ZoomOut) with the
RIO override handlers @004d22fc..@004d24f8, + {0x19, Keypress→@004d2514} (RIO's OWN keypress,
unreconstructed — we use the shared L4 0x17 @004d1bf0) + {0x1a, Hotbox→@004d2574} @0x51DE98.
The reconstruction's enum chained these ids to 0x19-0x2a until 2026-07-20 — every streamed
MFD-bank/zoom press hit the base FAIL trap; now pinned + static_assert-locked (btl4mppr.hpp).
- Unhandled ids are SILENT: `Receiver::Receive` Find() returns NullHandler and does nothing.
- MechWeapon ATTRIBUTE table @0x511890: ELEVEN entries — 0x12 PercentDone@0x320, 0x13 TriggerState@0x31C,
0x14 DistanceToTarget@0x324, 0x15 TargetWithinRange@0x34C, 0x16 WeaponRange@0x328, 0x17 PipPosition,
0x18 PipColor, 0x19 PipExtendedRange, 0x1A EstimatedReadyTime@0x330, 0x1B RearFiring@0x334,
@@ -287,6 +338,7 @@ default-ON (`'0'` disables).
| `BT_VIEWCYCLE_TEST=<frame>` / `BT_MODECYCLE_TEST=<frame>` | scripted verify (mech4.cpp): pulse a secondary-schematic cycle / a control-mode cycle at the frame and every 300 after — pixel-verifiable with `BT_DEV_GAUGES_DOCK`+`BT_SHOT` |
| `BT_PRESET_TEST=<frame>` | scripted verify (mech4.cpp, Gitea #9): from the frame, pulse an upper-MFD preset-page cycle on all three MFDs every 120 frames (Quad → populated Eng pages → Quad) — pixel-verifiable with `BT_DEV_GAUGES_DOCK`+`BT_SHOT` |
| `BT_PRESET_HOLD=<n>` | (with BT_PRESET_TEST, Gitea #11) stop after n pulses so a page is HELD — steady-state pixel verification; the 120-frame cycle phase-locks with BT_SHOT's 90-frame cadence, so cycling shots periodically capture the 1-frame page-switch transition |
| `BT_LOOK_TEST=<frame>` | scripted verify (mech4.cpp, Gitea #12): from the frame, HOLD the rear-view button 300 frames / release 300, repeating — drives mapper→BTCommitLookState→viewFireEnable so the projectile denied-shot blip (stay-Loaded, no launch) is log-verifiable headless |
| `BT_SEEK_LOG` | `[seek] BecameActive/replot` — SeekVoltageGraph activation sentinel + clear/replot events with the response-at-12kV value (Gitea #11, btl4gau2.cpp) |
| `BT_CTRLMAP_LOG` | streamed `.CTL` record audit (L4CTRL.cpp CreateStreamedMappings): Direct records with the resolved member offset + EVENT records (button→subsystem message; found the pod's secondary-panel Cycle buttons 0x15/0x18) |
| `BT_CAM_LOG` | camera-seat/broadcast trace: streamed camera network count, director pick + Players-group census, ship follow state, PlayerLink dispatch/receive, ranking-window render ([[multiplayer]] camera seat) |
+48 -15
View File
@@ -10,7 +10,8 @@ open_questions:
- "HUD attr table RESOLVED 2026-07-19 (Gitea #10 audit): re-dumped @5110b8 -- the table starts one record earlier (id 3 FlickerRate@0x1D8), every hud.hpp offset was shifted one slot; hud.hpp/CLASSMAP corrected, no behavioral impact (the reticle feeds via port globals). See GAUGE_COMPOSITE.md AUDIT finding B"
- "SeekVoltageGraph RECONSTRUCTED 2026-07-19 (Gitea #11, was #10 finding A): full widget landed (see §SeekVoltageGraph below) -- ghosts gone steady-state (BT_PRESET_HOLD verification); remaining polish: a 1-frame transition artifact when a BT_SHOT lands on the exact page-switch frame (label BecameActive vs the graph's next rated Execute -- same lag class as the binary; self-heals next frame)"
- "Secondary-view cycling RESOLVED 2026-07-19 (Gitea #6): the selector is the DISPLAY mode (CycleDisplayMode -> vtbl+0x4C override @4d1ae4), NOT CycleControlMode; desktop 'N' / pad RightThumb wired; pixel-verified dama->crit->heat"
- "Upper-MFD PRESET pages RESOLVED 2026-07-19 (Gitea #9): SetPresetMode table @0051dbf0 re-decoded (little-endian -> ModeMFD bits 0-14), per-MFD pod button banks identified from the .CTL dump, desktop J/K/L cycle wired; remaining: the always-active 0x28-0x2F / 0x1A-0x1D per-subsystem msg-4 records (subs 3-13 -- likely the condenser-valve/coolant buttons) unidentified"
- "Upper-MFD PRESET pages RESOLVED 2026-07-19 (Gitea #9): SetPresetMode table @0051dbf0 re-decoded (little-endian -> ModeMFD bits 0-14), per-MFD pod button banks identified from the .CTL dump, desktop J/K/L cycle wired"
- "Always-active msg-4 records IDENTIFIED 2026-07-20 (glass input audit): 0x2C = Reservoir InjectCoolant (the flush button), 0x2F/0x2E/0x2D/0x2B/0x2A/0x29 = Condenser1-6 MoveValve, 0x1A-0x1D = GeneratorA-D ToggleGeneratorOnOff (@0050fb90, unreconstructed); plus 0x13 = Mech DuckRequest (crouch), 0x28 = Mech BalanceCoolant, 0x12/0x14 = ThermalSight/Searchlight toggles -- see pod-hardware.md + docs/GLASS_COCKPIT.md 2026-07-20"
- "MP DEATHS resolved 2026-07-12 (observed-death tally + display clamp); remaining: verify multi-death tallies stay in sync across a long session (GAUGE_COMPOSITE.md)"
---
@@ -77,6 +78,35 @@ over one shared `SVGA16` pixelBuffer; `SVGA16::DrawDevSurface`). On the POD they
intact). The `overlay` port (SectorDisplay lives there) shares the `sec` physical surface via a
different bit-plane (0x00C0). [T2]
## MP gauge-window FREEZE = dangling bindings + permanent SEH disable (Gitea #12, 2026-07-19) [T2 log-convicted]
The live-MP "dev-gauges window froze entirely mid-session" incident (issue #12) is NOT a
swap-chain/present bug: `scratchpad/incident_2157/operator_1.log` shows a burst of
`[gauge-fault] '<Widget>' Execute FAULTED -> gauge DISABLED` (PilotList, HeadingPointer,
MapDisplay, SectorDisplay, TwoPartBars, NumericDisplayScalar …) exactly while ALL THREE mechs
were being RE-CREATED at mission launch (second `[zonebuild]`/`[cyl]` set at new addresses —
the session had a pre-launch host drop/rejoin; a normal 2-player LAN launch on the same build
did not re-stream and did not freeze). The gauge tree had been built (lazily) during the
LOBBY phase against the FIRST-stream mechs; the re-stream freed them; every gauge Execute then
AV'd on its dangling [[attribute-pointer]]s and the `BT_DEV_GAUGES` SEH guard
(`Gauge::GuardedExecute`, GAUGE.cpp:618) DISABLED each one — `Disable(True)` sets `rate=0`
PERMANENTLY, nothing re-enables or rebinds → the whole window static for the rest of the
session (= symptoms 1+3). The AUTHENTIC engine flow handles mission transitions by
`Application::Shutdown → gaugeRenderer->Shutdown() → ShutdownImplementation → Remove(0)` (all
gauges deleted, tree lazily rebuilt next mission, APP.cpp:787/GAUGREND.cpp:3264) — the port's
in-session re-stream path bypasses it. **FIX LANDED (2026-07-19, awaiting human MP verify):**
`BTL4GaugeRenderer::TearDownForViewpointRestream()` (btl4grnd.cpp) performs the ENTITY-BOUND
half of the engine ShutdownImplementation sequence, same order (RemoveAllAlarms → Remove(0) →
entity-grid Clear), deliberately KEEPING warehouse/graphics-ports/interpreter/controls-L4Lamps
(RemoveAllLamps mid-session would delete the CONTROLS-owned L4Lamps behind the buttonGroup's
`&lamp->automaticValue` registrations — a dangling mapping; the mech-state "lamps" like
AnimatedSubsystemLamp are Gauges and go with Remove(0)). Called from
`BTL4Application::MakeViewpointEntity` before `ConfigureForModel("Init", entity)` whenever the
viewpoint is a RE-make (that handler empirically runs once per stream — twice in the incident
log, `[ctrlmap] installing` ×2) — the tree then rebuilds bound to the NEW mech. Log sentinel:
`[gauge] viewpoint re-stream: tearing the gauge tree down for rebuild`. Residual exposure:
`Remove(0)` flushes each gauge (`Update(gaugeRate_A)`, GAUGREND.cpp:3465) before deleting it, so
gauge CONNECTIONS may read the freed mech once more (plain reads; Execute stays SEH-guarded).
## The secondary screen's THREE views (Damage / Critical / Heat) — mode-gated [T0/T1/T2]
The `sec` port stacks three mode-gated mech-schematic layers at offset (50,0) over the
always-on radar/heading/speed/messageBoard (`Secondary1`): **Damage** (`ModeSecondaryDamage`,
@@ -176,16 +206,18 @@ locked**. **RESOLVED 2026-07-12 [T2]:** deaths now tally per node from LOCALLY O
own VehicleDead path counts its node), and `BTPilotDeaths` clamps the 2/1 pre-acquire seed
to 0 for display. Own row counts correctly as before.
## Launcher-panel recharge dial — authentically STATIC [T1, writer census]
The weapon panels' SegmentArc270 tick ring reads MechWeapon::rechargeLevel (+0x320). A FULL
writer census of the binary (all decomp spellings: `0x320) =`, `+ 800) =`, `[200] =`) finds
exactly four writers in the weapon family: the ctor (@004b99a8, 1.0), the stream init
(@004b8fec), ResetToInitialState (@004b96ec, 1.0), and the ONE live writer —
`Emitter::ComputeOutputVoltage` @004ba738, reached only through the Emitter vtable. Projectile
weapons (MissileLauncher/autocannon) never write it: their dial draws full ONCE and never moves
on the pod. The launcher panel's live indicators are the AMMO DIGITS (AmmoBin::ammoCount via
the complete-type bridges, 2026-07-12 — the old raw bin+0x180/0x18c reads were layout garbage),
the jam/fire lamps and the eject wipe. Do NOT "fix" the static ring — it is the authentic look.
## Launcher-panel recharge dial — CORRECTION 2026-07-19: it IS live (slot 17 @004b9c9c) [T1]
The weapon panels' SegmentArc270 tick ring reads MechWeapon::rechargeLevel (+0x320).
**The old "authentically STATIC on projectile weapons" claim was WRONG** — the writer census
missed the anonymous vtable slot 17 (vtbl+0x44) body **@004b9c9c**:
`rechargeLevel = (rechargeRate@0x3DC recoil@0x3E8) / rechargeRate` (capstone disasm, issue
#12; Ghidra never emitted it). The recovered `ProjectileWeaponSimulation` @004bbd04 calls it
every frame in the Loading(3) and unavailable(7) alarm cases — so the launcher dial
authentically ANIMATES 0→1 through each reload. (The census's four other writers stand: ctor
@004b99a8, stream init @004b8fec, ResetToInitialState @004b96ec, and
`Emitter::ComputeOutputVoltage` @004ba738 — slot 17 is the Emitter override of the SAME slot;
@004b9c9c is the MechWeapon/projectile base body.) The launcher panel's other live indicators:
AMMO DIGITS (AmmoBin::ammoCount via the complete-type bridges), jam/fire lamps, eject wipe.
## ConfigMapGauge (the weapon panel's regroup lamp column) — authentically DORMANT
The per-weapon btjoy.pcc joystick image + 4 cm_* state lamps (off/other/only/both) showing,
@@ -329,14 +361,15 @@ and every instrument is now live [T2]:**
(the weapon-GROUP bits Front/Rear/Left/Right). **Range plays NO part** — Execute never reads
the stored TargetWithinRange slots. **FIX 2026-07-18 [T2]:** the pip's "loaded" was a port
approximation reading `rechargeLevel ≥ 1` — correct for emitters (charge-driven) but
**statically 1.0 on projectile weapons** (MissileLauncher/autocannon never write it — see
§Launcher-panel above), so missile/AC pips were **permanently lit and never blinked on fire**.
**statically 1.0 on projectile weapons in the PORT** (the port sim never called the slot-17
updater @004b9c9c — see the §Launcher-panel CORRECTION), so missile/AC pips were
**permanently lit and never blinked on fire**.
Now reads the AUTHENTIC attr 0x1C = the weaponAlarm StateIndicator level (`WeaponStatePtr`
`GaugeAlarm54::LevelPtr`), compared `== stateConst2` (the const the binary itself stores in
AddWeapon). The state cycles Loaded(2)↔Firing(0)/Loading(3)/Jammed(5) for BOTH families, so a
missile pip now momentarily drops when fired, exactly like the emitter's. Verified live
(Blackhawk, `BT_AUTOFIRE=1 BT_AF_MISSILE=1`): both SRM6 pips toggle 2↔0/5 on each salvo. The
authentically-static `rechargeLevel` recharge DIAL (§Launcher-panel) is untouched.
(Blackhawk, `BT_AUTOFIRE=1 BT_AF_MISSILE=1`): both SRM6 pips toggle 2↔0/5 on each salvo.
(The recharge DIAL is driven by slot 17 @004b9c9c — §Launcher-panel CORRECTION.)
- **Lock ring** = subB9 (ring+cross) at frame centre, **SPINNING 4°/frame** while the Lock attr
(0xA) is up. The Lock PRODUCER is the authentic HudSimulation rule (part_013.c:5619-5634 [T1],
wired task #38): lock requires a target AND your own HUD's host zone damage < **0.75** (a
+15
View File
@@ -70,6 +70,21 @@ copied from them.
- **Step 4 (Steam): CODE COMPLETE 2026-07-18 [T2 single-machine].** See [[steam-networking]] —
the wire seam + `L4STEAMNET` identity-token transport + `btl4lobby`. Live 2-account
cross-machine session pending (`docs/STEAM_TEST.md`).
- **Input-coverage audit + keyboard reconciliation: DONE 2026-07-20 [T2].** Root cause of the
dead panel: `MechRIOMapper`'s message ids were chained one enum too high (0x19-0x2a) — the
binary RIO table @0051dd30 re-registers the BASE aux/zoom ids 3..0x13 (Hotbox 0x1a); fixed
ungated + static_assert-locked (`btl4mppr.hpp`) → all 24 MFD-bank + 2 zoom buttons live
(52/72 panel buttons now work; 8 dead await handler reconstruction — Gitea backlog issue;
12 have no streamed mapping authored, authentically inert). **Keyboard = the ~20 core
gameplay actions on the CONTROLS.MAP keys; the PANEL covers every pod address by click**
(right-click = hold latch) — the merged default profile lives in `L4PADBINDINGS.cpp`
(auto-writes `content\bindings.txt`; W/S/A/D/Q/E/R/F/X drive, 1-4/Space/Ctrl fire, M/N/H/C =
pod buttons 0x18/0x15/0x2C/0x2F, `/V view toggle + J/K/L preset cycles hardcoded in PadRIO).
Bound keys are SUPPRESSED from the typed 1995 channel (`PadRIO::SuppressKey`, the btinput
pattern — ended the glass double-dispatch: 'w'=pilot-select, numpad/F-key key-up aliases);
unbound keys keep their authentic meanings (5/z presets, t-o pilot select, +/- zoom).
In BAS control mode the pod turns with the STICK (Q/E) and pedals are inactive — authentic;
A/D turn in MID/ADV. Full audit + census: `docs/GLASS_COCKPIT.md` §2026-07-20.
## Key Relationships
- Extends: [[pod-hardware]] (RIO input path) · Uses: [[gauges-hud]] dev-gauge modes ·
+48
View File
@@ -139,6 +139,17 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
(task #8 "landed" vs the kShotDamage=12 bring-up residue) — audit before reworking.
## Deferred subsystems / feeds (authentic path scoped, marked in code)
- **Glass panel dead-button backlog (input-coverage audit 2026-07-20) [T1 census].** 8 of the 72
panel buttons dispatch a streamed message with NO reconstructed handler (silently ignored):
0x13 → Mech `DuckRequest` @0049fa00 (CROUCH — `duckState` attr + SQUAT clips exist!), 0x28 →
Mech `BalanceCoolant` @0049f728, 0x1A-0x1D → Generator A-D `ToggleGeneratorOnOff` @004b1ed0,
0x12 → ThermalSight toggle (pvision unported), 0x14 → Searchlight `ToggleLamp` (body
reconstructed, handler-set unwired — note the 1995 latent bug below: even wired, the light
never turns on in the original). Plus page-gated Eng-page meanings: `ToggleCooling` @004ad6f8
(msg 3), `ToggleSeekVoltage` @004ba478/@004b8a48 (msg 0xb energy / 9 Myomers), `EjectAmmo`
@004bb9b8 (msg 0xb ammo weapons), and MechRIOMapper's own Keypress @004d2514 (id 0x19).
Filed as the Gitea "glass panel: unreconstructed button handlers backlog" issue; tables in
[[decomp-reference]] §2026-07-20; census in `docs/GLASS_COCKPIT.md`.
- **Searchlight-driven fog swap — DEFERRED, and it is an ORIGINAL 1995 LATENT BUG (task #63,
2026-07-13) [T1 decomp].** The arcade swaps fog between `fog=` (lights on) and `nosearchlightfog=`
(off) via `PullFogRenderable` watching the Searchlight's `lightState`. It never fires — in the PORT
@@ -631,6 +642,43 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
numbers no longer point at that code).
## Multiplayer (Phase 7 / P6)
- **Gitea #12 MP incident (2026-07-19) — root causes found; BOTH FIXES LANDED 2026-07-19
(awaiting the human MP death-and-survive verification).** Findings [T1/T2 — see the #12
comments + `scratchpad/incident_2157/`]:
(1) *Gauge-window freeze* = lobby-built gauge tree dangling over a mission-launch mech
RE-STREAM after a pre-launch host drop/rejoin; SEH guard disables each faulted gauge
permanently — see [[gauges-hud]] §MP gauge-window FREEZE. ✅ FIX LANDED:
`BTL4GaugeRenderer::TearDownForViewpointRestream()` (alarms + Remove(0) + entity grids — the
entity-bound half of the engine `ShutdownImplementation`), called from
`MakeViewpointEntity` before `ConfigureForModel` on a viewpoint RE-make; the tree rebuilds
bound to the new mech. Sentinel: `[gauge] viewpoint re-stream: tearing the gauge tree down`.
(2) *Missiles cycle-but-never-launch*: ✅ FIX LANDED — the port's RivetGun-approximation
ProjectileWeaponSimulation replaced with the disasm-exact @004bbd04 machine
([[decomp-reference]] §5; projweap.cpp): fault gate reads `simulationState@0x40==1`; the
`simulationFlags|=0x1` DelayWatchersFlag poisoning removed everywhere
([[reconstruction-gotchas]] §20 — emitter SetDirty split into ForceUpdate/ExecuteOnUpdate per
site); view/target gating + the `bin->FeedAmmo` pull + recoil set moved into the caller's
Loaded case with the authentic 4-blip stay-Loaded denial; recoil bleeds ONLY in Loading at
electrical-Ready and clamps at 0; slot-17 @004b9c9c called from Loading/7 (the launcher
recharge dial now ANIMATES). State 7 kept binary-faithful (re-asserted unconditionally —
permanent once entered; recovery only via ResetToInitialState/mission reset). Solo-verified
on 4.11.388+: 54-60 `[projectile] PUSH` + impact smoke; denial window (BT_LOOK_TEST
look-back) = trig pulses, state STAYS 2, zero launches, resumes on release; disabled-mech
gate pins enemy launchers at 7+full recoil; STREAK-6 dial sweeps across screenshots; lasers
fire throughout. Open fidelity question REMAINS: is launcher FailureHeat/jam-rate at
sustained autofire authentic-intensity? (both SRM6s jam within ~2 min of held autofire via
the bring-up CheckForJam heat gate; emitters never trip it — task #11 duty-cycle
measurement.)
(3) *J/K/L preset hypothesis REFUTED for solo* [T2]: BT_PRESET_TEST cycling on 387 — missiles
kept launching after presets (36 PUSH before / 24 after), pip count unchanged, gauges live,
[mode] masks touch ONLY ModeMFD bits 0-14 (`reticleElementMask` is a different object).
Corroborating: a 2-player LAN session on the same build (no J/K/L) had working missiles +
normal pips. UNTESTED: J/K/L during a live RELAY MP session.
(4) *"WAY MORE pips" (BLH, symptom 4)* — UNRESOLVED: the incident registered 10 pips
(8×3016 @750/500×5/225×2 + 2×3024 @800) which matches NO stock arsenal (solo dumps: bhk1=7,
madcat=6, thor=7); the reticle built identically TWICE (lobby + launch re-stream). Needs the
incident egg / a live 3-seat repro to identify the mech that owned those 10 weapons
(custom operator-egg loadout? viewpoint mis-assignment at build time?).
- **✅ Console-death peer-replication freeze — NOT REPRODUCIBLE + the one real bug FIXED
(2026-07-15) [T2].** The 2026-07-14 report (btconsole.py died mid-match → peer↔peer replication
froze both ways) did NOT reproduce in the current build under EITHER failure mode, tested directly
+17 -5
View File
@@ -42,11 +42,23 @@ preset-able MFD owns the 8-button bank AROUND it, mode-mask-gated per page — *
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, gen mode, weapon config/unjam). Always-active: **0x28-0x2F
(AuxUpperLeft) → per-subsystem msg 0x4 to roster subs 3-9** and secondary-panel 0x1A-0x1D →
subs 10-13 msg 0x4 (identities unverified — likely the condenser-valve / coolant buttons
[T4]); 0x10/0x11 = map ZoomIn/Out, 0x15 = CycleDisplayMode, 0x18 = CycleControlMode (#6).
Full decode: [[gauges-hud]] §preset pages.
(generator select A-D msg 4-7, gen mode msg 8, weapon configure msg 9; also msg 0x3 =
ToggleCooling and msg 0xb = ToggleSeekVoltage (energy) / EjectAmmo (ammo weapons) —
unreconstructed, silently dead). **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 — DEAD until
reconstructed). Newly decoded from the binary message tables: **0x13 → Mech `DuckRequest`
(0x1a @0049fa00 — the manual's CROUCH button)**, **0x28 → Mech `BalanceCoolant` (0x16
@0049f728)**, 0x12 → ThermalSight toggle (id 3), **0x14 → Searchlight + Searchlight2
`ToggleLamp` (id 3 @004b860c; body reconstructed but the handler-set is default-constructed
EMPTY — unwired)** — all currently dead; 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;
+28
View File
@@ -405,6 +405,34 @@ The name-keyed samplers (`PowerSourceConnection`/`GeneratorVoltageConnection`, w
---
## 20. Dirty-bit mis-mapping: `updateModel@0x18` (replication) vs `simulationFlags@0x28` bit 0 (DelayWatchersFlag)
**Symptom class (Gitea #12):** subsystem watchers (the AUDIO watchers — `ExecuteWatchers`,
SIMULATE.cpp:461) silently stop for a weapon after its FIRST shot; and any "flags == 1" state
test misfires. **Cause:** the binary's "needs replication" mark is
**`Simulation::updateModel@0x18 |= 1`** — in the disasm it reads `or word ptr [this+0x18], 1`
(i.e. "this+6" in dword-speak) and is EXACTLY what the engine `ForceUpdate()` does. Several
port reconstructions transcribed it onto **`simulationFlags@0x28 |= 0x1`** (`Emitter::SetDirty`
emitter.cpp:215, the MissileLauncher salvo mark mislanch.cpp:337) — but simulationFlags bit 0
is the engine **DelayWatchersFlag** [T0 SIMULATE.h:165]: once set (nothing clears it),
`PerformAndWatch` skips `ExecuteWatchers()` for that subsystem FOREVER. Bit 1 is
DontExecuteFlag; bits 2-3 the replicant-copy mask; bit 8 the master flag. A related misread:
the @004bbd04 fault gate tests **`simulationState@0x40 == 1`** (subsystem DESTROYED), which the
port transcribed as `simulationFlags == 1` — with the spurious `|=1` above, that arms a
permanent alarm-7 kill-switch for any launcher whose other flag bits are 0. **Rule:** a binary
`[this+0x18] |= 1` = `ForceUpdate()`; `[this+0x28]` = simulationFlags (engine-defined bits —
never invent meanings); `[this+0x40]` = MechSubsystem::simulationState (1 = destroyed). [T1]
**FIXED 2026-07-19 (Gitea #12, all sites disasm-verified):** `Emitter::SetDirty` retired — it
conflated `or word [this+0x18],1` (= ForceUpdate; FireWeapon tail @4bafaa, Reset tail @4ba55d)
with `or dword [this+0x28],2` (= ExecuteOnUpdate; ServiceDischarge @4ba99a/@4ba943) — each call
site now uses the correct engine call; the emitter fault gate `GetFlags()==1``simulationState
== 1` (@4baab9); `mislanch.cpp` salvo mark and `projweap.cpp` AC mark / ResetToInitialState
(@4bbb47) likewise. ⚠ One un-audited sibling remains: `btplayer.cpp:426` `simulationFlags|=0x1`
("request a forced update" — likely the same mis-transcription; verify against the
VehicleDead handler's disasm before changing). [T2]
---
## Diagnostic recipe (the standard loop)
1. Read the RAW decomp `reference/decomp/all/part_*.c` for the `FUN_xxxx`.
2. Map `FUN_`/`DAT_`/`this+0xNN` to engine symbols via BT headers + WinTesla MUNGA source +