BT410 5.3.126: the enemy had no hands -- the lifecycle kill-cycle crash was a missing controls mapper, and the whole mission lifecycle is green again
The crash that has killed lifecycle2.conf since somewhere in 5.3.26..120 was never in the missile code. The map named Missile::MoveAndCollide because it resolves the nearest preceding symbol; the instruction bytes named the truth: mov eax,[ebx+0x128]; mov eax,[eax]; mov eax,[eax+0xFC] = mech->subsystemArray[0]->member through a NULL slot 0. Roster slot 0 is the CONTROLS MAPPER, and the BT_SPAWN_ENEMY dev enemy never got one -- SetMappingSubsystem is only called on the player's viewpoint mech. The binary never NULL-checks that slot (mech2's advancers, the master perf's duck evaluation and the effectiveness census all deref it bare, correctly: a real mech always has its mapper), so the enemy faulted the first frame it reached one of those reads. Nothing dereferenced a non-player mech's mapper before the gait arc, which is exactly why it lay hidden. The object was proven by the fault-address rule this project already learned: the faulting member offset MOVED 0xFC -> 0x100 across the 5.3.125 build, which added ejectLatch to MechControlsMapper -- a 4-byte layout shift in the very class being dereferenced. The dev enemy now installs a plain MechControlsMapper (inert without device input), preserving the binary's contract. VERIFIED end to end: kill -> respawn -> second kill -> OUT OF LIVES -> mission end -> clean DOS exit, zero exceptions -- the full lifecycle is green for the first time since 5.3.25. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,10 @@
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(MECHMPPR_HPP)
|
||||
# include <mechmppr.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(RESOURCE_HPP)
|
||||
# include <resource.hpp>
|
||||
#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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user