The interactive 2-node playtest wave -- every fix decomp-grounded and live-verified: COLLISION ECONOMY (the ram one-shot): StaticBounce mutates worldLinearVelocity per contact and ProcessCollisionList walks EVERY touched solid per frame; with 2007 terrain-as-solids the reflections compounded x4-x40 within one frame and a walking bump one-shot a pristine mech for 112,375 pts (62-pt authentic economy). Fix: frameEntryWorldVelocity restore per contact (damage always priced at the real approach speed -- all the binary's physics ever saw); Mech::Reset zeroes the mover motion (respawn = teleport); [collide-tx]/[mp-hdlr] telemetry. Gotcha #16 (engine-facility drift class). MISSILES: peer-visible salvos (the launcher record extension carries a salvo counter + aim point; ForceUpdate actually enqueues it -- the dirty flag alone never serialized), the authentic arc (authored MuzzleVelocity vector + the Seeker's 200m/0.1/300 loft + gain-4 steering, decoded from @004beae4/@004bef78), world-impact bursts (rounds detonate on cave geometry instead of phasing through), contact-only damage (flight-cap expiry = fizzle, no more teleport damage), live re-lead, and ballistic (unguided) shells for autocannons. projweap's stale BTPushProjectile extern (the /FORCE signature trap, gotcha #6 corollary) crashed the Avatar's first AFC100 shot -- fixed + sweep rule recorded. RADAR: two transcription bugs made the scope permanently empty -- FUN_0040b244 is the affine INVERSE (not a copy) and FUN_0040adec writes ONLY the 3x3 rotation (never the translation); worldToView now Invert(view) built rotation-first. CulturalIcons sorted out of the moving grid (the phantom red pips were map props), visible-radius culls on all three draw passes, live pip verified at |delta| x ppm px. Gotcha #17 (verify the FUN_ body, not its call shape). WEAPON PANELS (the frozen-dial hunt): the binary's *(subsystem+0x40) means FAILED -- the recon's 'operating' name was backwards, inverting the destroyed-X lamps, the panel look, the children enable and the ready-lamp gate (which had NEVER executed). Polarity chain corrected end-to-end (failedState, fed by real damage saturation). Root cause of the freezes: MFD page-mode gating -- the dev composite shows ALL pages at once, so off-page dials legitimately stopped; under BT_DEV_GAUGES the 15 page-plane bits stay active (the exclusive secondary trio untouched). The SEH gauge guard now names its kills; repaint-heal resets the incremental arc after panel repaints; [panel]/[arc] probes added. COMM/SCORE: MessageBoard LIVE (the engine already shipped the whole Player__StatusMessage queue; wired the binary's one producer -- the kill branch, victim's name, 6s -- plus the consumer bridge and a lazy source bind); MP DEATHS counted via the observed-death tally (each node scores every pilot from locally observed events, the same model as the KILLS credit) and the -2/-1 engine seed clamped for display. DEV UX: node-tagged window titles (-net port), gauge panel reworked (1320x480, true 4:3 MFD cells, the portrait secondary UNROTATED upright, linear filtering, BT_GAUGE_SCALE), fixed close spawns via BT_SPAWN_XZ, Boreas flies an Avatar (first second-chassis live outing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
423 lines
35 KiB
Markdown
423 lines
35 KiB
Markdown
---
|
||
id: open-questions
|
||
title: "Open Questions — deferred systems, unknowns, get-from-Nick"
|
||
status: living
|
||
source_sections: "CLAUDE.md §9; docs/HARD_PROBLEMS.md; per-subsystem deferral notes"
|
||
related_topics: [source-completeness, subsystems, multiplayer, combat-damage, locomotion]
|
||
---
|
||
|
||
# Open Questions
|
||
|
||
Known unknowns + deliberately-deferred systems. For each: what's known, what's missing, what
|
||
would resolve it, status. Deferred ≠ broken — most are marked-and-guarded stand-ins with the
|
||
authentic path scoped.
|
||
|
||
## From Nick (external — gating for some paths)
|
||
- **[GATING] The BT game SOURCE CODE** — the missing implementation `.cpp` (mech, subsystems,
|
||
mapper, HUD, app; see [[source-completeness]]). Without it, Route A (recompile original) is
|
||
impossible; we reconstruct from the binary instead. Likely on a backup/dev drive. Status: OPEN.
|
||
- Confirmation the PodPC image is the complete content master. (Low priority — the runtime
|
||
`BTL4.RES` already has 8 maps + the full mech anim set.) Status: OPEN.
|
||
- Pod specifics for Phase 8: the 7-monitor driver setup, the RIO cockpit I/O protocol, current
|
||
Win10+wrapper pod config. Status: OPEN.
|
||
|
||
## Deferred subsystems / feeds (authentic path scoped, marked in code)
|
||
- **✅ 0xBD3 SubsystemMessageManager — UNTANGLED + LIVE (task #7, 2026-07-11) [T1/T2].**
|
||
Both halves landed: (1) the factory case builds the REAL messmgr (ctor @0049bca4, 0x130,
|
||
static reconstruction messmgr.cpp) cached at `mech+0x434` — the binary-wide census found
|
||
exactly ONE reader (@0x4b984b in `MechWeapon::SendDamageMessage` @004b9728, which was ALSO
|
||
mislabeled, as "DrawWeaponPip"); every mapper consumer re-pointed to roster slot 0
|
||
(`MappingMapper()`; `SetMappingSubsystem` @0049fe40 touches only slot 0); non-viewpoint mechs
|
||
carry a slot-0 demand LATCH ([T3] accommodation for the port's wider drive/gait reachability).
|
||
(2) The authentic CONSOLIDATED damage delivery is live: the beam path submits TakeDamage into
|
||
`AddDamageMessage` → per-frame `ConsolidateAndSendDamage` (@0049b784) builds ONE
|
||
`Entity::TakeDamageStreamMessage` (id 0x13, wire-verified 0x34+4+N×12) dispatched at the
|
||
victim, whose T0 handler (ENTITY.cpp:817) re-splits it; replicant victims reroute cross-pod.
|
||
Explosion bundling (weapon+0x3E4 per record, unique-queued, Explosion::Make at the impact)
|
||
implemented; TWO latent chain-purge bugs fixed (records re-applied every tick). RESIDUE
|
||
([T3], next task): the weapon-side submission with per-weapon `damageData` +
|
||
`inflictingSubsystemID` awaits the DAMAGE-ECONOMY reconciliation (authored 0.25-scale
|
||
amounts vs the bring-up kShotDamage=12); the 0.1s explosion stagger + the terrain-hit
|
||
derivation check are noted in messmgr.cpp.
|
||
⚠ The earlier "0xBD3 gates the valve/Myomers" claim remains WRONG (those read the owning
|
||
BTPlayer at mech+0x190 — see the next entry); the old wiring notes are superseded by this
|
||
landing.
|
||
- **✅ `mech+0x190` IDENTIFIED (2026-07): it is the owning `BTPlayer`** (`Mech::GetPlayerLink()`,
|
||
`MECH_OWNING_PLAYER`; ENTITY.h:430). Set by `FUN_0049f624` (the mech↔player bind: `mech+0x190 = player`
|
||
AND `player->playerVehicle(+0x1fc) = mech`), which resolves the player from the mission player registry
|
||
(`app+0x2c+0x54`, `FUN_0041fd18`) by the pilot key. The valve/Myomers "gates" read the owning player's
|
||
fields at `player+0x260` and `player+0x274` (the reconstruction maps this block as `showDamageReceived@0x25c
|
||
/ showKills@0x260 / showDamageInflicted@0x264 / roleClassIndex@0x274` in btplayer.hpp — from the SCORING
|
||
work, so the names may NOT match the gate semantics; the true meaning + the WRITER of `player+0x260/0x274`
|
||
are not yet pinned — part_013.c:4553-4660 is the TorsoSimulation, a different object, not the setter). So
|
||
there is NO new subsystem to build. **The wiring (small):** point `MechSubsystem::IsDamaged()` (`FUN_004ac9c8`, valve
|
||
guard) at `GetPlayerLink()->`(0x274) and `Myomers::OwnerAdvancedDamage()` (`FUN_004ad7d4`) at
|
||
`GetPlayerLink()->`(0x260) — via NAMED members (databinding trap: our BTPlayer layout ≠ binary; do NOT
|
||
raw-read `player+0x260`). ⚠ Resolve first: (a) a naming conflict — `FUN_004ad7d4` is labeled BOTH
|
||
`HeatModelActive` (heat.hpp) AND `OwnerAdvancedDamage` (myomers.cpp); re-verify which body is which; (b)
|
||
the true semantic of `player+0x260/0x274` vs the scoring-derived `showKills`/`roleClassIndex` names. NOTE:
|
||
these are MODE flags — in the basic test mission the inert Myomers / dormant valve is likely AUTHENTIC
|
||
(advanced damage off); wiring makes them RESPOND when a mission enables the mode, not necessarily change
|
||
the basic-mission behavior. The MessageBoard feed is separate (StatusMessagePool, below).
|
||
- **✅ Authentic target acquisition RECONSTRUCTED (tasks #36/#39, 2026-07-08)** — the `Reticle`
|
||
pick-ray chain is LIVE (see [[combat-damage]] Targeting for the full port map): the crosshair =
|
||
**torso boresight** (NO free-aim mouse — the pod stick twisted the torso; you steer to aim) →
|
||
pick ray → per-frame lock (boresight ON the mech = locked; off = none) → aimed zone damage +
|
||
designator hotbox; `BT_FIRE_ARC` is opt-in-only now. Firing needs a target but NOT a manual
|
||
lock/pinpoint — the target is AUTO-acquired (task #40, binary-verified: FireWeapon doubly-gated
|
||
on mech+0x388, but 0x388 has 11 reads / 0 direct stores across CODE → set indirectly). Residue:
|
||
(a) the binary's own per-frame reticle→mech copy + the AUTO-TARGET SELECTOR are still in an
|
||
un-exported gap (a message/selector; our port auto-targets the single living enemy — for MP,
|
||
reconstruct the real multi-target selection: nearest/most-aligned/cycle); (b) the eyepoint now
|
||
rides the parent DCS (task #55 authentic eye), so torso pose reaches the view; on a
|
||
TWIST-CAPABLE mech (selectable via `BT_FORCE_MODEL`) the crosshair should deflect with the
|
||
torso twist (`BTTwistToReticleX` is wired for it) AND the torso should visibly lead the legs —
|
||
verify the reticle deflection on a twist mech (the BLH is fixed-torso, boresight always
|
||
centred); (c) pre-burial, the pick still
|
||
tests the sinking wreck's collision box at its parked position (pre-existing wreck-targeting
|
||
behavior).
|
||
- **Cockpit HUD leftovers (tasks #35-#38 residue, 2026-07-08)** — the reticle + 7 weapon pips +
|
||
ALL the instrument dynamics are LIVE (the Execute @004cdcf0 gap was RECOVERED via capstone
|
||
disasm, task #37; the authentic Lock producer + the simple-X mode landed in #38 — see
|
||
[[gauges-hud]] for the full instrument map). Remaining: (a) the **`*_cop` canopy shell** —
|
||
SOLVED for all 8 (task #55): authentic eye + the PUNCH stencil-cut kit + double-sided cop
|
||
geometry + unlit constant frame colour (`BT_COP_FRAME`) — see [[cockpit-view]] Rendering/FINAL;
|
||
shows by DEFAULT now (`BT_HIDE_COCKPIT=1` hides). NOTE the old claim here ("canopy windows are
|
||
punch texels") was WRONG — the shell has no texture; the openings are geometry. ✅ RESOLVED
|
||
(2026-07-11): PUNCH = a 3-chunk STENCIL-CUT kit (mask/hull/twin, i860-firmware-decoded)
|
||
executed as a D24S8 stencil cut; visible canopy = hull-minus-apertures ([[cockpit-view]]
|
||
§FINAL). All 8 mechs render connected dark frames with clear viewports (the intermediate
|
||
"frameless bhk1/loki/vulture/avatar" reading was wrong); thor matches the pod footage exactly.
|
||
Eye placement verified exact per-mech incl. Thor's +1.13 offset cockpit ([[cockpit-view]]). (b) the **3D marker
|
||
chain** ([0x31c]/[0x320] dpl objects + the PNAME1-8.bgf player-name meshes over the locked
|
||
target — matters for MP). (c) the compass rotation SIGN is derived-not-verified [T3] (turn
|
||
left → the stem should swing right; flip the sin sign in Draw if footage disagrees).
|
||
- **✅ StatusMessagePool / MessageBoard — LIVE (2026-07-12) [T1/T2].** Decoded: FUN_0042e580 =
|
||
the engine `Player::AddStatusMessage` (chain add at +0x1e4); FUN_0042e5a0 = `StatusMessageUpdate`
|
||
(+0x1dc = the displayed message; +0x14 displayTime countdown, expiry-deleted) — ALL already in
|
||
the engine [T0 PLAYER.cpp]. The ONE binary producer = BTPlayer::ScoreMessageHandler's KILL
|
||
branch (@004c02e4 type-2): pool-alloc @00512f6c + ctor(victim's player, strip 0, 6.0s) +
|
||
AddStatusMessage. Port: engine `new Player__StatusMessage` + Register_Object (the engine
|
||
reclaims with delete — the binary pool is an allocation detail); consumer =
|
||
BTResolveMessageBoard reads the mission player's statusMessagePointer + resolves the name via
|
||
Mission::GetSmallNameBitmap; the board's SetSource binder (no recovered caller) is replaced by
|
||
a lazy viewpoint-entity bind in Execute. Ticker pops on BOTH nodes (each node's death
|
||
transition posts its local KillScore).
|
||
- **Gyroscope (0xBC4) — RE-ENABLED LIVE (task #56, 2026-07-10).** The NaN revert's root causes
|
||
are fixed byte-exact (ctor field map @004b3778 — springConstant@0x1E8/dampingConstant@0x1F4
|
||
were mislabelled, the 0x254-0x2B3 block was missing, clamps/accumulators uninitialised; the
|
||
integrators' state-minus-target/componentwise-damping-overwrite/X-Z-crossing semantics; the
|
||
writers' one-node-two-channels form; dispatch from the MECH performance tail, not the gyro
|
||
Performance). Layout locked (sizeof==0x3D0); runtime clean (joints type 5, finite state, no
|
||
NaN). Empirical: spring targets are SYMMETRIC → eye equilibrium (0,0,0) → the gyro is a
|
||
±0.1-0.15u BOUNCE mechanism, NOT a steady offset (the earlier hypothesis is disproven).
|
||
**FAN-OUT LANDED (2026-07-11):** FUN_004b2980 re-disassembled from raw bytes and reconstructed
|
||
(`Gyroscope::ApplyDamageResponse`) + wired at the take-damage hub, the crushable crunch and the
|
||
firing recoil — the hit-BOUNCE is live and verified (damped eye oscillation, no NaN).
|
||
REMAINING (task #56 tail): (a) the alternate-gait engage jolt + engaged-gait rumble
|
||
(@4aa158-4aa365 — [T3] gate naming, mutates the #49/#50-stabilized gait machine; byte recipe in
|
||
the wf_6880e605 synthesis); (b) the mech+0x3F0 overspeed sway model (swayBias fed 0); (c) the
|
||
per-frame `EyepointRotation.pitch = torso pitch` writer (FUN_004b66b4) + mapper glance-look
|
||
states (FUN_004afd10 — yaw/pitch from mech+0x564..0x570); (d) senders filling
|
||
`Damage::damageForce` for directional bounce (random-fallback is binary-legal meanwhile).
|
||
Also: `deathAnimationLatched`/`legResetLatch` were never ctor-initialised (0xCDCDCDCD gate bug,
|
||
fixed in mech.cpp ctor). See [[cockpit-view]].
|
||
- **MechControlsMapper look/eyepoint commit** — reconstructed but its offsets collide with declared
|
||
members; arbitrate before enabling (no port consumer yet). The fire-trigger-through-mapper is
|
||
still the `gBTWeaponTrigger` bring-up.
|
||
- **✅ Weapon groups — the AUTHENTIC defaults + fire path LIVE (task #5, 2026-07-11) [T1/T2].**
|
||
The system decoded end-to-end: a fire "channel" = a physical `buttonGroup` slot in the global
|
||
`LBE4ControlsManager` (fire buttons 0x40 Trigger / 0x45 Pinky / 0x46 ThumbLow / 0x47 ThumbHigh);
|
||
binding = a permanent mode-0x10000 DIRECT mapping button → the weapon's **TriggerState** attr
|
||
(id **0x13**, = `fireImpulse@0x31C`; the binary MechWeapon table's ONLY attribute @0x511890);
|
||
the weapon self-edge-detects (CheckFireEdge @4b9608). **The default grouping is NOT messages —
|
||
it is the per-mech type-6 controls-map resource in BTL4.RES** ("Thrustmaster"/"L4" variants;
|
||
e.g. madcat: Trigger={4 weapons}, ThumbHigh={2}), installed at `MakeViewpointEntity` by the
|
||
ENGINE's `CreateStreamedMappings` (T0, L4CTRL.cpp) — which the port already called; it needed
|
||
only the TriggerState attribute published (id PINNED to 0x13 — our attr-id chain is 6 short of
|
||
the binary's; full binary attr-table dump in the task #5 scan) + an input feed. Keyboard now
|
||
pushes press/release edges into the buttonGroups (SPACE→Trigger, '2'→ThumbLow, '3'/CTRL→
|
||
ThumbHigh; BT_AUTOFIRE pulses the Trigger); the gBT*Trigger bypasses + pulse hack are retired.
|
||
VERIFIED 2-node at the BINDING level (⚠ honest correction, task #8: those kills still flowed
|
||
through the retired mech4 bring-up block -- the emitter DISCHARGE chain was latched shut by
|
||
the NaN edge bug below until 2026-07-11; the full weapon-fire chain is now verified solo).
|
||
- **✅ THE CONFIG-MODE SESSION — RECONSTRUCTED (task #6, 2026-07-11) [T1 anchors].** The
|
||
pod's in-cockpit weapon-REGROUPING UI, fully decoded from the binary + T0 engine:
|
||
* **The flow**: the pilot HOLDS a weapon's configure button (a per-weapon type-6
|
||
EventMapping, id 9, on the MFD-quadrant aux panels; e.g. BTL4.RES @0xAA3DC) →
|
||
MechWeapon handler id 9 "ConfigureMappables" @004b9550 (table @0x511860; the old
|
||
"Myomers fns" label swept) → mapper(slot 0)->EnterConfiguration @004d1840 (vtbl+0x38):
|
||
StartMappableButtonsConfigure (T0 CONTROLS.cpp:291 -- latches the active weapon for the
|
||
gauge, flips the GLOBAL mode NonMapping 0x10000 → Mapping 0x8000, so every normal fire
|
||
mapping goes dormant), re-arms the held button (+0x8000, id 9), temp-maps the four fire
|
||
buttons 0x40/0x45/0x46/0x47 → id 10. Tapping a fire button → handler id 10
|
||
"ChooseButton" @004b95b8 → RIO AddOrErase(direct) @004d262c: buttonGroup[v-1].AddOrErase
|
||
(0x10000, &fireImpulse) — a TOGGLE; the permanent 0x10000 instances ARE the commit.
|
||
Release → ExitConfiguration @004d18dc: StopMappableButtonsConfigure + strip the 0x8000
|
||
layer. Only the RIO (pod) mapper implements AddOrErase; L4/Thrustmaster carry RET
|
||
no-ops @004d195c/@004d1964 (a joystick cockpit cannot regroup).
|
||
* **Vtable truth (swept)**: base @0050F45C +0x38 Enter(Fail @004b0280) / +0x3C Exit(Fail
|
||
@004b029c) / +0x40 AddOrErase-event(Fail @004b02b8) / +0x44 AddOrErase-direct(Fail
|
||
@004b02d4) / +0x48,+0x4C Notify pair. There is NO "secondary vtable @0050f498" and NO
|
||
"CreateTemporaryEventMappings" virtual (RP-name drift). @004afbc4 (the mapper's shared
|
||
aux/zoom handler) is a pure Fail thunk ("Unhandled button mapping!", line 0x7a), not an
|
||
AddOrErase router.
|
||
* **Port landed**: MechWeapon handlers 9/10 + the GetMessageHandlers() accessor chain
|
||
(MechWeapon → Emitter/ProjectileWeapon → MissileLauncher, + GAUSS/PPC.CPP; dispatch
|
||
resolves through the CONCRETE class's SharedData — the empty per-class sets silently
|
||
swallowed the messages); the 6-arg Enter/1-arg Exit reshape; the L4 Enter/Exit full
|
||
bodies; MechSubsystem +0xE8/+0xEC corrected to controlDestination/controlMessageID (the
|
||
old hostEntity/subsystemId2 labels were wrong; MechWeapon ctor defaults the destination
|
||
to &fireImpulse per @004b99a8); dev harness = HOLD 'G' (BT_CONFIG_SLOT selects the
|
||
weapon; BT_CONFIG_TEST scripts a headless session).
|
||
* **ConfigMapGauge finding [T1]**: the shipped binary NEVER enables it — no caller of
|
||
SetColor @004c6ee0 exists (the only child accesses are BecameActive/SetEnable forwards),
|
||
so color stays 0 and Execute early-outs: the gauge is authentically DORMANT in BTL4OPT.
|
||
The state loop is now reconstructed anyway (@004c6f1c: 4 {y,button} pairs from
|
||
DAT_00518eb4 = {13,Pinky}{37,ThumbLow}{61,Trigger}{85,ThumbHigh}, x=0xc;
|
||
buttonGroup[btn].GetMapState(controlDestination, subsys, controlMessageID, 0x10000) →
|
||
cm_off/cm_other/cm_only/cm_both), behind the PORT dev enable BT_CONFIGMAP=1.
|
||
* **Bonus [T1]**: the binary MechWeapon ATTRIBUTE table @0x511890 has ELEVEN entries
|
||
(0x12 PercentDone .. 0x1C WeaponState) — future gauge-wave feeds; the "TriggerState is
|
||
the only MechWeapon attribute" claim was wrong (fixed in mechweap.hpp).
|
||
* Tails: weapon+0x110 (write 0/-1 on session open/close; port name vitalSubsystemIndex)
|
||
has NO confirmed reader yet; @004d1acc (+0x48 L4 Notify override) is unexported —
|
||
re-export from Ghidra.
|
||
* **Task #12 (2026-07-11) [T2]: the PoweredSubsystem POWER-ROUTING handlers landed** --
|
||
ids 4-8 registered (table @0x50F4EC): SelectGeneratorA-D = FindGeneratorByNumber
|
||
(@004b0b18 ROSTER walk matching generatorNumber@0x1E0) -> AttachToVoltageSource ->
|
||
modeAlarm=Connected; ToggleGeneratorMode (@004b0abc) = <2 -> AutoConnect, ==2 ->
|
||
Detach + ManualConnect. Weapons inherit via the MechWeapon handler chain (the old
|
||
qualified GetMessageHandlers() calls silently resolved to the engine Receiver ROOT
|
||
set -- same trap as the task #6 weapon handlers). The AutoConnect hunt's
|
||
GetSegment() scan corrected to the subsystem ROSTER (the +0x128 gotcha). Desktop:
|
||
F5-F8 assign the selected weapon (BT_CONFIG_SLOT) to Generator A-D, F9 toggles
|
||
Manual/Auto; BT_GENSEL_TEST scripts a headless re-tap. Note: Myomers/Sensor still
|
||
chain the root set (their binary tables unverified) -- extend if aux maps prove
|
||
they receive these ids. TWO MORE TRAPS found landing this: (a) MESSAGE_ENTRY
|
||
tables must be function-local statics INSIDE the accessor (cross-TU static-init
|
||
order emptied the table -- see [[reconstruction-gotchas]] #9 last bullet); (b)
|
||
**FUN_004ac9c8 is NOT IsDamaged** -- raw body: `owner(+0xD0) -> mech+0x190 player
|
||
-> roleClassIndex(+0x274) == 0` = the ROOKIE-role lockout for advanced cockpit
|
||
systems (port bridge BTPlayerRoleLocksAdvanced, btplayer.cpp; NULL player =
|
||
unlocked [T3]; bring-up role defaults to 2 = unlocked). AUDIT TAIL: the other
|
||
powersub.cpp sites annotated FUN_004ac9c8 (the coolant-draw gate ~:427,
|
||
ForceShortRecovery ~:444, the Generator/PowerWatcher site ~:1085) still call the
|
||
heat-family IsDamaged (simulationState != 0) stand-in -- each needs its raw fn
|
||
re-checked and swapped to the role bridge where the binary calls 4ac9c8.
|
||
- **Myomers authentic coupling** — the structural un-stub is INERT (mover feed + heat-gen no-op).
|
||
Real coupling needs the advanced-damage gate (`OwnerAdvancedDamage`/`FUN_004ad7d4` → the owning
|
||
**BTPlayer** `mech+0x190`+0x260, NOT 0xBD3 — see the mech+0x190 item above) + `MoverAttach` routing
|
||
into the LIVE JointedMover (must be reconciled with the gait cutover first).
|
||
- **SeekVoltageGraph** — 4 Seek* attrs unpublished (a cluster-child, not config-called; non-blocking).
|
||
|
||
- **✅ DAMAGE ECONOMY — AUTHENTIC (task #8, 2026-07-11) [T1/T2].** The whole economy closes:
|
||
`_DAT_004bafbc` is an x87 float80 = **1e-7** (the port's kDamageScale=1.0 was the mystery) --
|
||
it cancels the ctor's ×1e7 energy bookkeeping, so **damagePortion = authored DamageAmount ×
|
||
(charge/seekV[rec])²** (port: the closed form at fire time; the bring-up charge cycle keeps
|
||
the degenerate EC=1 electrical model). Authored values (BTL4.RES type-0x11, +0x19C; scanner
|
||
scratchpad/scan_weapsub.py): madcat AC=25/LRM15 salvo=50/ERLL=6/ERSL=2; bhk1 PPC=12/SRM6=35/
|
||
ERML=3.5. Zone model already byte-exact (Δlevel = amount/armorPoints, legs ×0.5; armor dump
|
||
in the task #8 synthesis) → the pod's 4-8-hit heavy-weapon pacing. The weapon-side
|
||
SendDamageMessage (@004b9728) is LIVE from Emitter::FireWeapon (damageData filled incl.
|
||
**damageForce = target−muzzle → the gyro's directional hit-bounce feed**); the mech4
|
||
bring-up damage block + flat kShotDamage are retired to diag hooks. **ROOT-CAUSE find: the
|
||
CheckFireEdge NaN latch** -- TriggerState carries ControlsButton INTS (release −65 = a
|
||
negative NaN); the binary's x87 unordered-compare treated it as "released" but an
|
||
IEEE-correct float compare latched the detector shut after the first release (why the
|
||
emitters never discharged in-game until now); fixed with bit-pattern sign compares
|
||
(@004b9608). Residues [T3]: heat stays on the bring-up scale (authentic = heatCost×1e7
|
||
energy units, the missile path already feeds it -- the HEAT-CALIBRATION audit);
|
||
LODReuseHysteresis corrected 0.82→0.33 (a double); the MP beam-kill live-verify awaits a
|
||
clear-sightline spawn (mechanism identical to solo; force-dmg cross-pod cycles verified).
|
||
|
||
- **HEAT (task #9, 2026-07-11): authentic 1e7-unit heat LIVE; conduction ROUTING defect
|
||
remains [T2/T3].** Landed + byte-anchored: emitters emit `heatCostToFire x 1e7 x
|
||
(charge/seekV)^2` (closed form; PPC = 1.1e8 -> +632 K on its 174000-mass sink); the missing
|
||
projectile/missile heat adds (raw pre-scaled resources); the bank's AMBIENT RADIATOR @4ae73c
|
||
(the system's only heat exit: relax toward 300 K, conductance x 0.1 x HeatSinkCount
|
||
[_DAT_004ae974 float80 = 0.1 -- the audit's "1.7375" was a misread]); the link-attach guard
|
||
corrected (skip = the 0xBBE bank, NOT Condenser -- the inversion had closed the system);
|
||
three coolant epsilon corrections (0.0025/0.003/1e-4 for the old single 1e-4) +
|
||
CoolantCapacityScale = 0.05 float80 (both prior readings wrong). Verified: heat flows,
|
||
exits (~2/3 of a 1e10 spam run drained), weapons overheat under max-rate autofire exactly
|
||
as the authored-constant simulation predicts (thermal spam is unsustainable BY DESIGN;
|
||
heatLoad -> effectiveRange 0 = the authentic overheat cutout).
|
||
- **Task #10 -- the "scrambled routing" RESOLVED (2026-07-11) [T2].** The routing was NEVER
|
||
scrambled: a `[heat-link]` attach log proved every subsystem links its authored sink
|
||
exactly (bhk1: PPC_1->C4, PPC_2->C6, ERM_1/2/3->C1/C6/C4, SRM6s->C2, generators->C1/C2/C3/C5,
|
||
condensers+reservoir->bank@slot2). The "pools in Condenser1" observation was the
|
||
DIAGNOSTIC-SAMPLER ALIASING trap (one shared static 1-Hz timer, pendingHeat-gated); the
|
||
census log is now per-instance. The REAL defect was the fire RATE: emitter.cpp's local
|
||
`FUN_00417ab4` stub returned NULL, so although the PoweredSubsystem ctor attaches the
|
||
authored generator to `voltageSource@0x1D0`, the Emitter TU could never see it -- the whole
|
||
electrical model was inert and the E7 force-charge recharged every emitter in ONE frame
|
||
(~0.3 s cycle, 1501 fires/90 s, ~1.7e9 heat/s input). Fixed authentically [T1, disasm +
|
||
byte-verified constants]:
|
||
* Emitter ctor @004bb120: seekVoltage[i] = authored fraction x generator ratedVoltage
|
||
(authored 10000); EC = energyTotal/(seekV[rec]^2 x 0.5); **voltageScale@0x310 =
|
||
(RechargeRate / -ln(1 - 0.0001 x seekV)) / EC** -- the exponential charge reaches
|
||
seekV[rec] in EXACTLY the authored RechargeRate on a cold generator (PPC 5 s, ERLLaser
|
||
4 s, SRM6 3 s, ERMLaser 2 s, LRM15 6 s). The gate reads the OWNER's simulationFlags
|
||
(the usual ctor-gate gotcha; the old this-flags read never armed).
|
||
* `PoweredSubsystem::ChargeTimeScale()` (@004b0d50, was a =1.0 stub): voltageScale x
|
||
(1 + thermalResistivityCoefficient x max(0, srcT - srcT0)) -- a HOT generator charges
|
||
slower ("voltageScale is never read back" was wrong -- this reads it every tick).
|
||
* TrackSeekVoltage @004ba838: charging I^2R (`seekRate^2 x dtScale x dt`) lands in the
|
||
GENERATOR's pendingHeat@0x1c8 (~3.5e8 per full PPC charge -> generators self-heat ->
|
||
conduct to their condensers -> slow further charging). This closes the heat/firepower
|
||
feedback economy.
|
||
* **FailureHeat consumers found [T1, disasm]:** `this+0x184` (the weapon's own heatAlarm
|
||
status level) == 2 gates BOTH families -- @004baa88 (emitter: ResetFiringState +
|
||
currentLevel=0 until it cools) and @004bbd36 (ballistic: recoil pinned to rechargeRate +
|
||
alarm state 7). Emitter::GetFaultState() now returns heatAlarm.GetLevel() (was a 0 stub).
|
||
* ProjectileWeaponSimulation @004bbd04 opens with `call 0x4b0bd0` (disasm-verified) -- the
|
||
launchers now run the powered/heat step (their task-#9 firing heat previously accumulated
|
||
in pendingHeat forever; SRM6s now census at ~950 under sustained fire).
|
||
Verified live (120 s max-rate autofire): PPC ~470-500 (was 55,000), bank plateaus ~600 and
|
||
sheds, generators 1100-1400, ERMLaser self-regulates at the 2000 failure threshold
|
||
(shutdown -> cool -> resume). Remaining tails: the HeatWatcher/AmmoBin cook-off
|
||
watchedLink; the mech-level heatAlarm@0x450 producer; the cockpit power-routing message
|
||
handlers (@004b099c..@004b0abc: assign weapon to Generator 1-4 + auto/manual toggle via
|
||
modeAlarm@0x2CC -- wired to buttons, not yet reachable).
|
||
|
||
- **Task #11 -- the live-play regressions (2026-07-11) [T2].** User-reported after the
|
||
electrical model landed. THREE distinct causes, all resolved:
|
||
(1) *Phantom fire on the enemy*: the mech4 bring-up shot block painted an explosion at the
|
||
victim on its OWN 0.3s cadence whenever fire was held -- desynced once real recharges landed.
|
||
RETIRED (the authentic impact visual flows from each real discharge via the messmgr's
|
||
SubmitExplosion). (2) *Weapons bricking one by one* ("fire ~1s then cuts out"): the
|
||
FRAME-PACING TRAP -- see [[reconstruction-gotchas]] §12: the Loading->Loaded snap window
|
||
(+-0.01 around seekV) assumes the pod's locked 60 fps; a dt spike jumps it, the overshoot
|
||
clamp zeroes rechargeLevel, and the weapon sticks in Loading at level ~10000 forever.
|
||
Fixed with pod-frame (1/60s) sub-stepping of the binary's own Loading tick (also fixes the
|
||
I^2R integral overfeeding generator heat on big steps). (3) *Slow kills / short fire
|
||
windows*: AUTHENTIC -- beams last the authored DischargeTime, recharges take the authored
|
||
2-5s stretched by hot generators (ChargeTimeScale), and max-rate autofire equilibrates
|
||
generators in the degradation band (1100-1500 K; the FailureHeat breaker never trips in
|
||
solo max-abuse -- duty-cycle measured). The pilot's authentic counters are the systems
|
||
under reconstruction: config-mode regrouping (task #6), generator reassignment (ids 4-8),
|
||
coolant valves.
|
||
|
||
- **Subsystem-panel online/offline gate — fields unidentified [T4] (2026-07-12).** The binary's
|
||
SubsystemCluster draw-state reads `*(subsystem+0x40)==1` and `*(subsystem+0x278)!=4`; neither
|
||
1995 field is identified (weapon simulationState is 0/2/3/4 -- +0x40 isn't it). The port pins
|
||
the panel ONLINE (GetDrawState()==1) because the old raw reads were layout NOISE that flapped
|
||
the state and black-filled the panel over the recharge arc (the frozen-PPC-dial bug; the
|
||
WeaponCluster repaint-heal also landed). Identify both fields to restore the authentic
|
||
destroyed-subsystem dark-panel look.
|
||
|
||
## Locomotion / combat polish (non-gating)
|
||
- Authentic per-mech TURN-RATE constant (currently a bring-up constant rate).
|
||
- Body-callback gimp handlers (states 16-19, targets 0x70b2/0x7161 undecoded → fall back to stand);
|
||
airborne callbacks (`FUN_004a6344`/`FUN_004a7970`).
|
||
- Wall-block-vs-climb tuning. **✅ Collision DAMAGE application — DONE 2026-07-08 [T2].** The two deferred
|
||
dispatches in `Mech::ProcessCollision` (mech4.cpp: mech-vs-mech `:15324-15358` + icon-crunch `:15369-15401`)
|
||
now fire: on a collision with another Mech or a CulturalIcon, an `Entity::TakeDamageMessage{zone==−1}` is
|
||
dispatched to the victim (via `BTDispatchCollisionDamage`, the engine ctor — same as the weapon path), and
|
||
STEP 6's cylinder table resolves the impact point → a zone on the receiver. Terrain (walls/hills) matches
|
||
neither branch → blocks without damage (faithful). Reachability is guaranteed (`Mover::ProcessCollisionList`
|
||
calls the **virtual** `ProcessCollision` → `Mech::ProcessCollision`); built + stable. The live dispatch
|
||
wasn't captured headlessly (the solo auto-walker never rammed a tree/mech), but the path is proven reachable
|
||
and composed of runtime-verified pieces. **Note:** validated STEP 6's height ref along the way —
|
||
`collisionTemplate->maxY ≈ 7.1` (a real mech height), confirming `CylinderReferenceHeight` reads a height,
|
||
not the heat value the `mech+0x2ec` dual-labeling (heat-gauge sink vs groundRef, mech4.cpp:2697) hinted at.
|
||
- **✅ Cylinder hit-location (STEP 6) — DONE + LIVE 2026-07-08 [T2].** Built + runtime-verified: the `dmgtable.cpp`
|
||
classes now have real storage + a working `ResolveHit→zone`, the mech ctor loads the type-0x1d table by the
|
||
DamageZoneStream name (`[cyl] table 'bhk1' layers=7`), and `Mech::TakeDamageMessageHandler` resolves
|
||
unaimed (zone==−1) hits before base-routing. ⚠ The FIRST "verified" claim was PARTIAL: the table was
|
||
cached via `Wword(0x111)` (the ABSORBER — stores nothing) so every unaimed hit silently no-op'd; fixed
|
||
2026-07-08 by promoting the cache to the named member `Mech::damageLookupTable`
|
||
([[reconstruction-gotchas]] §2). Now verified end-to-end (19 zones resolving, `BT_AUTOFIRE` harness).
|
||
Full change list in [[combat-damage]] "STEP 6 COMPLETE". Historical investigation notes retained
|
||
below for provenance.
|
||
**Re-investigated 2026-07 (see [[combat-damage]] for the full algorithm):** the table (height×angle
|
||
grid, resource type 0x1d) IS built (recon `StandingAnimation` @Mech[0x111], but with an EMPTY name → 0 rows,
|
||
because `ResourceFindByName` is a **no-op template stub**). **CORRECTION to the earlier note:** the LOOKUP is
|
||
**exported** [T1] — `FUN_0049eb54` (table height→row) + `FUN_0049e678` (row angle→cell), NOT unexported.
|
||
Only the thin `Mech::TakeDamageMessageHandler` glue (zone==-1 → call the lookup → `damageZones[zone]->TakeDamage`)
|
||
is unexported, and it's fully implied by pieces already reconstructed → no disassembly needed. Remaining:
|
||
6b real `FindByName` (name = the mech's type-0x14 DamageZoneStream name @ResourceDescription+0xc); 6c the 3
|
||
container ctors + the lookup as a `BTCylinderResolveZone` bridge (ground the cell zone-dict stream format vs
|
||
the real .RES bytes); 6d register the handler + verify. Feasible; the only open risk is the cell stream format.
|
||
The earlier-cited `FUN_004a0230`/`FUN_0049ed0c` don't exist.
|
||
- **Death sequence — CORE DONE 2026-07-08 [T2]; effects+anim deferred.** `Mech::UpdateDeathState()`
|
||
(mech4.cpp) reconstructs the death STATE machine (collapse `movementMode` → RP `VTV::DeathShutdown`
|
||
subsystem loop → settle to disabled/frozen; wreck stays, no crash) — runtime-verified (`BT_DEATH_LOG`).
|
||
Full map + citations in [[combat-damage]] "Death SEQUENCE". ✅ (b) `MechDeathHandler` DONE (mechdmg.cpp) +
|
||
✅ the render-side **RemakeEntity** destroyed-mesh swap DONE 2026-07-08 (btl4vid.cpp
|
||
`RemakeEntityRenderables` + `BTRemakeMechModel` bridge; in-place `SetDrawObj` swap — the tree dtor does
|
||
NOT cascade so never rebuild). ✅ (a) **RESOLVED — no collapse animation exists in BT 4.11** [T1]: the
|
||
fall latch (modes 5-8 → table slots 0x1c-0x1f) is a vestige — no loader fills the slots, no fall clip
|
||
ships, and firing it would bind resource 0 (an AUDIO stream) as keyframes. ✅ The authentic death
|
||
VISUAL is the **WRECK-HULK SWAP** (death ModelList `blhdead` → effect 104 → the victim becomes its
|
||
`<mech>dbr.bgf` burning hulk) — RECONSTRUCTED + verified (`SwapToWreck`, btl4vid.cpp; see
|
||
[[combat-damage]] "Death SEQUENCE" for the full chain + proofs).
|
||
✅ The wreck **sinks** (the 1996 quadratic burial, `offsetY = -0.025·t²`, gone ~17s) with the `ldbr`
|
||
debris field; at burial it goes **INERT** (collisionVolumeCount=0 + target lock dropped — no phantom
|
||
blocking/hits). **Remaining**: (c) the whole-mech **DeathSplash** radius damage; wreck mesh FLAMES
|
||
(flamesml/flamebig + sweep flicker, 1996 script case 4); the **full entity teardown at burial**
|
||
(the authentic death-row removal — needs the mech render tree unhooked from the renderer first;
|
||
the inert wreck is the safe stand-in until then).
|
||
Do NOT issue DestroyEntityMessage on death (before the render-tree teardown lands).
|
||
- ✅ **Critical-subsystem plugs BOUND — damage propagates (task #2, 2026-07-11) [T1/T2].**
|
||
The "binding write is elsewhere" reading was WRONG: the binding is IN the zone ctor loop
|
||
(@0049d0e1-0049d10d) — **Ghidra dropped the two argument pushes** ("type propagation not
|
||
settling"), making `Slot::AddImplementation(roster[streamedIndex])` read as a bare no-arg
|
||
call (mislabeled Resolve). The stream's third per-entry dword IS the roster index into
|
||
`subsystemArray` (mech+0x128). Port: the `DZSlot` stand-in (always-null Resolve) replaced
|
||
with the engine `SlotOf<T>` (binary vtables 0050bb84/0050bb7c ARE its instantiations); bind
|
||
gated MasterInstance+DynamicFlag (`simulationFlags@+0x28`); replicant plugs stay unbound
|
||
(master-authoritative). `SendSubsystemDamage` rewritten to the recovered @0049c9a8 logic:
|
||
the unused crit allotment lands in the subsystem's OWN private 0x160 DamageZone (the old
|
||
`SubProxy2` cast to a mech zone was wrong), ≥1.0 → statusAlarm Destroyed + gated PrintState
|
||
+ zone valve, vital → `graphicAlarm 9` (mech kill); `CriticalHit` now calls the real
|
||
`ApplyDamageAndMeasure` (@4ac07c); the artifact `parentArtifactZone.Add` back-pointer bind
|
||
also revived (LOD damage averaging). `videoObjectFlag` was `vitalSubsystem` (+0xE4,
|
||
res+0x48 "VitalSubsystem"). VERIFIED live (`BT_CRIT_PROBE=<zone>` diag): 66 plugs bound at
|
||
ctor per mech, zone destroyed → crits damaged/DESTROYED with authentic accumulation.
|
||
Residue: roster idx 0/1 (mapper installed post-ctor / voltage-bus stub) stay unbound with a
|
||
logged skip — none of the 8 mechs' streams crit-list them in practice.
|
||
- **Two dead Wword comparison branches (multiplayer)**: `mech.cpp:1511` + `:1613` (replicant
|
||
leg-state/stability sync in ReadUpdateRecord) — always-false BTVal comparisons ([[reconstruction-gotchas]] §2);
|
||
needs slots 0xf/0x10 promoted to named members when P6 replication work resumes.
|
||
|
||
## Multiplayer (Phase 7 / P6)
|
||
- ✅ Cross-pod COMBAT — DONE (tasks #46/#47: replicant targeting + damage rerouted to the owning
|
||
master, full cross-pod kill verified). ✅ Replicant GAIT animation — DONE (task #50: replicant
|
||
derives speedDemand from the replicated velocity, full stand→walk→run lifecycle verified).
|
||
See [[multiplayer]] — the entries here were stale (swept 2026-07-11).
|
||
- ✅ **Mech-level update records (@0x4a0c2c) — DONE (task #1, 2026-07-11):** all 9 types
|
||
transcribed + 2-node verified; the peer's death SINK/burial, knockdown, heat and state now
|
||
replicate (the observer's replicant runs its own wreck loop off the replicated
|
||
simulationState). Full mechanics in [[multiplayer]] "Mech-level update records". Residue
|
||
[T3]: stream the model's UpdatePositionDiffrence/TurnVelocity/TurnDegree deadband constants
|
||
to the senders (stand-in thresholds meanwhile).
|
||
- The WinSock2 stack (L4NET.CPP) is 2-node verified end-to-end (cross-pod kills, beams,
|
||
replicant gait — tasks #46-#51); remaining = the real pod-LAN config (real IPs, bare-IP
|
||
pilot entries).
|
||
|
||
## Rendering follow-ups (non-blocking)
|
||
- **.PFX effect-layer polish (deferred 2026-07-08; the layer itself is LIVE + verified).** The
|
||
reconstructed BT particle layer (L4VIDEO.cpp, see its banner) renders the authentic .PFX content
|
||
(fire + smoke, premultiplied blend, impact-frame oriented — weapon hits AND damage bands via
|
||
`lastInflictingID`, now written by `Mech::TakeDamageMessageHandler`). Remaining nuances, all
|
||
cosmetic: (a) `atten/attenv` distance attenuation not honoured; (b) the per-file texture name
|
||
(`btfx:firesmoke1_scr_tex`) — all effects share the radial-masked grit sheet; decoding FIRE.BSL
|
||
would give the authored sheet; (c) beams draw before particles, so a beam through thick smoke
|
||
reads too bright (pass-ordering); (d) `colorWarp/alphaWarp` applied as `t^(1/warp)` [T4 convention];
|
||
(e) emission uses rate×releasePeriod batching [T4 convention vs the exact 1995 emitter].
|
||
- Replacement-LOD selection for NON-additive multi-LOD models (needs the board's hot-spot/
|
||
reference-point semantics; leads: `s_dplobject.lod_ranges[16]/lod_hot_spot`, 0x2047/0x2048).
|
||
- Day/night MATERIAL path priority (BMF/IMG) — infrastructure done, gated `BT_MATPRI` (over-applies
|
||
to shared terrain mats); needs the terrain-material model understood.
|
||
- RGBA4444 alpha (TREE.BSL cutout) decoded but not alpha-tested outside punch batches; VTX/TGA paths.
|
||
|
||
## Content build sub-project (low priority)
|
||
- Lab/other-build maps (`des`/`burnt`/`frstrm`) are source-only (`.map` in CONTENT/BT/MAPS/); would
|
||
need compiling into a RES via the DOS `btl4tool.exe`. The 8 RES maps cover testing.
|
||
|
||
## Key Relationships
|
||
- Feeds from: every subsystem/render topic (their deferral notes collect here).
|
||
- Gating master: [[source-completeness]] (the missing BT source).
|