Files
BT412/context/decomp-reference.md
T
arcattackandClaude Fable 5 092408041c Cockpit: the CONFIG-MODE weapon-regrouping session + the live-play fire fixes (tasks #6+#11)
TASK #6 -- the pod's in-cockpit weapon regrouping, fully reconstructed and
live-verified (hold-configure -> tap fire button -> toggle membership ->
release-commit):
- MechWeapon handlers id 9 ConfigureMappables @004b9550 / id 10 ChooseButton
  @004b95b8 (binary table @0x511860; the old "Myomers fns" mislabel swept)
  + the GetMessageHandlers() accessor chain through Emitter/ProjectileWeapon/
  MissileLauncher/GAUSS/PPC (empty per-class sets swallowed dispatch).
- Mapper vtable truth: +0x38 EnterConfiguration / +0x3C ExitConfiguration /
  +0x40,+0x44 AddOrErase evt/dir.  No "secondary vtable @0050f498", no
  "CreateTemporaryEventMappings" virtual (RP-name drift; swept incl CLASSMAP).
  L4 Enter/Exit rebuilt complete: StartMappableButtonsConfigure (the
  NonMapping 0x10000 <-> Mapping 0x8000 mode flip + the gauge's active-weapon
  latch) + the held-button re-arm + the 4 fire-button temp maps; only the RIO
  mapper implements the toggle (Thrustmaster no-ops = can't regroup).
- MechSubsystem +0xE8/+0xEC corrected to controlDestination/controlMessageID
  (ex hostEntity/subsystemId2 mislabel); MechWeapon ctor defaults the
  destination to &fireImpulse (@004b99a8).
- ConfigMapGauge state loop reconstructed (PE-recovered DAT_00518eb4 table;
  buttonGroup GetMapState sampler -- the "needs ModeManager" guard rationale
  was wrong).  Finding [T1]: the shipped binary NEVER enables this gauge (no
  SetColor caller) -- authentically dormant; BT_CONFIGMAP=1 is the dev enable.
- Dev harness: HOLD 'G' opens the session (BT_CONFIG_SLOT picks the weapon);
  BT_CONFIG_TEST scripts a headless verify.  @004afbc4 corrected to its real
  Fail-trap body (the guessed AddOrErase(NULL) body would corrupt groups).
- Bonus [T1]: the binary MechWeapon ATTRIBUTE table has ELEVEN entries
  (PercentDone..WeaponState) -- "TriggerState is the only one" was wrong.

TASK #11 -- the user-reported live-play regressions (both real bugs):
- PHANTOM FIRE: the mech4 bring-up shot block painted an explosion at the
  victim on its OWN 0.3s cadence whenever fire was held -- desynced once the
  authentic recharges landed.  Retired (the real impact visual flows from
  each discharge via the messmgr SubmitExplosion).
- WEAPON BRICKING (the "cuts out" bug): the Loading->Loaded snap window
  (+-0.01 around seekV) assumes the pod's LOCKED 60 fps; one port dt-spike
  jumps it, the byte-verified >1.0 clamp (_DAT_004ba830=0) zeroes readiness,
  and the weapon sticks in Loading at level ~10000 forever (observed live).
  Fixed with pod-frame (1/60s) sub-stepping of the binary's own Loading tick
  -- also fixes the I^2R integral over-heating generators on big steps.
  NEW GOTCHA CLASS recorded: reconstruction-gotchas #12 (frame-pacing trap).
- Duty-cycle measured (max-rate autofire): generators equilibrate in the
  degradation band (recharge stretch ~3.5-4.4x), the FailureHeat breaker
  never trips solo, PPCs sustain ~11.8-damage full-charge fire.  The slow
  kill pacing and recharge gaps are the AUTHENTIC heat economy; the pilot's
  counters (this regrouping UI, generator reassignment, coolant valves) are
  the reconstruction queue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:39:35 -05:00

19 KiB
Raw Blame History

id, title, status, source_sections, related_topics, key_terms, open_questions
id title status source_sections related_topics key_terms open_questions
decomp-reference Decomp Reference — resource types, ClassIDs, offsets, addresses, env gates established CLAUDE.md §10c; game/reconstructed/CLASSMAP.md; reference/decomp/all/part_*.c
subsystems
combat-damage
reconstruction-gotchas
build-and-run
gauges-hud
ClassID
resource-type
subsystem-roster
BTL4OPT
factory
0xBCE weapon family fully mapped? (GaussRifle : Emitter, FireWeapon is a no-op in this build)

Decomp Reference

The quantitative hub for the reconstruction: resource-type enum, ClassID map, mech struct offsets, key FUN_ addresses, and env gates. Cite a section here rather than re-deriving. Everything is [T1] (decompiled + byte-verified) unless tagged otherwise. The living source-of-truth is reference/decomp/all/part_*.c; the class map is game/reconstructed/CLASSMAP.md.


1. Resource lookup

ResourceFile::FindResourceDescription(name, type, index=-1) (FUN_00406ff8 = find-by-type-and-name; FUN_00407064 = SearchList(resourceID, type)). Lookup is by NAME or by ID. Mech::ResolveAnimationClip(prefix,suffix) builds a name and calls it.

⚠ A clip_id from ResolveAnimationClip is a DIRECT resource IDFindResourceDescription(clip_id), NOT SearchList(clip_id, 16) (which treats the arg as a LIST → walks garbage → AV). [T1]

Resource type enum

Type Value Holds
SkeletonStream .SKL skeletons
DamageZoneStream 0x14 damage-zone records (Mech ctor reads count + array)
ModelList 18 model/mech names (bhk1, madcat, …)
AnimationResourceType 16 (0x10) animation clips (lowercase: blhrrl=904, blhrrr=905, blhwwli=910, blhrrli=916)
damage-zone segment list 0x14 per-zone segment list
critical-subsystem segment 0x1e crit subsystem segments

Animation clip names in BTL4.RES are lowercase. [T1]


2. ClassID map (subsystem factory)

Base 3000 = 0xBB8. The factory switches on the resource ClassID; the case <Name>ClassID LABELS are systematically mislabeled — trust the // FUN_004xxxxx ctor-address comment reconciled via CLASSMAP.md, not the case label. [T1]

ClassID Real class ctor @ Notes
0xBB9 Mech @004a2d48 (Mech::Make) sizeof 0x854 (binary); reconstructed 0x638
0xBBD Condenser @4ae568 valve; case="?"
0xBBE AggregateHeatSink (heat-sink BANK) @4ae8d0 GUID 0x50e590, sizeof 0x1E4; case=SensorClassID
0xBC0 Reservoir @4af408 case=Condenser
0xBC1 Generator @4b… power bus
0xBC2 PoweredSubsystem @004b0f74 sizeof 0x31C
0xBC3 Sensor case=MyomersClassID
0xBC4 Gyroscope @004b3778 LIVE (task #56: byte-exact ctor/integrators/writers; hit-bounce fan-out wired)
0xBC5 Torso @004b6b0c sizeof 0x280; case=SinkSource
0xBC6 Myomers @4b8fec case=Actuator; INERT un-stub
0xBC8 Emitter @004b1d18 sizeof 0x478; energy weapon
0xBCB AmmoBin case=JumpJet; sizeof 0x22C
0xBCD ProjectileWeapon @4bc3fc sizeof 0x448 (case≠ this — see VDATA.h)
0xBCE GaussRifle @4bdcb4 : Emitter; FireWeapon is a no-op in this build
0xBD0 MissileLauncher @4bcff0 sizeof 0x44C
0xBD3 SubsystemMessageManager @0049bca4 damage/explosion consolidation hub (ConsolidateAndSendDamage, weaponExplosions); cached to Mech[0x10d]=0x434 (recon MISLABELS it controlsMapper; the drive squats there — the real mapper is roster slot 0). NOT the valve/Myomers gate (that's the owning BTPlayer @mech+0x190). WAVE 8 = untangle + damage-hub.
0xBD4 PPC (: Emitter) same FireWeapon as Emitter; factory calls it GaussRifle
0xBD6 HUD alloc 0x2a4; case=MechDisplay-ish
0xBD8 Searchlight case=LegSubsystem
0xBDC MechTech alloc 0x104
0xBDE ThermalSight case=MechDisplay

Identify a class at runtime with sub->GetClassID() (== sub+4). [T1]

Class hierarchy (the two branches that share only MechSubsystem)

  • Heat leaf: PoweredSubsystem : HeatSink : HeatableSubsystem : MechSubsystem (Emitter/PPC/Sensor/Myomers/weapons chain here). [T1]
  • Watcher branch: Torso/Gyroscope/Searchlight/ThermalSight/HUD : PowerWatcher : HeatWatcher : MechSubsystem; AmmoBin : HeatWatcher. Uses a 0xC connection + 0x54 alarm (NOT the 4/8-byte heat-leaf types). [T1]

Derivation-tag addresses (FUN_0041a1a4(obj, tag) = IsDerivedFrom)

Tag Class Evidence
0x511830 MechWeapon::ClassDerivations The reticle AddWeapon loop (part_014.c:5386) hard-aborts on missing WeaponRange/PipPosition/… attrs for every member; part_012.c:9344 counts ready weapons; the Mech ctor roster at this[0x1ef] (weaponRoster, ex-mislabel "poweredSubsystems") ORs weapon capabilityFlags@+0x334. [T1]
0x50f4bc PoweredSubsystem btl4gau2 gauge wave. [T1]
0x50e3ec HeatableSubsystem btl4gaug widget gate. [T1]
0x50e604 HeatSink btl4gaug widget gate. [T1]
0x51155c roster at Mech this[0x1eb] (heatableSubsystems label) tag ≠ 0x50e3ec — the label is UNVERIFIED [T4]; arbitrate before relying on it.
0x50e4fc roster at Mech this[499] (damageableSubsystems label) [T3]
0x50bdb4 Mech emitter.cpp destroyed-check. [T1]
0x5121a8 ProjectileWeapon-family (checked after 0x511830; reads ammo state @+0x364) [T3]

3. Mech struct offsets (Mech*, binary layout)

Reconstructed access MUST use named members/accessors — these raw offsets are the BINARY's, valid for reading the decomp, NOT for our compiled layout (see reconstruction-gotchas). [T1]

Offset Field Notes
+0x11c / +0x120 damageZoneCount / damageZones[] inherited Entity; Mech ctor populates the array
+0x124 / +0x128 subsystemCount / subsystemArray[] the subsystem-roster (NOT the segment table)
+0x190 owning BTPlayer (GetPlayerLink()) set by FUN_0049f624 (mech↔player bind; also player->playerVehicle(+0x1fc)=mech). The valve guard (FUN_004ac9c8 →player+0x274) + Myomers gate (FUN_004ad7d4 →player+0x260) read the owning player's game-mode flags (NOT the messmgr @0x434). MECH_OWNING_PLAYER.
+0x434 (word 0x10d) SubsystemMessageManager cache the 0xBD3 damage/explosion hub (factory @10142). Our recon MISLABELS this controlsMapper + the drive squats here; the real mapper is roster slot 0.
+0x260 / +0x26c localOrigin = { Point3D linearPosition; Quaternion angularPosition } ORIGIN.h:15; position@+0x100 in some subsystem views
+0x300 segment table skeleton EntitySegments (muzzle sites, joints)
+0x37c target world Point3D aim source
+0x388 target Entity* HasActiveTarget() gate
+0x38c targeted sub-zone (int) -1 = whole
+0x42c shadowJointNode (Joint*) ShadowJointName @model+0xB4
+0x438 torso subsystem cache HUD/mapper read it
+0x5b4 HUD subsystem cache (factory case 0xBD6 writes param_1[0x16d])
+0x650 deathAnimationLatched / crashed flag
+0x7cc damageableSubsystems chain the condenser chain (GUID 0x50e4fc) RecomputeCondenserValves iterates

mech+0x100 (a subsystem-view of localOrigin.linearPosition): .x@+0x100, .z@+0x108. [T1]


4. Damage delivery

  • Entity::TakeDamageMessage(id, size, inflictingEntityID, zone, Damage&)target->Dispatch(&msg).
  • Base Entity::TakeDamageMessageHandler IGNORES zone==-1 — send a VALID zone index (the Mech cylinder-lookup override for unaimed hits is not reconstructed). [T1]
  • class Damage { damageType(enum Collision/Ballistic/Explosive/Laser/Energy), damageAmount, damageForce, surfaceNormal, impactPoint, burstCount }. [T1]
  • Weapon effect id: "explode" = 13 (Explosion::Make). [T2]
  • DestroyEntityMessage(id,size) removes an entity — but a killed mech STAYS (a WRECK); death = a STATE transition (SetGraphicState(DestroyedGraphicState)), NOT removal. Issuing removal-on-death is the P5 teardown bug (do not). [T2]

5. Weapon fire-rate constants (recovered)

From the weapon .SUB records + the charge-curve .data constants (PE-parsed as x87 float10). [T1]

  • DischargeTime = beam-on time. ER-medium laser (BLH) = 0.2 s.
  • RechargeRate = recharge time in seconds exactly (the two ln5 factors cancel): ER-S 1.0 / ER-M 2.0 / ER-L 4.0 / PPC 5.0.
  • One BLH ER-M cycle = 0.2 + 2.0 = 2.2 s; ~12 lasers staggered → aggregate ≈ 5/s.
  • WeaponRange 500 (ER laser), DamageAmount 3.5, PipColor 1.0 0.0 0.0 (RED).
  • Charge constants: _DAT_004bb3c4=1.0, _DAT_004bb3b8=1e-4, _DAT_004bb3b4=0.5; RatedVoltage (GENRATOR.SUB)=1e4; fire threshold seekVoltage[rec]=0.8*RatedVoltage.
  • The electrical chain (task #10, LANDED) [T1]: ctor @004bb120 calibrates voltageScale@0x310 = (RechargeRate / -ln(1 - 1e-4*seekV[rec])) / EC; ChargeTimeScale @004b0d50 reads it back every tick x (1 + thermalResistivityCoefficient (res+0x100, emitters 0.0024) x max(0, srcT-srcT0)) -- hot generators charge slower; TrackSeekVoltage @004ba838 puts the charging I^2R (seekRate^2 x dtScale x dt) into the GENERATOR's pendingHeat@0x1c8 (~3.5e8/full PPC charge). ComputeOutputVoltage @004ba738 snaps rechargeLevel@0x320 to 1.0 within 0.01. FailureHeat consumers: this+0x184 (own heatAlarm level) == 2 -> emitter @004baa88 resets firing + holds charge 0; ballistic @004bbd36 pins recoil=rechargeRate + alarm 7. @004bbd04 (ProjectileWeaponSimulation) opens with call 0x4b0bd0 (the powered/heat step), like @004baa88. Cockpit power-routing handlers @004b099c/@004b09e4/@004b0a2c/@004b0a74 (bind generator by authored id 1-4 via @004b0b18) + @004b0abc (auto/manual, modeAlarm@0x2CC); attach/detach @004b0dd8/@004b0e30 (generator client capacity @0x1e4/count @0x1e8).

The CONFIG-MODE session chain (task #6) [T1]

  • MechWeapon handler table @0x511860: {9,"ConfigureMappables"→@004b9550}, {10,"ChooseButton"→@004b95b8} (strings @0x511994/@0x5119A7). PoweredSubsystem table @0x50F4EC: ids 4-8 = SelectGeneratorA-D / ToggleGeneratorMode → @004b099c/@004b09e4/@004b0a2c/@004b0a74/@004b0abc.
  • MechWeapon ATTRIBUTE table @0x511890: ELEVEN entries — 0x12 PercentDone@0x320, 0x13 TriggerState@0x31C, 0x14 DistanceToTarget@0x324, 0x15 TargetWithinRange@0x34C, 0x16 WeaponRange@0x328, 0x17 PipPosition, 0x18 PipColor, 0x19 PipExtendedRange, 0x1A EstimatedReadyTime@0x330, 0x1B RearFiring@0x334, 0x1C WeaponState@0x350.
  • Mapper vtables: base @0050F45C (+0x38 Enter Fail@004b0280 / +0x3C Exit Fail@004b029c / +0x40 AddOrErase-evt Fail@004b02b8 / +0x44 AddOrErase-dir Fail@004b02d4 / +0x48,+0x4C Notify @004b048c/@004b0494); L4 @0051E440 (+0x38=@004d1840, +0x3C=@004d18dc, +0x40/+0x44 = RET no-ops @004d195c/@004d1964, +0x48/+0x4C=@004d1acc/@004d1ae4); Thrustmaster @0051E3F0 (inherits the no-ops); RIO @0051E3A0 (+0x40=@004d25e8, +0x44=@004d262c). @004afbc4 = a pure Fail thunk ("Unhandled button mapping!", 0x7a).
  • ControlsManager vtbl: +0x20 StartMappableButtonsConfigure / +0x24 Stop / +0x28 AddOrEraseMappableButton (T0 CONTROLS.cpp:291-351). Group vtbl: +0x0C Remove, +0x10 Update, +0x14 Add(evt), +0x18 Add(dir), +0x1C AddOrErase(evt), +0x20 AddOrErase(dir), +0x24 GetMapState (unmapped=0/others=1/mine=2).
  • Mode masks (T0 BTL4MODE.HPP): ModeMapping=0x8000 (bit 15) / ModeNonMapping=0x10000 (bit 16), mutually exclusive. MechSubsystem +0xE8/+0xEC = controlDestination/controlMessageID (ctor pass-throughs; MechWeapon defaults destination=&fireImpulse@0x31C per @004b99a8). Subsystem+0x110: 0/-1 config-session flag written by handler 9 (reader unknown).
  • ConfigMapGauge @004c6d80/@004c6f1c: table DAT_00518eb4 = {y,btn} {0x0d,0x45}{0x25,0x46}{0x3d,0x40} {0x55,0x47}, blit x=0xc; SetColor @004c6ee0 has NO caller (gauge dormant in the shipped build).

6. Env-var gates (runtime; default OFF unless noted)

The authentic stack is DEFAULT-ON (BTEnvOn, mechrecon.hpp): BT_GAIT_CUTOVER, BT_GAIT_SM, BT_COLLISION, BT_REAL_CONTROLS default ON — set =0 to fall back. ⚠ Most BOOLEAN gates check getenv(name) != NULL or first-char != '0', so BT_X="" (empty) still ENABLES; use Remove-Item Env:\BT_X. Value-parsing gates are NOT empty-enabled: BT_AUTOFIRE and BT_KEY_NOFOCUS require '1', BT_GOTO requires a non-empty value, and BT_WARP_MIP is default-ON ('0' disables).

Var Effect
BT_FORCE_THROTTLE auto-walk forward (no key)
BT_SPAWN_ENEMY spawn a target mech 120u ahead along the spawn facing
BT_AUTOFIRE=1 hold the trigger (headless walk→fire→death harness; supersedes the dead BT_FORCE_FIRE — btl4main.cpp:310 set fireForced once at startup, but mech4.cpp:1567 unconditionally overwrites it every frame)
BT_ASSERT_TO_DEBUGGER route CRT asserts to the debugger, not a modal box
BT_HEAPCHECK whole-heap validation on every alloc/free (O(n²) at mission load — SLOW)
BT_PROBE_BGF=<name|ALL> boot-time BGF loader probe, exits after
BT_LOG=<file> per-instance log file (in content/)
BT_GAUGE_ATTR_LOG [attr] <binding> OK/NULL per config binding
BT_GAUGE_SKIP_LOG [gskip] <primitive> per unregistered gauge widget the parse skips
BT_VALVE_LOG condenser valve flow distribution
BT_SECTOR_LOG SectorDisplay Make/Execute
BT_NET_TRACE [net-tx]/[net-rx]/[net-upd] MP tracing
BT_DEV_GAUGES render the 6 pod MFD surfaces in a separate dev window
BT_AIM="x y" pin the reticle crosshair (reticle coords) — headless aim harness
BT_AIM_LOG [pick] ray/box/hit diagnostics (Mech::PickRayHit)
BT_FIRE_ARC=<deg> OPT-IN external-camera fire-arc clamp (unset = authentic no-arc)
BT_START_INSIDE begin in the cockpit view (V toggles)
BT_FORCE_MODEL=<name> force the player mech (madcat/owens/…; btl4mssn.cpp — per-mech cockpit bring-up)
BT_HIDE_COCKPIT hide the *_cop canopy shell (SHOWS by default — see cockpit-view)
BT_COP_* / BT_EYE_FWD=<f> cockpit canopy + eye diagnostics (FRAME (unlit frame colour override, default 0.13,0.12,0.15) / SINGLE (single-sided diag; double-sided is default) / PLATES=1 (disable the punch stencil-cut kit) / FLIP / DEBUG / DUMP; eye forward-push probe) — cockpit-view
BT_SHOT=<path.png> periodic backbuffer dump (every 90 frames) — non-disruptive visual verification
BT_TLOC_LOG translocation-warp lifecycle log ([tloc] warp COLLAPSE/EXPAND/phase=…)
BT_WARP_* translocation-warp visual tuning — all default to the verified values (see translocation-warp): EYE_UP=8.25 (on-axis), SPIN=4, TWIST=0, CONTRAST=1.0, LO_*/HI_* (lavender ramp), BLUR=0, TESS=3, MIP=1, CULL=cw, ANISO=0
BT_WARP_SELFTEST / BT_WARP_SELFSHOT=<prefix> DIAG (off by default): force a held warp in a solo game / dump backbuffer frames to disk (visual-verification harness)
BT_GYRO_LOG gyro bring-up log (gyro.cpp)
BT_GYRO_TRACE per-frame hit-bounce integrator trace
BT_CRIT_PROBE=<zone> hammer one own-mech zone every 4s (crit-propagation diag, task #2; mech4.cpp)
BT_REPL_LOG replicant/MP replication log (mech4.cpp)
BT_GOTO="enemy"|"x z" / BT_GOTO_LOG self-driving beeline (to the enemy or a map coordinate) + its log
BT_MP_LOG / BT_MP_NET / BT_MP_FORCE_DMG MP diagnostics / forced cross-pod damage
BT_AUTODRIVE=<0..1> headless throttle
BT_KEY_NOFOCUS=1 accept key input without window focus

Full render/locomotion gates (BT_RAMP, BT_MATPRI, BT_CULL, BT_SHADOW_*, BT_LODSEL, BT_ADDLOD, BT_PUNCH, …) are catalogued in rendering. Warp visuals: translocation-warp.

Translocation-warp addresses [T1]: POVTranslocateRenderable = L4VIDRND.cpp:1749 / .h:638; its per-frame Execute (spin about local Z + submit, NO texcoord/ramp work) = FUN_00453dc4 (reference/decomp/all/part_007.c), using axis-rotation helper FUN_0040998c (axis idx 2 = Z).

Gyro damage fan-out [T1, re-disassembled from raw bytes] (full story: cockpit-view): FUN_004b2980 (0x4b2980-0x4b2d8b, unexported gap) = Gyroscope::ApplyDamageResponse — args = (Gyroscope*, Damage BY VALUE 12 dwords), caller cleans 0x34; constants: dir-eps 1e-4, sign 0.5f (@0x4b2d84), clamp 1.3f (@0x4b2d88); vibrationDirection = gyro+0x390 (0,1,0). Call sites: the take-damage hub @0x4a02fb (hub = FUN_004a0230, gyro kick FIRST); crushable crunch @4aa81e (torque 0.4)/@4aa86c (up 0.2); gait jolt/rumble @4aa254/@4aa288/@4aa342 (DEFERRED [T3 gates]); firing recoil @4bc136-4bc19c (damage>3 → (0,0.6,1.5) × damage/16; gyroRumbleTimer = mech+0x5c4 float, ex-"clipLoadGuard"). Byte tooling: scratchpad/dis_4b2980.py / dis_range.py over reference/decomp/section_dump.txt.

PUNCH firmware decode [T1] (the definitive mechanism, 2026-07-11): cmd 0x20 = vr_damage_action (VR_PROT.H enum); the i860 firmware content/VREND.MNG (code @0xf0400000) handler 'damageize' @0xf040f6f8 writes the token triple onto the geogroup's first THREE geometry-list nodes' geometry_type@+8 IN FILE ORDER (superlist appends at tail @0xf040c620); DPL_PRIV.H names the roles: f_t_dam(0x92)/sca(0x05)/undamage(0x04)/f_t_undam(0x17)/ damage_set(0x03). Punchize triple = mask, hull, reset-twin -- the visible surface is the HULL with the MASK shapes stencil-cut through it. Disassembler: scratchpad/i860dis.py.

Cockpit-eye / punch addresses [T1] (full story: cockpit-view): eye ctor FUN_004579a8 (part_007.c:9274 = DPLEyeRenderable; offset = segment GetBaseOffset segment+0x74, parent = parent-segment DCS; caller part_014.c:5525-66 gates on "siteeyepoint"); matrix multiply FUN_0040b104(dst,A,B) = B × A (translation from B — operand-order trap!); euler→rotation FUN_0040ac6c; per-frame view FUN_004c22c4 (part_013.c:11742): eyeWorld from the live view-DCS then FUN_0040b244 = affine INVERSE → VIEW (+ uniform zoom) — NO LookAt; eye euler-change refresh FUN_00457b48 (part_007.c:9360). Punch: dpl_Punchize = FUN_00490308 (part_011.c:6219) → FUN_0048e5b0FUN_00492580(0x20,…) board send, constant tokens {0x80000003,0x80000017,0x80000004} (sibling dpl_Damagize FUN_004902b0 = same cmd, different triple); SV_SPECIAL PUNCH dispatch part_011.c:10209/10252 → callback LAB_004596ac. Respawn SIM path: DropZoneReply FUN_004bffd0Mech::Reset @0x4009fb74; CreatePlayerVehicle FUN_004bfcac. See translocation-warp, multiplayer.


7. Tools

  • tools/disas2.py <VA> [len] — capstone disassembly of BTL4OPT.EXE at a VA (recovers x87 math Ghidra drops; folds known call targets + float constants). THE tool for any FUN_ the assert-anchored exporter didn't capture (e.g. @0x4ae464 MoveValve, @0x4ca07c Execute).
  • tools/disas2.py-style PE reads: scratchpad/rdva.py (4-byte VA read), rdtbyte.py (80-bit float10).
  • reference/ghidra_scripts/ExportBTSource.java — the headless assert-anchored decompiler.
  • reference/ghidra_scripts/DecompVSS.java — headless address-list decompiler (any skipped fn).
  • tools/btconsole.py — the MP console emulator. tools/mapscan.py/resscan.py — content scanners.

Key Relationships