Files
BT411/context/decomp-reference.md
T
arcattackandClaude Opus 4.8 1cd57ade85 context-system: bootstrap the progressive-context expert knowledge base (foundation)
Adapt the spark-lesson / expert-seed.md progressive-context pattern to the bt411 port:
a thin router + context/*.md knowledge graph so the 2236-line CLAUDE.md's deep knowledge
loads on-demand instead of every session.

NON-DESTRUCTIVE foundation pass (live CLAUDE.md unchanged; proposed router is a draft):
- reference/glossary.yaml        -- ~45 terms (engine/formats/scene/reconstruction)
- context/decomp-reference.md    -- the quantitative hub: resource types, ClassID map,
                                    mech offsets, damage delivery, weapon constants, env gates, tools
- context/reconstruction-gotchas.md -- the 12 systemic bug classes (conventions/DO-NOT hub)
- context/bgf-format.md          -- geometry format + CONN/PCONN + LOD-sqrt3 + ramp shading
- context/gauges-hud.md          -- the gauge/MFD system (the just-completed wave)
- context/open-questions.md      -- deferred systems + get-from-Nick
- context/_ROUTER-DRAFT.md       -- the proposed slim CLAUDE.md (identity, protocols,
                                    quick-lookup, evidence tiers T0-T4, conventions, structure)
- phases/phase-01-context-restructure.md -- design + migration plan + status

docs/*.md ledgers stay as the DETAILED logs; context/*.md are the curated digests that
route into them. Remaining: ~10 topic files (project-overview, subsystems, combat-damage,
rendering, locomotion, wintesla-port, build-and-run, ...) then the CLAUDE.md swap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 22:06:53 -05:00

9.4 KiB

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 DEFERRED (NaN integrator; reverted to stub)
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 DEFERRED (WAVE 8; the valve/message control hub @mech+0x190)
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]

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 messageManager the 0xBD3 SubsystemMessageManager (damage guard / valve+message route source)
+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.

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. ⚠ Gates check getenv(name) != NULL, so BT_X="" (empty) still ENABLES; use Remove-Item Env:\BT_X.

Var Effect
BT_FORCE_THROTTLE auto-walk forward (no key)
BT_SPAWN_ENEMY spawn a target mech 120u ahead along the spawn facing
BT_FORCE_FIRE auto-fire on cooldown
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

Full render/locomotion gates (BT_RAMP, BT_MATPRI, BT_CULL, BT_SHADOW_*, BT_LODSEL, BT_ADDLOD, BT_PUNCH, …) are catalogued in rendering.


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