Files
TeslaRel410/restoration/source410/BT/MECH.NOTES.md
T
CydandClaude Fable 5 ebf0d8c23f BT410 Phase 5.3.2: DRIVABLE mech -- authentic control interpretation + locomotion
The mech now drives from the authentic control chain (faithful route). Verified
headlessly: full throttle -> walks straight at top speed (30 u/s) along heading;
throttle+turn -> walks a circle (radius = speed/turnRate); neutral -> holds pose;
zero Fail.

MechControlsMapper::InterpretControls (mechmppr.cpp @004afd10) -- installed as the
mapper's per-frame Performance (roster slot 0, ticks before the mech):
- speedDemand = topSpeed*throttle*fwdScale (reverse inverts); soft stick (square)
  / pedal (cube) response; Basic=stick turn, Std/Vet=pedal turn; speed clamped
  while turning hard. Reads owner stride via Mech accessors.
- BT_FORCE_THROTTLE/BT_FORCE_TURN dev hooks for headless verification.

Mech::Simulate drive -- consumes the mapper demands:
- accelerate currentBodySpeed toward speedDemand (maxBodyAcceleration);
- authTurnRate = lerp(walkingTurnRate, runningTurnRate) by ground speed + over-run
  falloff (mech4.cpp master-perf @0x4aa3d3);
- integrate heading via Quaternion::Add(prevPose, (0,turn*rate*dt,0));
- facing = world -Z basis (GetFromAxis(Z_Axis)); worldLinearVelocity = facing*spd;
- integrate position (increment-1 core).

MECH.HPP: 9 named locomotion members out of reservedState (now [211]) --
walking/runningTurnRate, reverse/walkStrideLength, reverseSpeedMax,
forwardThrottleScale, maxBodyAcceleration, body/currentBodySpeed. BRING-UP
DEFAULTS (authentic values come from the model resource + LoadLocomotionClips --
next refinement).

Deferred: gait-clip-exact advance + leg anim (needs animation subsystem/renderer),
model-resource sourcing, terrain drop, torso/free-look aim, telemetry filters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:46:30 -05:00

228 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.