diff --git a/restoration/source410/BT/BTPLAYER.CPP b/restoration/source410/BT/BTPLAYER.CPP index 3cb91d39..8426c764 100644 --- a/restoration/source410/BT/BTPLAYER.CPP +++ b/restoration/source410/BT/BTPLAYER.CPP @@ -43,6 +43,10 @@ # include #endif +#if !defined(MECHMPPR_HPP) +# include +#endif + #if !defined(RESOURCE_HPP) # include #endif @@ -391,6 +395,28 @@ void Mech *enemy = Mech::Make(&create_enemy); Register_Object(enemy); + + // + // EVERY mech carries a controls mapper in roster slot 0 -- the + // binary never NULL-checks it (mech2's advancers, the master + // perf's duck/census reads all deref it bare). The dev enemy + // gets the plain base mapper (inert without device input) + // precisely to keep that contract: its absence WAS the + // lifecycle kill-cycle crash (subsystemArray[0]->speedDemand + // through NULL once the enemy started limping; the faulting + // member offset MOVED with a mapper-layout edit, proving the + // object). 0x7dc = MechControlsMapperClassID (BTL4MPPR.HPP:26 + // -- cited as a literal so the BT layer stays clear of BT_L4 + // includes). + // + enemy->SetMappingSubsystem( + new MechControlsMapper( + enemy, + 0, + "ControlsMapper", + (RegisteredClass::ClassID)0x7dc + ) + ); ((Mech *)playerVehicle)->SetTargetEntity(enemy); // // Mutual: the enemy targets us back -- with BT_FORCE_FIRE its diff --git a/restoration/source410/BT/MISSILE.NOTES.md b/restoration/source410/BT/MISSILE.NOTES.md index 3914a3e5..9b50fccb 100644 --- a/restoration/source410/BT/MISSILE.NOTES.md +++ b/restoration/source410/BT/MISSILE.NOTES.md @@ -92,24 +92,36 @@ app task, one delete per frame, not mid-walk). - Cluster SPLASH + explosion entity (ClassID 0x5C @004be078), world-geometry collision (@0042291c), target-velocity intercept lead, Missile WriteUpdateRecord (tag 0x78) for MP replication. - -## OPEN DEFECT (found 5.3.121, PRE-EXISTING): lifecycle kill-cycle crash - -`lifecycle2.conf` (KILLTEST.EGG, BT_SPAWN_ENEMY + BT_FORCE_FIRE + -BT_FORCE_ZONE=10) now dies ~seconds after the first SRM salvos launch, -BEFORE any [death]: `Exception 0E ... illegal address 000000FC`, faulting -instruction `mov eax,[eax+0xfc]` after `mov eax,[ebx+0x128]; mov eax,[eax]` -with ebx = THE ENEMY MECH (matches the [enemy] spawn address in OUT.TXT). -Map resolves the EIP into `Missile::MoveAndCollide` (0x4b1b54-base) -- the -seeker/thruster work is folded in there, but our LeadTarget NULL-guards -targetEntity, so the exact deref is not yet identified (mech+0x128 in OUR -layout -> a pointer whose first dword is NULL -> +0xfc). - -**PROVEN PRE-EXISTING by a control run**: the identical crash (same 0xFC -address, EIP shifted 0xCC by the relink) reproduces on a build with all -5.3.121 changes stashed. The rig last ran green at 5.3.25 -- the break -landed somewhere in the gait/renderer/duck arc (5.3.26..5.3.120) and was -never noticed because nothing re-ran the kill-cycle conf. Bisect hint: -the [msl] LAUNCH lines print, so the launch path is fine; the fault is in -the per-frame chase or the mutual-fire path against the enemy mech. -Two-agreeing-runs done (both builds crash identically, deterministic). + +## SOLVED (5.3.126): the lifecycle kill-cycle crash was a MISSING MAPPER + +Symptom: `lifecycle2.conf` died seconds after the SRM salvos with +`Exception 0E ... illegal address 000000FC`, EBX = the spawned enemy mech. +The map put the EIP inside `Missile::MoveAndCollide` -- **a red herring**: +the map resolves the nearest PRECEDING symbol, and the real faulting code +was a mech-side inline. + +The instruction settled it: `mov eax,[ebx+0x128]; mov eax,[eax]; +mov eax,[eax+0xFC]` = `mech->subsystemArray[0]->member` with +subsystemArray[0] NULL. **Roster slot 0 is the controls mapper, and the +BT_SPAWN_ENEMY dev enemy never got one** -- `SetMappingSubsystem` is +called only from BTL4APP's viewpoint-entity path, i.e. for the PLAYER's +mech. The binary never NULL-checks slot 0 (mech2's advancers, the master +perf's duck evaluation and the census all deref it bare, correctly: a +real mech always has its mapper), so the first frame the enemy reached +one of those reads, it faulted. + +CONFIRMING EVIDENCE (the fault-address rule, used properly this time): +the faulting member offset MOVED 0xFC -> 0x100 across the 5.3.125 build, +which added `ejectLatch` to MechControlsMapper -- a 4-byte layout shift +in exactly the class being dereferenced. That named the object beyond +doubt. + +Why it lay hidden since ~5.3.26: before the gait arc nothing dereferenced +a non-player mech's mapper. `Missile::MoveAndCollide` was never involved; +the missile flight path is exonerated. + +FIX: the dev enemy spawn now installs a plain `MechControlsMapper` (inert +without device input) in slot 0, preserving the binary's contract. +VERIFIED: full lifecycle green -- kill -> respawn -> second kill -> OUT OF +LIVES -> mission end -> clean DOS exit, zero exceptions.