Fix "can't turn in MP": pilotArray[1] overrun stomped controlMode
User report from the first live 2-window session: throttle worked, A/D turn
did nothing. [mppr] showed the input REACHING the mapper (stickX=-1) but
turnDemand=0 and controlMode = pointer garbage (73583232 / 217478632 --
different per instance), from frame 1, MP only (solo mode=0).
Hunted with a cdb hardware write-breakpoint (ba w4 armed at ctor time on
&controlMode, compiled offset +0x128 from the [mode-ctor] probe). Caught the
writers red-handed: MechControlsMapper::BuildPilotArray / FillPilotArray.
ROOT CAUSE -- a shrunk-span array: the binary reserves a FIXED 10-slot pilot
block @0x15C..0x183 ((pilotArrayBuilt@0x184 - 0x15C)/4 = 10); the recon
declared `Pilot *pilotArray[1]` ("variable length"), so every write past
slot 0 stomps the members declared after it. In MP, FillPilotArray wrote the
PEER's Player pointer into slot 1 = over controlMode -> the turn shaping
dispatched on pointer garbage -> turnDemand 0. MASKED in solo: the zero-loop
overrun wrote 0 there, and 0 == BasicMode. This was ALSO the real cause of
task #48's "turnDemand zeroes out in -net" observation (worked around then
with the direct goto-turn; attribution corrected in the KB).
Fix: pilotArray sized to the binary's own 10-slot span; ctor zeroes all
slots; BuildPilotArray clamps pilotCount to capacity (local + 9 remotes; an
8-pod game fits). Diagnostics kept (BT_MODE_LOG ctor probe; [mppr] line now
prints mapper/&mode).
VERIFIED: MP session holds mode=0 throughout, turnDemand flows nonzero, the
mech turns (heading -1.33) and closes on the peer; solo kill chain intact.
KB: new "shrunk-span array" gotcha (reconstruction-gotchas 5) with the cdb
ba-w4 recipe; multiplayer.md task-#48 note corrected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d09cda6c36
commit
a110041a70
@@ -130,9 +130,12 @@ the drive being on). Fix: `BT_GOTO` now enables forced mode itself + supplies it
|
||||
holds and shoots instead of ramming). (2) The steering used the `gDriveHeading` SCALAR MIRROR (seeded to 0),
|
||||
but MP pilots spawn facing a non-zero heading, so the beeline steered the WRONG way. Fix: compute the heading
|
||||
error from the mech's ACTUAL forward (`localToWorld`'s -Z axis), signed angle to the target. (3) The goto turn
|
||||
was routed through `controlsMapper->turnDemand`, which zeroes out in -net (the mapper key-bridge only shapes
|
||||
the local viewpoint mech there) — the heading froze and A drove straight past B. Fix: apply the goto turn to
|
||||
the orientation integration DIRECTLY (`if(gBTGotoActive) turn=gBTGotoTurn`), after the mapper read.
|
||||
was routed through `controlsMapper->turnDemand`, which read 0 in -net — the heading froze and A drove straight
|
||||
past B. Fix: apply the goto turn to the orientation integration DIRECTLY (`if(gBTGotoActive) turn=gBTGotoTurn`),
|
||||
after the mapper read. **CAUSE FOUND LATER (task #51): turnDemand zeroed because `controlMode` was pointer
|
||||
garbage — the `pilotArray[1]` shrunk-span overrun (see [[reconstruction-gotchas]] §5), MP-only because solo's
|
||||
overrun wrote 0==BasicMode. Fixed at the root (10-slot array); the direct goto-turn stays (harmless, and it
|
||||
skips the 1-frame mapper latency). KEYBOARD turning in MP was restored by the root fix.**
|
||||
VERIFIED 2-node: A `BT_GOTO="enemy"` alone (no autodrive) drives `dist 1673→231 m`, holds at range, `mechPicks=59`,
|
||||
autofire → **B hdlr=192, DESTROYED** — a fully automated human-style cross-pod kill via the real beam path
|
||||
(not the FORCE_DMG hook). Solo un-regressed (plain autodrive + goto both work). `scratchpad/mp_kill_test.sh`;
|
||||
|
||||
@@ -90,6 +90,15 @@ The 33-agent RESOURCE_AUDIT fixed 8 such bugs (docs/RESOURCE_AUDIT.md). [T2]
|
||||
member (HeatSink `heatState@0x184` == `heatAlarm.GetLevel()`). → route to the accessor.
|
||||
- **Phantom field:** a member at an offset PAST the object (Generator `shortFlag@0x25C` is
|
||||
really `*(owner+0x190)+0x25c` the msg-manager). → remove; read the real source.
|
||||
- **Shrunk-span array:** a binary FIXED-SPAN block declared as `member[1]` ("variable length"
|
||||
comment) — every write past slot 0 stomps the members declared after it. Archetype:
|
||||
`MechControlsMapper::pilotArray` — the binary reserves 0x15C..0x183 (10 slots); the `[1]`
|
||||
declaration let MP's `FillPilotArray` write the PEER's `Player*` over `controlMode`
|
||||
("can't turn in MP": turn shaping dispatched on pointer garbage → `turnDemand=0`), MASKED
|
||||
in solo because the overrun wrote 0 == BasicMode. → size the array to the binary's
|
||||
inter-member span ((next-member offset − array offset) / stride) + clamp the fill loops.
|
||||
Caught live with cdb `ba w4` on the compiled member address (log `&member` from the ctor,
|
||||
offset delta gives the compiled position). [T2]
|
||||
`GaugeAlarm54` = 0x54 (the real `AlarmIndicator`; STATUS level at +0x14, so
|
||||
`subsystem+0x184 == heatAlarm+0x14 == GetLevel()`); `SubsystemConnection` = 0xC. [T1]
|
||||
|
||||
|
||||
@@ -1688,7 +1688,9 @@ void
|
||||
<< " stickX=" << controlsMapper->stickPosition.x
|
||||
<< " -> speedDemand=" << controlsMapper->speedDemand
|
||||
<< " turnDemand=" << controlsMapper->turnDemand
|
||||
<< " mode=" << controlsMapper->controlMode << "\n" << std::flush;
|
||||
<< " mode=" << controlsMapper->controlMode
|
||||
<< " mapper=" << (void*)controlsMapper
|
||||
<< " &mode=" << (void*)&controlsMapper->controlMode << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -342,6 +342,10 @@ MechControlsMapper::MechControlsMapper(
|
||||
controlMode = BasicMode;
|
||||
displayMode = 0;
|
||||
pilotArrayPage = 0;
|
||||
if (getenv("BT_MODE_LOG"))
|
||||
DEBUG_STREAM << "[mode-ctor] mapper=" << (void*)this
|
||||
<< " controlMode=" << (int)controlMode
|
||||
<< " &controlMode=" << (void*)&controlMode << "\n" << std::flush;
|
||||
|
||||
//
|
||||
// Recenter the torso articulation: current yaw/pitch <- neutral yaw/pitch.
|
||||
@@ -364,7 +368,8 @@ MechControlsMapper::MechControlsMapper(
|
||||
|
||||
pilotArrayBuilt = False;
|
||||
pilotIDs = 0;
|
||||
pilotArray[0] = 0;
|
||||
for (int pi = 0; pi < PilotArraySlots; ++pi)
|
||||
pilotArray[pi] = 0;
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
@@ -913,6 +918,13 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
// Capacity clamp: the binary reserves 10 slots (local + up to 9 remotes;
|
||||
// an 8-pod game fits). pilotCount beyond that would overrun the block in
|
||||
// the binary too -- clamp so the `<= pilotCount` zero-loop (which zeroes
|
||||
// the local slot 0 PLUS pilotCount remotes, faithful) stays in bounds.
|
||||
if (pilotCount > PilotArraySlots - 1)
|
||||
pilotCount = PilotArraySlots - 1;
|
||||
|
||||
pilotIDs = new int[pilotCount]; // FUN_004022b0
|
||||
|
||||
for (int i = 0; i <= pilotCount; ++i)
|
||||
|
||||
@@ -176,8 +176,17 @@ class Pilot;
|
||||
ControlsButton torsoCenter; // @0x154
|
||||
|
||||
int pilotArrayPage; // @0x158 (which "page" of pilots)
|
||||
Pilot *pilotArray[1]; // @0x15C [0]=local; [1..] remote
|
||||
// (variable length; pilotCount entries)
|
||||
// FIXED 10-SLOT block, the binary's own reservation: 0x15C..0x183 =
|
||||
// (pilotArrayBuilt@0x184 - 0x15C)/4 = 10 pointers ([0]=local, [1..9]
|
||||
// remote; an 8-pod game + the local fits). The old `[1]` "variable
|
||||
// length" declaration made EVERY write past slot 0 stomp the members
|
||||
// declared after it: in MP, FillPilotArray wrote the PEER's Player
|
||||
// pointer over controlMode (the turn shaping dispatched on pointer
|
||||
// garbage -> turnDemand 0 -> "I can't turn"); in solo the zero-loop
|
||||
// wrote 0 there, masked only because 0 == BasicMode. Caught live via
|
||||
// cdb `ba w4` on &controlMode (task #51).
|
||||
enum { PilotArraySlots = 10 };
|
||||
Pilot *pilotArray[PilotArraySlots]; // @0x15C..0x183
|
||||
|
||||
enum ControlMode {
|
||||
BasicMode = 0,
|
||||
|
||||
Reference in New Issue
Block a user