Installs Mech::Simulate as the mech's per-frame Performance (was DoNothingOnce). Reconstructs the load-bearing spine of the 1995 Simulate (mech4.cpp @004ab430): integrate the body velocity into localOrigin (linearPosition.AddScaled(.., worldLinearVelocity, dt)) and commit the Origin to the world transform with the engine idiom (localToWorld = localOrigin, per ENTITY.cpp:988 / MOVER.cpp:850). Uses the NAMED engine base Mover fields (localOrigin/localToWorld/ worldLinearVelocity) -- BT411's MechBaseLayoutCheck proved the WinTesla decomp's raw this+0x100/0x260 offsets actually stomp those base fields; the clean 1995 build addresses them by name. - MECH.HPP: Mech Performance typedef + SetPerformance + Simulate() decl. - MECH.CPP: SetPerformance(&Mech::Simulate) at ctor tail; Simulate body. Verified headlessly (BT_MECH_LOG "[sim] mech pos=" 1Hz): BT_DRIVE="5,0,10" advances the mech +5.0/s x, +10.0/s z (y fixed) = exactly the injected world velocity; no BT_DRIVE -> worldLinearVelocity is 0, mech holds spawn pose. Zero Fail/Exception either way. BT_DRIVE is a retained dev hook for headless motion. Deferred (locomotion wave): gait-cycle self-propulsion feeding worldLinearVelocity (IntegrateMotion->AdvanceBodyAnimation, steered by mapper throttle/turn), heading integration, terrain-height drop, cockpit telemetry filters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
187 lines
11 KiB
Markdown
187 lines
11 KiB
Markdown
# 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).
|