Files
TeslaRel410/restoration/source410/BT/MECH4.NOTES.md
T
CydandClaude Fable 5 114ad9345e BT410 5.3.122: the pilot can punch out of the fire -- RealMaxSpeed, SetBurningState and ClearBurningState join the roster, and two more modes get names
Three of the six authentic Mech message handlers land, decomp-verified:

- RealMaxSpeed (@0049f604): adopt the streamed real-max-speed (+0x7a0,
  the 5-notch throttle quantizer base) only while our own is unknown
  (+0x7a4); wire payload at +0x1c, sender stamped by Entity's network
  send @0041f640.  The master-perf head's one-shot send stays staged
  with the EntityManager stream path.
- SetBurningState (@0049f674): the burning wreck parks in a per-host
  off-field slot -- x = localHostID x 10000, y = localHostID x -1000
  (data words @0049f6fc/@0049f6f8; the id is HostManager::
  GetLocalHostID, the same accessor the entity-instance dispatch
  compares against ownerID) -- and the mode alarm drops to 2: BURNING,
  the second InDeathTransition state, whose writer was the last missing
  piece of that predicate.  ForceUpdate(1) + AlwaysExecute (the
  binary's `flags &= ~2` IS DontExecuteFlag clearing).
- ClearBurningState (@0049f700): mode 0, same dirty/execute pair.

Decoded and recorded for the next sitting (MECH4.NOTES.md): BalanceCoolant
(@0049f728 mark + @0049f788 redistribute-with-gauge-blip; blocked on the
mech tail chain triple +0x7ac/+0x7bc/+0x7cc identity), and EjectPilot
(@0049f854, read end-to-end): the ejection gate is mech+0x414 -- the
combat-effectiveness census's consumer, found -- you can only punch out
of a CRIPPLED mech; ejection is mode 10 (why IsMechDestroyed tests >= 9)
plus a self-dispatched type-2 TakeDamage whose amount reads through the
mapper's +0x208 binding.

Soak: duck cycles green on the new build (7 volume swaps, crash-sig 0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 19:36:42 -05:00

320 lines
19 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.
# MECH4 — pre-TU reconstruction notes
**Status: no MECH4.CPP exists yet.** The ground snap, frame rejection,
knockdown stagger and collision economy that the binary keeps in mech4's
master performance are currently RE-HOSTED inside `Mech::Simulate`
([[MECH]], 5.3.98100) at seams marked STAGED. This file carries the decode
work for the pieces not yet written, so it survives session boundaries.
## Mech::ProcessCollision (@004abb40) — decoded, not yet reconstructed
The override of the engine's protected virtual (`MOVER.CPP:1354`), called
per contact by `Mover::ProcessCollisionList`. The decomp
(part_012.c:1520615416) reads as the BASE function plus four additions:
1. **The base head, verbatim**: `collisionVolume->ProcessCollision(collision,
worldLinearVelocity, lastCollisionList, &damage->surfaceNormal,
&penetration)`; on a miss NOTHING runs. Clamp penetration to the slice,
ratio = pen/dt, `damage->damageAmount = StaticBounce(old, dt, r, normal,
&elasticity, minimumBounceSpeed, &friction)` with the elasticity/friction
locals seeded from the mover's coefficients.
2. **The Mover-family branch** (owner = the collided solid's owner entity,
solid+0x1c; family test FUN_0041a1a4 against the "Mover" derivation GUID
@0x4e4518):
* SEPARATING-CONTACT GATE: `(ourWorldVel other->WorldVel[vtbl+0x20])
· normal < 1e-4` (`_DAT_004ac044`) → return, no damage, no dispatch.
* If the other's classID == **0xbb9** (mech), build a TakeDamage message
(raw wire shape `{id, len, inflictor = DAT_0050b9ac global, zone 1,
the Damage verbatim}`) and Dispatch it AT THE OTHER — the mech-vs-mech
ram. Use our `Entity::TakeDamageMessage` ctor as the weapon path does.
3. **The CulturalIcon branch** (GUID @0x4e7038):
* `stopping = other->instanceFlags & 0x8000` (the StoppingCollisionVolume
flag, entity+0x28).
* Same separating gate against a ZERO other-velocity (icons are static).
* Dispatch the crunch TakeDamage at the icon (crushable props have no
zones → their base handler no-ops it, but the dispatch is real).
* If NOT stopping: `damage->damageAmount = 0.00123f` (bits 0x3aa137f4) —
the WALK-THROUGH SENTINEL. The caller (the response block in Simulate)
must treat exactly 0.00123f as "the move stands": restore the post-snap
saves, zero the amount, feed the gyro crunch (torque 0.4 along the
normalized force, upward impulse 0.2 — binary @4aa81e/@4aa86c).
4. **The material tail**: the elasticity/friction OUT values from
StaticBounce are compared against `_DAT_004ac048`; sets
`collisionTemporaryState` (+0x44c) to 1, or 2 when both match and the
state was 0. The state is pushed into the `collisionState` StateIndicator
once per frame by the response block (0→1 fires the impact sound). Needs
the `_DAT_004ac048` constant identified before writing (likely the
default-material coefficient — check the literal pool at 0x4ac048).
## Two hazards the donor fought that DO NOT apply here
* **Compounded StaticBounce reflections**: our engine's
`Mover::ProcessCollisionList` (MOVER.CPP:1203) restores
`worldLinearVelocity = initial_velocity` and position PER CONTACT before
each ProcessCollision call, and averages the results. The port's
`frameEntryWorldVelocity` economy guard has no counterpart hazard in this
engine — do not import it.
* **The AlarmIndicator ODR split**: a port-only problem; our tree reads
`MovementMode()` / the alarms directly.
## Already re-hosted in Mech::Simulate (move here when mech4.cpp exists)
| piece | binary | landed |
|---|---|---|
| ground probe + snap | @4aa6304aa6cc | 5.3.98 |
| frame rejection + crash self-damage + knockdown | @4aa6cf4aab0b | 5.3.99 |
| collision economy divert + distributor | @0x4a0368 → @0049ffcc | 5.3.100 (in the damage hub, where the binary has it) |
| status→mode limp promotion | master perf (un-decompiled) | 5.3.97, STAGED |
| forwardCycleRate re-pick (ground/airborne) | IntegrateMotion head | partially — the ctor sets it once; the per-frame re-pick needs +0x3f4 |
## Still to decode
* `FUN_004a9b5c` — the master performance. **ISOLATED AND MAPPED
(2026-08-04):** one single 5,676-byte body @004a9b5c..@004ab168 (one
`ret`), hidden inside the manifest's mis-sized @004a9770 entry (that fn
is really the stream-builder's 6.6KB name→SharedData strcmp resolver,
and Ghidra swallowed everything to @004ab188 behind it). Registration
proven from the ctor decomp: `(flags & 0xC) == 4` → replicant perf,
else master @004a9b5c. Recipe: extract CODE bytes (va 0x1000 → file
0xe00), `ndisasm -b 32 -o 0x4a9b5c`. **5.3.121 corrections: the DECOMP
has both perfs in plain C (master = part_013.c:3059, and @004ab430 IS
the complete replicant perf — my "@004ab9d8 hidden inside it" note was
its CONSTANT POOL @004ab9c8.. right after the body). Replicant perf
read (not yet landed): zero +0x1dc, IntegrateMotion(dt, flavor 1);
ret 1 (fresher update present) → quaternion slerp +0x10c toward the
update target +0x26c with t = dt/(update age + dt) + matching velocity
lerps (+0x1d0 vs +0x2a4, +0x1f4 vs +0x298); either path applies the
+0x598 error-offset vector smoothed over the @004ab9cc window while
the +0x5a4 latch holds, then position ← the DR anchor +0x260; without
flags&0x4000 it re-resolves its room via the world chain (@0040e36c on
the +0x2e8 box) → +0x2f0; same ground clamp; `FUN_00421b6c`; same five
ring publishes + acceleration tail; NO update-record writer.**
**The ordered call skeleton (111 calls)** — blocks in execution order,
matching our Simulate's structure and locating every staged gap:
1. controls/demand intake (mech.cpp helpers 0x4a4eab/0x4a4ee9/0x4a4f9e)
2. three cerr+SetLevel triplets — illegal-state alarm guards
3. **THE DUCK DRIVER** — DECODED + LANDED 5.3.120, live-verified
(BT_FORCE_DUCK cycles down/hold/up continuously on the rig). The
full system: DuckRequest message 0x1A (authentic name from the
binary's handler table; roster = RealMaxSpeed 0x15, BalanceCoolant
0x16, SetBurningState 0x17, ClearBurningState 0x18, EjectPilot 0x19,
DuckRequest 0x1A) latches one evaluation (+0x398, one-shot, Reset
clears); the phase machine reads the RAW simulation state, the
mapper's duck cell (+0x25c) and the analog demand (+0x79c, deadzone
1e-4 @0x4ab16c): gait 0/4 + input -> down (LEG slot 2, sqd), gait 1
-> up (slot 3, squ); limp modes 3/4 force stand-up; the
collisionVolumeState alarm (+0x4c4; 1=standing, 0=ducked; "Whoa! Bad
Collision Volume State!" guard, line 417) swaps the collision
template's maxY (ExtentBox +0xc == the box TOP; the ground probe's
minY untouched) between the ctor-captured height and 0.6x it (the
double @004a2d38); duckState publishes for the cockpit selector.
Still open here: the two MarkUpdate masks (8 then 1) ride our
ForceUpdate no-op, and the mapper duck cell's streamed binding id
awaits a BT_MAP_LOG audit on a duck-bound RES.
4. verify-guarded state read (mech4 accessors + Fail 0x40385c)
5. gyro feed a (vec pair → gyro_b 0x4b2de4, gyro_a 0x4b2d8c)
6. death GUARD on the advance dispatch: InDeathTransition 0x49fb54;
0x4a71f4 = AdvanceLegAnimationGimp (modes 3/4 + 0x580), 0x4a5028 =
AdvanceLegAnimation -- NOT a death advancer (5.3.121 correction)
7. ~~status→mode LIMP PROMOTION~~ -> the STREAMED MODE-REQUEST COUNTERS
(see the 5.3.121 section; landed)
8. update-record block (0x421bac/0x40e5f0/0x4227e0/0x41db7c/0x422bac +
Point3D copies) — the dead-reckon/update writer
9. collision → gyro crunch (gyro feeds + alarms)
10. knockdown/bump arms (EntityID pair + two SetAnim/Mark/SetLevel arms)
11. IntegrateMotion @004ab1c8 + position dirty marks
12. pose/joint tail: gyro_d/gyro_c → **Torso::UpdateJoints @004b66b4** →
five StateIndicator::SetState publishes + four GetState reads
13. frame close (position latch)
**MarkUpdate = FUN_004a4c54 (×20!)** — the update-record dirty-mask OR
(death strips motion bits); our `ForceUpdate(int)` is an EMPTY STUB, so
none of these marks exist in our tree yet (MP replication feed).
Next sitting: instruction-level read of blocks 3 and 7 (the two STAGED
items), then the mark masks.
* `@004ab430` ReplicantPerformance (1432B) — the replicant-side interior.
* `@004ab1c8` IntegrateMotion — decoded (see 5.3.95 notes) but only
partially re-hosted: the local-velocity/orientation integration and the
dead-reckon latency fold (+0x778/+0x77c) remain. **`+0x598` is a Point3D**
(its replicant branch writes it with a vector subtract), so
`motionEventName` in MECH.HPP is mistyped as CString — fix when this
lands.
## 5.3.121 -- THE DECOMP HAS THE MASTER PERF (ledger correction + full C read)
**`part_013.c:3059` = `@004a9b5c file=bt/mech4.cpp` -- the ENTIRE master
performance is in the BT411 decomp as plain C.** Every "never decompiled /
raw-disasm-only" note above is superseded; the asm read (scratchpad
masterperf.asm) and the C agree line for line where both were done. Rule
for the rest of this file: read the C first, keep ndisasm for byte-level
checks. (`@004ab1c8` IntegrateMotion and `@004ab188` -- its dead-reckon
position fold -- are decompiled right below it; the replicant-perf interior
sits inside `@004ab430` from part_013:3804 with the SAME five ring
publishes at :3948.)
### The movement-mode carrier (the big one)
* **mech+0x2c is the entity MOVEMENT-MODE AlarmIndicator; +0x40 is its
LEVEL cell.** Everything we had read as "raw simulationState at +0x40"
is this alarm's level. It is entity-BASE machinery: the engine seeds it
from the class descriptor's state count (`@0041bdf0`,
`FUN_0041b9ec(entity+0x2c, *(desc+0xc))`).
* Mode roster so far: 0 DefaultState, 2/9 death transition (InDeathTransition
`@0049fb54` == 2||9 exactly), 3 GimpLeft, 4 GimpRight, 5-8 the four fall
variants (the body machine `@004a5678` head arms body clips 0x1c-0x1f from
them, one-shot latched by +0x650), 9 killed. WHO WRITES 2 and 5-8 is
still undecoded (the death FSM brick). Mode 1: seen only as "not
standing" in duck gates; identity open.
* **The damage path writes the mode DIRECTLY**: TakeDamage `@0049c690`
calls `FUN_0041bbd8(mech+0x2c, ...)` -- half-gone right leg -> 4
(`@0x49c8fb`), left -> 3 (`@0x49c926`), leg destroyed while already
limping -> 9 (`@0x49c88f`/`@0x49c8c5`), vital zone -> 9 (`@0x49c83c`),
gated by the REAL `InDeathTransition`. The old model (statusAlarm@0x714
as damage carrier + a Simulate "promotion") was a misconstruction --
RETIRED in 5.3.121; MECHDMG now writes the mode, and the @0x714 alarm is
ONLY the body-graphic clip selector (level = body clip index 0..0x20,
0x3c-stride name table at 0x50cfe8 "StandingAnimation"...).
* **Block 7 truth**: not a promotion. Three STREAMED request counters --
+0x334 DefaultState / +0x338 GimpLeft / +0x33c GimpRight (authentic
names, traced to the debug stream as consumed) -- re-assert the mode
alarm every frame they are positive. Writers: the creation-stream ctor
`@004b3778` (record +0x1c0/1c4/1c8) and the update-record appliers;
zeroed by ctor `@004a1674` and Reset `@0049fb74` (which also drops the
mode alarm to 0 and zeroes +0x328/+0x32c/+0x330 -- those three cells
still unidentified). LANDED with the authentic traces; inert until the
MP stream feed exists.
### Corrected field identities (from the C)
* **+0x79c = the MOBILITY SCALE**, not a duck analog: zeroed each frame,
then MAX of item+0x31c over the +0x7ac leg roster (count via the
iterator); throttle command (mapper+0x128) *= it; |scale| <= 1e-4 also
ZEROES the turn command (mapper+0x12c). The duck-down pick reads it as
the "legs still work" gate. Our tree HOLDS it at 1.0 ([T1]) until the
leg-roster scan is reconstructed.
* **+0x3f4 = the REVERSE/BRAKE flag** (the "airborne re-pick" note was
wrong): set when speedCmd < 0 while +0x348 (current speed) > 0, cleared
otherwise; +0x344 (the accel rate the body advancers slew by) picks
+0x5b8 forward / +0x5bc reverse accel (sentinel -1.1f @0x4ab174 = whole
system disabled; IntegrateMotion re-derives the pick at its head).
Entering reverse: MarkUpdate(0x100), gyro kick (0,0,+/-rand)@0.4 (50%
sign flip via 0x408050 rand + 0.5f compare @0x4ab170). While reversing
above the walk threshold (+0x534 < +0x348): pitch kick (0,1,0)@0.2 on a
0.4s countdown (+0x5c4) -- the braking shudder. NOT YET LANDED.
* +0x654 = the legResetLatch (saved at frame top, cleared, compared at
frame end -> MarkUpdate(8) on change). +0x584 = squat-capable,
+0x588 = turn-clip-capable, +0x580 = gimp-clip-capable.
* +0x574/+0x578 = walk/run turn rates; turn attenuation: speed >= +0x538
-> t=(speed-+0x534)/(+0x34c-+0x534), lerp A->B, t>1 -> B/t^2; clamp >=0;
x mapper turn cmd (+0x12c); ZERO while fallen (+0x650) or legState
1/2/3; result -> +0x1d4.
* +0x348 = current speed, +0x34c = max run speed, +0x350 = min(?) speed
clamp in the shortfall term, +0x7a0 = real max speed (5-notch throttle
quantizer: cmd = round(5*cmd/+0x7a0 + 0.5)/5 * +0x7a0... exact rounding
via ftol of +0.5f), +0x6b4/+0x6b8 = body-channel demand/speed pair.
* Perf head: `FUN_0049fa1c` = the combat-effectiveness census -> +0x414:
walks roster slots 2.., classID 0xBBE (generator) charge fraction
+0x12c/+0x128, 0xBC1 count with state != 1 && +0x210 != 4 (working
coolant units), RTTI-gated weapon count (excluding launchers with
+0x364 == 7); +0x414 = 1 when weapons < +0x448 min, no coolant, charge
below threshold, or limping with the duck cell 0. Consumer unknown.
* Perf head one-shot: +0x7a4 set && +0x7a8 clear -> sends the
**RealMaxSpeed message (0x15, 0x20 bytes)** carrying +0x7a0, then
latches +0x7a8 -- the network share of the computed real max speed
(pairs with the 0x15 handler @0049f604, still to land).
* Link plug at +0x418: first bound entity's distance -> +0x400, presence
-> +0x3fc (identity of the link: open).
* Mission-list y-compare -> +0x394 (the old "verify-guarded read" block):
compares *(+0x67c) against a mission-chain entry's position y when
legState not 0/1 -- identity open (in-water / off-pad flag?).
### Block 8 (move/collide) -- decoded, NOT yet landed
Ground clamp `FUN_0040e5f0(+0x2f0, pos)` (penetration -> pos.y and the
+0x2e8 box y/z); save old basis/pos; `FUN_004227e0(this,0)` volume;
+0x44c = 0; `FUN_00422bac(this, vol, dt, &oldpos, &probe)` = THE MOVE.
Outcomes on probe[1] (local_1c8): == sentinel @0x4ab180 -> restore
pos+basis, gyro kick along normal @0.4 + pitch @0.2, CollisionState alarm
(+0x450, 3 levels) -> 0; <= 0 -> alarm 0; else REAL HIT: partner
material *(partner+0x24) -> +0x4a4, world->body normal -> +0x4a8, closing
speed -(n . v) -> +0x4b4 (the CollisionMaterialType / CollisionNormal /
CollisionSpeed attribute cells), alarm <- +0x44c, STOP at old pos
(basis <- identity const @0x4e0f74), then **collision damage = a
SELF-DISPATCHED TakeDamage message (id 0x12, 100 bytes, damage type
DAT_0050b9ac, attacker = own entity id +0x184)** via vtbl+0xc; slow
(speed^2 <= @0x4ab184) -> legs to 0 + legResetLatch=1 (+ MarkUpdate(1|
0x40) if +0x464 == 1), fast -> stagger clip 0x20 + MarkUpdate(1|0x20).
After: +0x464 changed over frame -> MarkUpdate(0x80); vel.y = pos.y -
oldpos.y (raw delta); `FUN_00421b6c`; IntegrateMotion(dt, 0); dirty
thresholds (+0x768 pos-xz^2, +0x76c turn-delta, +0x770 heading-delta ->
MarkUpdate(0x10)/(1); mapper cmd != +0x6b4 -> MarkUpdate(4)).
### The tail (decoded, partially ours already)
Instability accumulator +0x3f0, THREE terms then clamp 1.0:
(|accelvec +0x82c| / +0x784 * +0x788)^2 + speed-shortfall
(clamped cmd vs +0x348, relative) * +0x78c + (legState==4 ? +0x798 : 0);
-> gyro+0x3a8 when gyro exists. Gyro churn `FUN_004b2eac(gyro, dt)` only
when legState != 0 && +0x57c == 0, then `FUN_004b34ec(gyro)` always --
both gated !fallen(+0x650). Torso::UpdateJoints(+0x438, &+0x360) gated
!fallen. `FUN_0049fe80(this)` (a second roster census -- damage-side;
decode parked). Five RING BUFFERS (FUN_0043ade4 = ring push, 0x43ae47/
0x43ae0b = ring reduces): +0x7e0<-vel.z, +0x7ec<-vel.y, +0x7f8<-vel.x,
+0x804<-turn rate, +0x810<-dt; smoothed values -> acceleration vector
+0x1dc (delta/sum-dt vs prevs +0x81c..+0x828) -> copied to +0x82c (the
instability input). `FUN_00421b2c` opens AND closes the frame (called
twice); the mobility iterator is destroyed at the very end
(`FUN_004a4ffb`).
### Landed in 5.3.121
MECHDMG kill/limp writes retargeted to the mode (SetSimulationState);
IsMechDestroyed reads the mode (>= 9); the staged promotion replaced by
the authentic counter block (+ ctor/Reset zeroes + Reset mode drop); the
duck driver moved to its authentic slot (counters -> duck -> limp standup
-> volume swap -> advance) and its gate renamed mobilityScale; the
advance dispatch death-guarded. SIDE EFFECT: `Mech::InDeathTransition`
and the camera director's cut-away are now LIVE (mode 9 is finally
written on kills) -- the director path needs a rig eyeball when a pod is
back.
## 5.3.122 -- the message roster fills (three of the six landed)
Landed: **RealMaxSpeed** (@0049f604: adopt streamed +0x7a0 while +0x7a4
clear; wire payload at +0x1c, sender EntityID stamped at +0xc by Entity's
network send @0041f640 = {stamp id, msg+0x14 <- entity+0x194, EntityManager
(app+0x30) send @00434ea0}), **SetBurningState** (@0049f674: park the wreck
at (localHostID x 10000, localHostID x -1000) -- constants @0049f6fc/f8,
host id = HostManager::GetLocalHostID, THE accessor the instance dispatch
compares against ownerID -- then MODE 2 = BURNING, the second
InDeathTransition state's missing writer, + ForceUpdate(1) +
AlwaysExecute), **ClearBurningState** (@0049f700: mode 0 + same pair).
simulationFlags bit 1 == DontExecuteFlag (SIMULATE.HPP) -- the binary's
`&= ~2` IS AlwaysExecute().
Still to land from the roster:
* **BalanceCoolant @0049f728** (decoded): if payload > 0, mark item+0x1d0=1
across the mech+0x7cc roster, then the redistribute pass @0049f788: total
the marks, per item fraction = mark/total (0.0 @0049f850 when empty),
blip the item's alarm at +0x1dc (level 2 if fraction <= stored else 1,
then 0 -- the gauge flash), store fraction at item+0x15c. BLOCKED ON:
identifying the mech tail chain triple +0x7ac (legs -- the mobility
scan) / +0x7bc / +0x7cc (coolant holders) against our capability chains
-- @004aefa4 walks all three plus the plug at +0x590 to collect the
coolant set, so decode that fn to pin them.
* **EjectPilot @0049f854** (head read): gated on payload > 0 AND
**mech+0x414** -- THE EFFECTIVENESS-CENSUS CONSUMER FOUND: you can only
eject a CRIPPLED mech (@0049fa1c's weapons/coolant/generator/limp
census) -- and !InDeathTransition; notifies via an app+0x2c accessor
(@00429078) + app+0x20 vcall (score/event, code 5, ownerID +0x18c);
sets **MODE 10 = EJECTED** (which is WHY IsMechDestroyed tests >= 9:
9 killed, 10 ejected); then builds a large effect/explosion struct --
tail still unread. Mode roster now: 0 default, 2 burning, 3/4 gimp
L/R, 5-8 falls, 9 killed, 10 ejected; writers of 5-8 still open.