Files
BT411/context/subsystems.md
T
0bcba26213 KB: land the EXPERIENCE-LEVELS decode from glass-cockpit (Cyd)
Brings Cyd's experience-level / simulation-mode research onto master (the
KB-decode portion of glass-cockpit 4e01e83; the bundled BT410 source
manifest is left for the full glass-cockpit merge).

- context/experience-levels.md (NEW): the egg per-pilot 'experience' field
  (novice/standard/veteran/expert) is the pod's SIMULATION-FIDELITY tier,
  decoded end-to-end. FUN_004c0bc8 reads btMission->experienceLevel(+0xe4)
  and fans it into the +0x25c/+0x260/+0x26c/+0x270/+0x274 flag block:
  +0x25c 'sim live' (novice lockout: jams/searchlight/powersub), +0x260 the
  HEAT-MODEL master switch (veteran+expert; FUN_004ad7d4), +0x274 raw level
  (FUN_004ac9c8 = ==0 novice predicate; the valve/advanced-cockpit lockout).
  4.0->4.10 drift: the viewscreen hunting-aid gate is GONE in 4.10 (HUD reads
  none of these flags), and movement heat is veteran+expert not expert-only.
- btplayer.cpp/.hpp: CORRECTED comments/labels -- the +0x25c..+0x274 block is
  seeded from btMission experienceLevel/advancedDamageOn, NOT the scenario
  role's returnFromDeath; the 'roleClassIndex/showKills' names are scoring-era
  mislabels. Code logic UNCHANGED (comment-only): the ctor still reads
  scenarioRole (STAND-IN, defaults 2 = veteran) pending the wiring task.
- Corrections swept into gauges-hud.md (ROOKIE->novice lockout),
  open-questions.md (player+0x260/0x274 semantics now PINNED),
  pod-hardware.md, subsystems.md, decomp-reference.md; CLAUDE.md router row.

No code-logic change -> no rebuild needed (comment + markdown only). The
runtime wiring (seed from BTMission::ExperienceLevel()) remains TODO.

Co-Authored-By: Cyd <cyd@falloutshelterarcade.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 23:20:51 -05:00

8.0 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
subsystems Subsystems — the factory + the reconstruction waves established PROGRESS_LOG.md §10d; docs/SUBSYS_PLAN.md; docs/VEHICLE_SUBSYSTEMS.md; docs/RESOURCE_AUDIT.md
decomp-reference
reconstruction-gotchas
combat-damage
gauges-hud
subsystem
factory
ClassID
subsystem-roster
PoweredSubsystem
bridge
Myomers coupling (gates on owning-player @mech+0x190); factory capability-roster loops 2-4 still dead (SubProxy stub)

Subsystems

The mech's components (heat/power/weapons/sensor/gyro/torso/myomers/…), built by the Mech-ctor factory. Full per-family plan: docs/SUBSYS_PLAN.md; the engineering-cluster panels: docs/VEHICLE_SUBSYSTEMS.md; the layout audit: docs/RESOURCE_AUDIT.md. ClassID map: decomp-reference §2.

The factory

The Mech ctor switches on the resource ClassID to build each subsystem into the subsystem-roster (subsystemArray@0x128). The case <Name>ClassID LABELS are systematically MISLABELED — the real class is the // FUN_004xxxxx ctor-address comment reconciled via CLASSMAP.md, not the case name (e.g. 0xBBE case="Sensor" is actually AggregateHeatSink). Each real class is wired via a Create<Class>Subsystem(Mech*,int,void*) bridge in the class's own .cpp (do NOT #include the real subsystem headers into mech.cpp — the local stubs collide). Keep the alloc SIZE + special-cache. [T2]

Class hierarchy (two branches, shared only at MechSubsystem)

  • Heat leaf: PoweredSubsystem : HeatSink : HeatableSubsystem : MechSubsystem. Emitter/PPC/ Sensor/Myomers/ProjectileWeapon/MissileLauncher/GaussRifle chain here. Uses 0xC SubsystemConnection
    • 0x54 GaugeAlarm54 (alarm level at +0x14). [T1]
  • Watcher branch: Torso/Gyroscope/Searchlight/ThermalSight/HUD : PowerWatcher : HeatWatcher : MechSubsystem; AmmoBin : HeatWatcher. Uses a 0xC WatchedConnection + 0x54 WatcherGaugeAlarm. [T1] Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER in one build, each static_assert-locked (the P7 alarm-unification: HeatSink 0x1D0 → PoweredSubsystem 0x31C → MechWeapon 0x3F0 → Emitter 0x478). [T2]

Reconstruction waves (state: ALL 20 factory cases wired to real classes — 0xBD3 = the real SubsystemMessageManager since task #7, 2026-07-11; the mapper lives in roster slot 0)

  • WAVE 1 — HeatableSubsystem re-based onto MechSubsystem (de-shadow cascade).
  • WAVE 2 — heat family (Condenser/AggregateHeatSink/Reservoir) + HUD + MechTech (un-swap).
  • WAVE 3 — power bus (Generator/PoweredSubsystem) + Emitter/PPC fire-path (end-to-end fire; heat conducts to the central sink via the linked-sink roster).
  • WAVE 4 — standalone readouts: Sensor/Searchlight/ThermalSight/AmmoBin (de-shim, gate fixes).
    • Searchlight has an ORIGINAL latent bug [T1, task #63]: SearchlightSimulation (@004b841c) reads requestedOn@0x1E0 (never written) while ToggleLamp (@004b860c) toggles commandedOn@0x1DC — no bridge, so lightState@0x1D8 is perpetually 0 (lamp never lights). Present in the 1995 binary (sibling ThermalSight reads the field it toggles, 0x1DC); the port reproduces it faithfully. Left as-is by decision; it makes the searchlight→fog swap inert. See open-questions + rendering fog.
  • WAVE 5 — Torso (aim twist/elevation; joint-I/O reconstructed; the BLH record disables torso → faithfully no visible twist).
  • WAVE 6 — Myomers (mover-coupled; structural un-stub is INERT — no live mover/heat coupling).
  • WAVE 7 — projectile/missile weapons (byte-exact; flying projectiles are a PORT reconstruction — the 2007 Entity is 0x1BC vs the binary's 0x300, so raw base-offset reads fail).
  • TASK #56 (2026-07-11) — Gyroscope LIVE byte-exact (ctor @004b3778, sizeof 0x3D0 locked; integrators + FUN_004b2980 damage fan-out; joint dispatch from the Mech performance tail @0x4aaf74/83; BT_GYRO_LOG/BT_GYRO_TRACE) — see cockpit-view.
  • DONE (task #7, afefaee) — SubsystemMessageManager 0xBD3 (a damage/explosion consolidation hub cached to Mech[0x10d]=0x434; the factory builds the REAL class and mech.hpp names the slot messageManager — the old controlsMapper mislabel is swept, the real mapper is roster slot 0. NOT the valve/heat-model gate — that's the owning BTPlayer @mech+0x190 carrying the EXPERIENCE-level flags, see experience-levels). [T2]

The watcher electrical chain (task #57, 2026-07-13 — the torso power gate)

The Watcher branch is POWERED indirectly: a watcher WATCHES another roster subsystem and mirrors its electrical state. The full chain, byte-verified [T1]:

  • Data: the model entry WatchedSubsystem=<name> → segment index +2 at resource+0xE4 (HeatWatcher::CreateStreamedSubsystem @004aec54); the ctor @004aeb40 stores it at watcher+0x128 (watchedSubsystem).
  • Bind (factory post-roster loop 1): vtable slot 14 (+0x38) — @004aee2c (HeatWatcher) / @004b1a40 (PowerWatcher/Torso override, byte-identical); Ghidra missed both function starts (recovered from raw bytes; the vtables.tsv rows have GAPS where the exporter skipped slots — dump the exe bytes at vtable+slot*4 when a slot looks missing). Master-gated ((owner->simulationFlags & 0xC)==0 && (flags & 0x100)); binds watchedLink(+0x114).Add( owner->roster[+0x128][watchedSubsystem]). Port: BTWatcherWatchedIndex/BTWatcherBindTarget bridges (heatfamily_reslice.cpp) called from the mech.cpp factory loop.
  • Tick: @004b181c = the REAL PowerWatcher Performance (PTR @0050f5fc → 004b181c) = UpdateWatch(): heat mirror (FUN_004aeac4) + watchdogAlarm.SetLevel(watched->electrical level @+0x278) + brownout downgrade to 1 when gen->outputVoltage(+0x1DC) <= minVoltage(+0x180) × gen->ratedVoltage(+0x1D8). The Torso sims (@004b5cf0/@004b65f8) call it first-line. (@004b1804 is slot-10 ResetToInitialState, NOT the Simulation — old recon mislabel, fixed.)
  • MinVoltageScale = 0.01 — a 10-byte x87 literal at 0x4b1924 (0a d7 a3 70 3d 0a d7 a3 f8 3f); the port had 1.0f, making minVoltage 100× too big → the brownout latched every watchdog at 1.
  • PowerWatcher::GetClassDerivations chains HeatWatcher (real base) — the old HeatableSubsystem stand-in broke IsDerivedFrom(HeatWatcher) for Torso/Searchlight/ThermalSight and silently skipped them in the connect pass.
  • Effect: Torso::ElectricalStateLevel()==Ready(4) un-gates effectiveTwistRate — the MadCat torso twists at its authored 50°/s (±140° limits, roster 17 watching 15 → generator @10000V); the BLH is authentically fixed (horizontalEnabled=0, limits ±0.01°). [T2 live-verified]
  • STILL DEAD: factory loops 2-4 (heatable/weapon/damageable capability rosters) go through the SubProxy stub whose IsDerivedFrom returns 0 — they add NOTHING. See open-questions.

The four systemic checks (every subsystem)

See reconstruction-gotchas: (1) shadowing (re-declared engine-base fields), (2) the Wword trap, (3) message-handler chaining, (4) entity validity. Plus resource-struct layout (must mirror the class hierarchy, byte-exact — the RESOURCE_AUDIT found 8 bugs) and object-layout (alias/phantom/interior fields). A +0x128-style owner offset is the ROSTER, not the segment table (the heat-link type confusion that caused a heap-corruption via OOB ConductHeat writes). [T2]

Engineering cluster panels (vehicleSubSystems)

Not a widget — a per-subsystem FACTORY (FUN_004cbaf0) building a status CLUSTER onto one of 12 aux MFD positions, dispatching on classID → HeatSinkCluster/MyomerCluster/EnergyWeaponCluster/ BallisticWeaponCluster. Reads PoweredSubsystem::auxScreenNumber/Placement/Label (res +0x104/8/C) via the BTGetSubsystemAuxScreen bridge. See docs/VEHICLE_SUBSYSTEMS.md + gauges-hud. [T2]

Key Relationships