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:
Cyd
2026-08-10 00:13:29 -05:00
co-authored by Claude Fable 5
parent 7b57f6b059
commit e682f64acb
2 changed files with 59 additions and 21 deletions
+26
View File
@@ -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
+33 -21
View File
@@ -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.