# MECH.CPP / .HPP — reconstruction notes **Status: `Mech::Make` (factory) reconstructed; the Mech constructor and the entire mech subsystem are the CURRENT FRONTIER — the largest remaining milestone in the reconstruction.** ## What is reconstructed - `Mech::Make(MakeMessage *)` = `return new Mech(creation_message);` — the factory the registry / `MakeAndLinkViewpointEntity` calls. - The static shared data (ClassDerivations / DefaultData) — from the earlier staging. - A staged Mech ctor that chains the real `JointedMover` base ctor (so the model skeleton/segments stream from BTL4.RES) and then Fails. The spawn factory path is now validated live: `BTPlayer::CreatePlayerVehicle` builds the `Mech::MakeMessage`, `Application::MakeAndLinkViewpointEntity` routes it through the registry to `Mech::Make` -> `new Mech` -> the JointedMover base ctor (real engine code, streams the mech model) -> the Mech ctor Fail. Boot reaches `mech.cpp:66`. ## Why the Mech ctor is a milestone, not a brick `Mech::Mech` is `@004a1674`, **5690 bytes** — the largest single function in the game. It walks the model's segment table and instantiates the full subsystem roster (power, heat, weapons, actuators, controls, tech, damage zones), and primes ~150 members. Reconstructing it requires three things the tree does not yet have: 1. **The complete 1995 Mech member layout as named fields.** MECH.HPP currently carries `int reserved[331]` (1324 bytes) as a placeholder. BT411's Ghidra reconstruction sidesteps the layout entirely by writing raw offset pokes (`Wword(0x104)`, `*(void**)((char*)this + 0x388)`, `this[0x14a]`) — a WinTesla-port technique that CANNOT compile into a fresh 1995 build, where BC4.52 assigns offsets from the header declarations. So the layout must be derived as real members first. 2. **The subsystem class hierarchy.** MechSubsystem + derived: sensor, generator (gnrator), gyro, torso, HUD, myomers, plus the weapon subsystems (mechweap, ppc, gauss, projweap, mislanch, ammobin...). Of these, only GAUSS, PPC, SENSOR survive as source in the 4.10 archive (CODE/BT/BT); GNRATOR is a partial .TCP. The rest are part of the measured "917 missing functions" and come only from BT411's decomp. 3. **The segment-table walk** that reads the model resource and instantiates + wires each subsystem. BT411's mech reconstruction is ~12,255 lines across mech.cpp / mech2 / mech3 / mech4 / mechsub alone, before the subsystem TUs — this is the bulk of the remaining game. ## Reconstruction plan (for the dedicated mech milestone) 1. **Derive the Mech member layout.** Turn `reserved[331]` into named members. Sources: BT411's offset comments (`this[0x14a]` gyroSubsystem, `+0x388` target entity, etc.) give the binary offsets; the embedded member types (NameFilter, AlarmIndicator, Slot/Socket, Reticle, Filter, CString, StateIndicator) come from the surviving MUNGA headers. Cross-check total size against the binary's `new Mech` allocation. 2. **Reconstruct MechSubsystem base + the subsystem roster**, backdating BT411 mechsub.cpp + the per-subsystem TUs; fold in the surviving 4.10 GAUSS/PPC/ SENSOR source where it exists (authoritative over the decomp). 3. **Reconstruct the Mech ctor** against the named layout: embedded-member construction, the segment-table walk, subsystem instantiation + wiring. 4. Then the per-frame path (mech2 animation SM, mech3, mech4 locomotion/ targeting) and the damage/weapon handlers. Until then the ctor Fails cleanly at the frontier; everything below it (engine, app, network, mission, player, factory, jointed-mover base) runs real reconstructed code. ## PHASE 4 DONE + PHASE 5 FRONTIER (2026-07-20) **The Mech constructor WORKS.** Live boot: `new Mech` -> segment walk instantiates the full subsystem roster into the base Entity subsystemArray (dispatch on the real VDATA classIDs; unknown IDs -> base MechSubsystem so no NULL slots) -> caches sensor/gyro/sinkSource/hud -> mech links as viewpoint -> SetMappingSubsystem installs the MechControlsMapper in slot 0 -> mission startup. All structural. **Phase-5 frontier (the behavioral integration):** boot now crashes (real NULL deref, not a staged Fail) in `Simulation::GetAttributePointer` reached via `Simulation::PerformAndWatch` (the per-frame execute + watcher update). Root: the reconstructed subsystems reuse the base `Subsystem::AttributeIndex`, so they publish NONE of their real attributes (Sensor RadarPercent/SelfTest/BadVoltage, Generator OutputVoltage, Emitter ChargeLevel, HUD/Torso/Gyro readouts, ...). The watcher/gauge binding + per-frame PerformAndWatch resolve those attribute IDs and hit a bad/NULL activeAttributeIndex path. Phase-5 work, in order: 1. Per-subsystem AttributeIndex: define each subsystem's AttributePointers[] (ATTRIBUTE_ENTRY(class, Name, member)) + a real AttributeIndex chained to the parent, and pass it to that subsystem's DefaultData (instead of the base). The surviving CODE headers (SENSOR.HPP, PPC.HPP, GAUSS.HPP) list the attribute enums; the decomp AttributePointers[] tables give the rest. 2. Watcher / capability-chain / plug wiring in the Mech ctor (controllable/ heatable/weaponRoster/damageable) so PerformAndWatch has valid targets. 3. The per-frame simulation itself: mech2 (animation SM), mech3, mech4 (locomotion/targeting) -- ~9K lines, currently all staged Fail. 4. Rendering integration (the DPL renderer bridge) + playable verification. Everything through the Mech ctor + viewpoint link runs real reconstructed code. ## PHASE-5 CRASH LOCALIZED (2026-07-20) Trace-confirmed: the Mech ctor FULLY constructs on the real pod TEST.EGG mech -- `subsystemCount=33 weaponCount=7` (BT_MECH_LOG). The crash is AFTER construction, in BTL4Application::MakeViewpointEntity's "Load the control mappings" block (btl4app.cpp ~420): it SearchLists the ControlMappingsListResourceType resource and binds each streamed mapping to a subsystem attribute -- via an AttributeWatcher -> Simulation::GetAttributePointer(attributeID). The reconstructed subsystems publish NONE of their real attributes (they reuse the base Subsystem::AttributeIndex), so the streamed attribute IDs don't resolve. Phase-5 step 1 = per-subsystem AttributeIndex, and it's a precision task: - BT411 has the AttributePointers[] tables (mechsub 1, heat/HeatSink 12, powersub 4, Generator, sensor 3, torso 13, weapons, ...) -- but ATTRIBUTE_ENTRY(class,Name,member) needs each member NAMED + accessible (several of ours sit in reserved dynamics pads -- Gyroscope/Torso/HUD -- and must be broken out). - The attribute-ID enums chain Simulation::NextAttributeID -> Entity -> Mover -> ... -> subsystem, and MUST match the 4.10 binary IDs (the streamed control mappings reference them by number). Then: watcher/plug/capability-chain wiring, then the mech2/3/4 per-frame sim (~9K lines, staged), then rendering. This is the behavioral half. ## PHASE-5.1 CRASH SOLVED + BOOT REACHES THE GAME LOOP (2026-07-20) The `GetAttributePointer` fault was localized (per-slot probe over the roster) to roster **slot 22 = `PPC_1`**, an energy weapon: its `GetAttributePointer` read `entryCount` off a **NULL `activeAttributeIndex`**. Root cause: our reconstructed `EMITTER.HPP` DECLARES `static AttributeIndexSet AttributeIndex` (with a `ChargeLevelAttributeID`) but `EMITTER.CPP` never DEFINED it — and the surviving 4.10 `PPC.CPP` binds `PPC::DefaultData` to the inherited `PPC::AttributeIndex` (= `Emitter::AttributeIndex`), so the SharedData ctor's `activeAttributeIndex(&attribute_index)` captured a null/zero symbol. The surviving `GAUSS.CPP` also *chains* `Emitter::AttributeIndex` as its inheritance, so the same hole corrupts GaussRifle. (Sensor et al. never crashed: their `DefaultData` uses the defined `Subsystem::AttributeIndex`.) **Fix (EMITTER.CPP):** define `Emitter::AttributePointers[]` (one entry, `ATTRIBUTE_ENTRY(Emitter, ChargeLevel, chargeLevel)`) and `Emitter::AttributeIndex` chained to `Subsystem::AttributeIndex`, and point `Emitter::DefaultData` at it. This is the correct 1995 reconstruction (Emitter publishes the beam charge level for the HUD gauge; PPC binds it; Gauss chains it). Static-init order is satisfied by ORDER_bt (`... emitter ppc ... gauss`) with `subsystm` in munga.lib (constructs first). After the fix the per-slot probe walks all 33 slots (PPC_1/2, ERMLaser_1..3, AmmoBinSRM6_1/2, SRM6_1/2, MessageManager, MechTech, ...) cleanly, and the control-mapping binding block completes. **Result:** with the Emitter fix + BTPlayer::InitializePlayerLink + VehicleDeadMessageHandler(non-death), the reconstructed tree now boots with **zero `Fail()`** all the way into the live per-frame game loop (`LBE4ControlsManager::Execute` spinning). See BTPLAYER.NOTES.md. The remaining gap to "playable" is behavioral (mech2/3/4 per-frame sim + renderer + real control input), not structural — the boot path is complete. ## PHASE 5.3 INCREMENT 1: Mech::Simulate MOTION CORE (2026-07-21) `Mech::Simulate(Scalar)` is reconstructed and installed as the mech's per-frame Performance (`SetPerformance(&Mech::Simulate)` at the end of the ctor). Until now the mech ran the base `DoNothingOnce`; it now runs real body code every frame (dispatched via Mover→Entity→`Simulation::PerformAndWatch` once RunningMission). This increment is the **load-bearing spine** of the 1995 Simulate (mech4.cpp @004ab430): integrate the body velocity into `localOrigin` (`linearPosition.AddScaled(linearPosition, worldLinearVelocity, dt)`) and commit the Origin to the world transform with the engine's own idiom (`localToWorld = localOrigin`, per ENTITY.CPP:988 / MOVER.CPP:850). BT411's RE was decisive here: the mech's motion operates on the ENGINE BASE Mover fields (`localOrigin`/`projectedOrigin`/`previousOrigin`/`projectedVelocity`/ `localToWorld`) — the raw `this+0x100/0x260/0x26c` offsets in the WinTesla decomp actually *stomp* those base fields (their MechBaseLayoutCheck static_asserts). So the clean reconstruction uses the NAMED base members directly. **Verified headlessly** (BT_MECH_LOG `[sim] mech pos=` 1 Hz): with `BT_DRIVE="5,0,10"` the mech advances +5.0/s in x and +10.0/s in z (y fixed), exactly the injected world velocity; with no BT_DRIVE `worldLinearVelocity` is zero and the mech holds its spawn pose — no regression, zero Fail. `BT_DRIVE` is a retained DEV hook (world-velocity injection) for headless motion tests. **Deferred to the next increments (the locomotion wave):** 1. gait-cycle self-propulsion that FEEDS `worldLinearVelocity` — the authentic model derives forward speed from the walk/run animation cycle (`IntegrateMotion`→`AdvanceBodyAnimation`→cycleDistance) steered by the control-mapper throttle/turn demands, NOT a raw velocity; 2. heading integration (rotate `localOrigin.angularPosition` by the turn rate); 3. terrain-height drop (`BoundingBoxTreeNode::FindBoundingBoxUnder`) resting the feet on the ground; 4. cockpit telemetry `FilteredScalar`s (head/aim/leg/torso angular rates). ## PHASE 5.3 INCREMENT 2: DRIVABLE via authentic control interpretation (2026-07-21) The mech is now **drivable**: `Mech::Simulate` consumes the control-mapper demands and drives locomotion with the authentic model. Chain: `MechControlsMapper::InterpretControls` (roster slot 0, ticks before the mech's Performance each frame — see MECHMPPR.NOTES.md) reads the pushed raw inputs and publishes `speedDemand` (world u/s = topSpeed·throttle·fwdScale) and `turnDemand` ([-1..1]). `Mech::Simulate` then: - reads speedDemand/turnDemand from `subsystemArray[0]`; - accelerates `currentBodySpeed` toward the demand (bounded by `maxBodyAcceleration`); - computes the authentic per-mech turn rate `authTurnRate` = lerp(walkingTurnRate, runningTurnRate) by ground speed with a `runTR/t²` over-run falloff (mech4.cpp master-perf @0x4aa3d3); - integrates heading: `localOrigin.angularPosition.Add(prevPose, (0, turnDemand·authTurnRate·dt, 0))` — the engine rotation-integrate op; - takes the world −Z basis (`localToWorld.GetFromAxis(Z_Axis,…)`, negated) as the facing and sets `worldLinearVelocity = facing · currentBodySpeed`; - integrates position (the increment-1 core). New named Mech members (out of `reservedState`, now [211]): `walkingTurnRate`, `runningTurnRate`, `reverseStrideLength` (top speed), `walkStrideLength`, `reverseSpeedMax`, `forwardThrottleScale`, `maxBodyAcceleration`, `bodyTargetSpeed`, `currentBodySpeed`. **BRING-UP DEFAULTS** — the authentic values come from the Mech model resource (WalkingTurnRate/RunningTurnRate/ MaxAcceleration) + LoadLocomotionClips (stride/top speeds from the walk/run clips); wiring the model-resource pointer + clip loader is the next refinement. **VERIFIED headlessly** (`BT_MECH_LOG` `[sim]`/`[mppr]`; forced-input dev hooks `BT_FORCE_THROTTLE`/`BT_FORCE_TURN` in InterpretControls): - `throttle=1.0` → speedDemand=30, mech accelerates to 30 u/s and walks straight along its heading (per-second position delta magnitude = 30.1); - `throttle=0.3, turn=1.0` → spd=9, mech walks a CIRCLE of radius ≈ 10 = speed/turnRate (9 / 0.87 rad·s⁻¹) — heading integrates continuously; - neutral (RIO, no hardware) → speedDemand=0, mech holds pose; - zero Fail/Exception throughout. Still deferred: gait-clip-exact advance + leg animation (needs the animation subsystem + renderer), model-resource/LoadLocomotionClips sourcing, terrain drop, torso/free-look aiming (Torso/HUD analog axes), telemetry filters.