The binding was in the zone ctor all along: Ghidra dropped the two arg pushes @0049d0e1 (Slot::AddImplementation(subsystemArray[streamedIndex])), making it read as a bare Resolve(). DZSlot stand-in -> engine SlotOf<T>; SendSubsystemDamage rewritten to the recovered @0049c9a8 body (allotment into the subsystem's OWN private zone; vital -> graphicAlarm 9); CriticalHit -> real ApplyDamageAndMeasure; parentArtifactZone.Add revived (LOD damage averaging); videoObjectFlag renamed vitalSubsystem (+0xE4). BT_CRIT_PROBE diag added. Verified: 66 plugs bound/mech; probe-destroyed zone -> crits damaged/DESTROYED, statusAlarm + destroyed-skin chain fire; MP kill + solo un-regressed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
20 KiB
20 KiB
id, title, status, source_sections, related_topics
| id | title | status | source_sections | related_topics | |||||
|---|---|---|---|---|---|---|---|---|---|
| open-questions | Open Questions — deferred systems, unknowns, get-from-Nick | living | CLAUDE.md §9; docs/HARD_PROBLEMS.md; per-subsystem deferral notes |
|
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.RESalready 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 (WAVE 8) — a per-mech damage/explosion consolidation hub
(
ConsolidateAndSendDamagePerformance @0049b784,weaponExplosionsqueue,CommonDamageInformation; messmgr.hpp). ctorFUN_0049bca4(vtable 0x50b954, installs a Performance → ticks). The factory caches it toMech[0x10d]= byte 0x434. What wiring it needs: an untangle — our reconstruction MISLABELEDMech[0x10d]ascontrolsMapperandmech4's live drive reads it as the mapper, so the real messmgr can't land there until the drive is re-pointed to the true mapper (roster slot 0, installed bySetMappingSubsystem/MakeViewpointEntity; aMechRIOMapper). Payoff = authentic damage/explosion consolidation (a COMBAT-fidelity improvement) + fixing the mapper/messmgr conflation. ⚠ CORRECTION (verified 2026-07): 0xBD3 does NOT gate the valve/Myomers/MessageBoard — those read a DIFFERENT object atmech+0x190(bothFUN_004ac9c8the valve guard→+0x274andFUN_004ad7d4the Myomers gate→+0x260derefowner+0x190, which ≠ the 0x434 messmgr cache). The earlier "0xBD3 gates 4 things" note was wrong. - ✅
mech+0x190IDENTIFIED (2026-07): it is the owningBTPlayer(Mech::GetPlayerLink(),MECH_OWNING_PLAYER; ENTITY.h:430). Set byFUN_0049f624(the mech↔player bind:mech+0x190 = playerANDplayer->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 atplayer+0x260andplayer+0x274(the reconstruction maps this block asshowDamageReceived@0x25c / showKills@0x260 / showDamageInflicted@0x264 / roleClassIndex@0x274in btplayer.hpp — from the SCORING work, so the names may NOT match the gate semantics; the true meaning + the WRITER ofplayer+0x260/0x274are 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): pointMechSubsystem::IsDamaged()(FUN_004ac9c8, valve guard) atGetPlayerLink()->(0x274) andMyomers::OwnerAdvancedDamage()(FUN_004ad7d4) atGetPlayerLink()->(0x260) — via NAMED members (databinding trap: our BTPlayer layout ≠ binary; do NOT raw-readplayer+0x260). ⚠ Resolve first: (a) a naming conflict —FUN_004ad7d4is labeled BOTHHeatModelActive(heat.hpp) ANDOwnerAdvancedDamage(myomers.cpp); re-verify which body is which; (b) the true semantic ofplayer+0x260/0x274vs the scoring-derivedshowKills/roleClassIndexnames. 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
Reticlepick-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_ARCis 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 viaBT_FORCE_MODEL) the crosshair should deflect with the torso twist (BTTwistToReticleXis 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
*_copcanopy 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=1hides). 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 (NULL stub, btstubs.cpp:62) — the per-player status-message queue.
BTPlayer+0x1dc is never populated → MessageBoard is empty (authentic for bring-up). What
resolves it: wire the pool + decode
AddStatusMessage @0042e580+ thePlayer__StatusMessagerecord ({messageId@+0xc, nameEntity@+0x10}). - 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-frameEyepointRotation.pitch = torso pitchwriter (FUN_004b66b4) + mapper glance-look states (FUN_004afd10 — yaw/pitch from mech+0x564..0x570); (d) senders fillingDamage::damageForcefor directional bounce (random-fallback is binary-legal meanwhile). Also:deathAnimationLatched/legResetLatchwere 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
gBTWeaponTriggerbring-up. - Weapon groups / per-button fire channels (decomp-anchored 2026-07-09) — MechWeapon message
handlers id 9
ConfigureMappables(FUN_004b9550) and id 10ChooseButton(FUN_004b95b8), table @0x511860 region: ConfigureMappables binds the weapon'sfireImpulse@0x31C to mapper fire-channel #N (msg value 1-based; <1 unbinds; mapper = roster slot 0, vtable +0x38 register / +0x3c unregister; weapon+0x110 = the bound-state), ChooseButton re-binds (vtable +0x44) — the pod's in-cockpit weapon→button GROUPING. Weapons sharing a button fire together; grouping was a PILOT choice (heat management: alpha vs split). The port's SPACE=all-energy / CTRL=missiles split is a stand-in default; the authentic system needs the mapper channel registration + who SENDS the default ConfigureMappables (mission/player setup — not yet traced). Payoff: authentic weapon groups + a path to chain-fire. - 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 BTPlayermech+0x190+0x260, NOT 0xBD3 — see the mech+0x190 item above) +MoverAttachrouting 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).
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, anEntity::TakeDamageMessage{zone==−1}is dispatched to the victim (viaBTDispatchCollisionDamage, 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::ProcessCollisionListcalls the virtualProcessCollision→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), confirmingCylinderReferenceHeightreads a height, not the heat value themech+0x2ecdual-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.cppclasses now have real storage + a workingResolveHit→zone, the mech ctor loads the type-0x1d table by the DamageZoneStream name ([cyl] table 'bhk1' layers=7), andMech::TakeDamageMessageHandlerresolves unaimed (zone==−1) hits before base-routing. ⚠ The FIRST "verified" claim was PARTIAL: the table was cached viaWword(0x111)(the ABSORBER — stores nothing) so every unaimed hit silently no-op'd; fixed 2026-07-08 by promoting the cache to the named memberMech::damageLookupTable(reconstruction-gotchas §2). Now verified end-to-end (19 zones resolving,BT_AUTOFIREharness). 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 (reconStandingAnimation@Mech[0x111], but with an EMPTY name → 0 rows, becauseResourceFindByNameis 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 thinMech::TakeDamageMessageHandlerglue (zone==-1 → call the lookup →damageZones[zone]->TakeDamage) is unexported, and it's fully implied by pieces already reconstructed → no disassembly needed. Remaining: 6b realFindByName(name = the mech's type-0x14 DamageZoneStream name @ResourceDescription+0xc); 6c the 3 container ctors + the lookup as aBTCylinderResolveZonebridge (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-citedFUN_004a0230/FUN_0049ed0cdon't exist. - Death sequence — CORE DONE 2026-07-08 [T2]; effects+anim deferred.
Mech::UpdateDeathState()(mech4.cpp) reconstructs the death STATE machine (collapsemovementMode→ RPVTV::DeathShutdownsubsystem loop → settle to disabled/frozen; wreck stays, no crash) — runtime-verified (BT_DEATH_LOG). Full map + citations in combat-damage "Death SEQUENCE". ✅ (b)MechDeathHandlerDONE (mechdmg.cpp) + ✅ the render-side RemakeEntity destroyed-mesh swap DONE 2026-07-08 (btl4vid.cppRemakeEntityRenderables+BTRemakeMechModelbridge; in-placeSetDrawObjswap — 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 ModelListblhdead→ effect 104 → the victim becomes its<mech>dbr.bgfburning 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 theldbrdebris 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 intosubsystemArray(mech+0x128). Port: theDZSlotstand-in (always-null Resolve) replaced with the engineSlotOf<T>(binary vtables 0050bb84/0050bb7c ARE its instantiations); bind gated MasterInstance+DynamicFlag (simulationFlags@+0x28); replicant plugs stay unbound (master-authoritative).SendSubsystemDamagerewritten to the recovered @0049c9a8 logic: the unused crit allotment lands in the subsystem's OWN private 0x160 DamageZone (the oldSubProxy2cast to a mech zone was wrong), ≥1.0 → statusAlarm Destroyed + gated PrintState- zone valve, vital →
graphicAlarm 9(mech kill);CriticalHitnow calls the realApplyDamageAndMeasure(@4ac07c); the artifactparentArtifactZone.Addback-pointer bind also revived (LOD damage averaging).videoObjectFlagwasvitalSubsystem(+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.
- zone valve, vital →
- 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 byMech::TakeDamageMessageHandler). Remaining nuances, all cosmetic: (a)atten/attenvdistance 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/alphaWarpapplied ast^(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 (.mapin CONTENT/BT/MAPS/); would need compiling into a RES via the DOSbtl4tool.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).