diff --git a/game/reconstructed/btstubs.cpp b/game/reconstructed/btstubs.cpp index b9ead94..81fa1f5 100644 --- a/game/reconstructed/btstubs.cpp +++ b/game/reconstructed/btstubs.cpp @@ -153,7 +153,8 @@ void Mech::SetMappingSubsystem(Subsystem *subsystem) delete subsystemArray[0]; } subsystemArray[0] = subsystem; - controlsMapper = (MechControlsMapper *)subsystem; + // (task #7: no [0x10d] mirror -- the binary FUN_0049fe40 touches ONLY + // slot 0; the 0x434 cache belongs to the SubsystemMessageManager.) } // TODO(bring-up): raise a mech-level status alarm of the given id. diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 70b549e..5479383 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -389,6 +389,12 @@ template inline void ParseVector(A&&...) {} template inline int ParseJointResource(A&&...) { return 0; } template inline int CreateModelResourceBase(A&&...){ return 0; } template inline void StreamDamageZone(A&&...) {} + +// task #7: the factory-case enum under its true name (0xBD3 was mislabeled +// MechControlsMapperID; the mapper never streams). +enum { SubsystemMessageManagerID = 0xBD3 }; +class SubsystemMessageManager; +extern Subsystem *CreateMessageManagerSubsystem(Mech *owner, int id, void *seg); template inline void StreamDamageLookup(A&&...) {} @@ -988,7 +994,7 @@ Mech::Mech( Wword(0x12e) = (int)((Wword(0xba).max - Wword(0xba).min) * UpdatePositionScale); Wword(0xbb).field8 += (float)Wword(0x12e); weaponCount = 0; // Wword(0x112) - controlsMapper = 0; // Wword(0x10d) + messageManager = 0; // Wword(0x10d) // // Instantiate one Subsystem per segment, dispatching on its streamed @@ -1108,10 +1114,14 @@ Mech::Mech( ++weaponCount; break; - case MechControlsMapperID: // 0xBD3 - subsystemArray[id] = (Subsystem *) - new (Memory::Allocate(0x130)) MechControlsMapper(this, id, seg, MechControlsMapper::DefaultData); // FUN_0049bca4 - controlsMapper = (MechControlsMapper *)subsystemArray[id]; // Wword(0x10d) + case SubsystemMessageManagerID: // 0xBD3 -> the REAL class (task #7): + // the per-mech damage/explosion consolidation hub (ctor + // @0049bca4, 0x130 bytes, ConsolidateAndSendDamage Performance + // @0049b784). The old MechControlsMapper build here was the + // mapper/messmgr conflation -- the mapper never streams; it is + // installed into roster slot 0 by SetMappingSubsystem. + subsystemArray[id] = CreateMessageManagerSubsystem(this, id, seg); // FUN_0049bca4 + messageManager = (SubsystemMessageManager *)subsystemArray[id]; // Wword(0x10d) break; case GaussWeaponClassID: // 0xBD4 -> real Emitter (PPC, mislabeled) @@ -1541,6 +1551,18 @@ Mech::Mech( } dir->Add(this); // (**(dir[4]+4))(dir+4,this) + // SLOT-0 DEMAND LATCH (task #7; see BTBuildMapperDemandLatch): every mech + // gets a base mapper in roster slot 0 so the drive harness + replicant + // gait have a demand latch; the viewpoint mech's is replaced by the real + // device mapper (SetMappingSubsystem). + { + extern Subsystem *BTBuildMapperDemandLatch(Mech *mech); + if (GetSubsystemCount() > 0 && GetSubsystem(0) == 0) + { + subsystemArray[0] = BTBuildMapperDemandLatch(this); + } + } + // @0049f788 -- distribute coolant flow across the condensers (post-init pass). // The real RecomputeCondenserValves; sets each condenser's coolantFlowScale to // valveState/sum(valveState) so the ValveSetting gauge reads the authentic 1/N @@ -1877,12 +1899,12 @@ void Mech::WriteUpdateRecord(Simulation::UpdateRecord *message, int record_type) { // The binary stamps *(subsystemArray[0] + 0x128) = the controls mapper's - // speedDemand into every record tail. Our live mapper handle is - // controlsMapper (the [0x10d] cache); replicants have no pilot input. - // (Bridge fn: this TU carries a local recon stub under the mapper's name, - // so the bridge is type-erased; mechmppr.cpp casts to the real class.) + // speedDemand into every record tail -- roster slot 0, the mapper's one + // true home (task #7 untangle; the old [0x10d] cache is the message + // manager). (Bridge fn: this TU carries a local recon stub under the + // mapper's name, so the bridge is type-erased.) extern Scalar BTMapperSpeedDemandRaw(void *mapper); - Scalar speedDemand = BTMapperSpeedDemandRaw((void *)controlsMapper); + Scalar speedDemand = BTMapperSpeedDemandRaw((void *)MappingMapper()); static const int s_mrecLog = getenv("BT_REPL_LOG") ? 1 : 0; if (s_mrecLog && record_type != 0) diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index 9dd6ecf..1ced0d5 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -529,6 +529,15 @@ struct ShotDescriptor // Gyro accessor (task #56): the firing-recoil kick (projweap.cpp, // binary @4bc194) reads mech+0x528. Subsystem *GetGyroSubsystem() { return gyroSubsystem; } + // The mapper's ONE true home (task #7): roster slot 0 -- the binary's + // SetMappingSubsystem @0049fe40 installs there, and every binary + // mapper consumer reads **(mech+0x128). Null-safe: the binary never + // ran these paths mapper-less; our reachability is wider. + MechControlsMapper *MappingMapper() + { + return (GetSubsystemCount() > 0) + ? (MechControlsMapper *)GetSubsystem(0) : 0; + } Subsystem *GetHudSubsystem() { return hudSubsystem; } // Reachable horizontal firing half-arc (radians) the mech's torso can bring // its guns to bear -- the wider torso twist limit, or 0 for a fixed torso. @@ -543,7 +552,15 @@ struct ShotDescriptor // controls mapper (@004afd10 writes cockpit+0x28c -- past MechTech's alloc, // inside the HUD's). Was misnamed mechTechSubsystem. Subsystem *hudSubsystem; // @0x5b4 this[0x16d] (0xBD6 -> real HUD) - MechControlsMapper *controlsMapper; // @0x434 this[0x10d] (0xBD3) + // THE UNTANGLE (task #7): binary [0x10d]/mech+0x434 caches the + // SubsystemMessageManager (0xBD3, the per-mech damage/explosion + // consolidation hub, ctor @0049bca4) -- NOT a controls mapper. The + // binary-wide census found exactly ONE reader (@0x4b984b in + // MechWeapon::SendDamageMessage @004b9728) and it is pure + // message-manager semantics. The old `controlsMapper` name/type was + // the misread that made the whole drive lean on this cache. The + // REAL mapper's one true home is roster slot 0 (MappingMapper()). + class SubsystemMessageManager *messageManager; // @0x434 this[0x10d] (0xBD3) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Pose / kinematics (subset touched by the recovered slice) diff --git a/game/reconstructed/mech2.cpp b/game/reconstructed/mech2.cpp index 7e820ac..4e07258 100644 --- a/game/reconstructed/mech2.cpp +++ b/game/reconstructed/mech2.cpp @@ -422,9 +422,10 @@ Scalar return 0.0f; // The binary reads edx = *(mech+0x128) then [edx]+0x128: subsystemArray[0] - // (the roster's ControlsMapper slot) -> speedDemand. controlsMapper is the - // typed mirror of that slot; null (no mapper) reads demand 0 -> the mech idles. - const Scalar spd = (m->controlsMapper != 0) ? m->controlsMapper->speedDemand : 0.0f; + // (the roster's ControlsMapper slot 0) -> speedDemand; null (no mapper) + // reads demand 0 -> the mech idles. (task #7: read the REAL slot-0 mapper.) + MechControlsMapper *mppr2 = m->MappingMapper(); + const Scalar spd = (mppr2 != 0) ? mppr2->speedDemand : 0.0f; const Scalar fcr = m->forwardCycleRate; // 0x344 const Scalar gts = m->globalTimeScale; // 0x5a8 const Scalar cyc = m->legCycleSpeed; // 0x348 @@ -528,8 +529,9 @@ Scalar // RECONCILED: the old draft double-deref'd the never-initialized controlSource // alias (an AV); controlsMapper is the typed mirror of roster slot 0. A mech // with no mapper reads demand 0 (idles) -- matching a zeroed binary roster. + MechControlsMapper *mppr = MappingMapper(); // roster slot 0 (task #7) Scalar commandedSpeed = - (controlsMapper != 0) ? controlsMapper->speedDemand : 0.0f; + (mppr != 0) ? mppr->speedDemand : 0.0f; Scalar distance = 0.0f; // binary: legAnimationState@0x3b0 IS legStateAlarm's level (one field; the @@ -585,10 +587,10 @@ Scalar // Standing on a live turn demand with no speed demand. The clip's // finished-callback drops back to stand; while the demand persists // this trigger re-arms it (a looping pivot step). - if (hasCrashSet != 0 && controlsMapper != 0 + if (hasCrashSet != 0 && mppr != 0 && ZeroSpeed <= commandedSpeed - && (controlsMapper->turnDemand > 0.05f - || controlsMapper->turnDemand < -0.05f)) + && (mppr->turnDemand > 0.05f + || mppr->turnDemand < -0.05f)) { SetLegAnimation(4); // turn-in-place (trn) goto advance_normally; diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index ebfa919..af416fa 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1376,7 +1376,8 @@ void // cadence matches the replicated speed (residual foot-slip is the // inherent dead-reckoning artifact). The state machine self-arms // stand->walk / winds down from the demand exactly as on the master. - if (!IsMechDestroyed() && controlsMapper != 0) + MechControlsMapper *replMppr = MappingMapper(); // roster slot 0 (task #7) + if (!IsMechDestroyed() && replMppr != 0) { const Vector3D &wv = updateVelocity.linearMotion; float spd = sqrtf((float)(wv.x * wv.x + wv.z * wv.z)); @@ -1384,7 +1385,7 @@ void localToWorld.GetFromAxis(Z_Axis, &zAxR); const float fdot = -((float)wv.x * (float)zAxR.x + (float)wv.z * (float)zAxR.z); // mech faces -Z - controlsMapper->speedDemand = (fdot < 0.0f) ? -spd : spd; + replMppr->speedDemand = (fdot < 0.0f) ? -spd : spd; // Prime the same clip-advance scalars the master's gait block sets // each frame -- uninitialized on a replicant they read 0, freezing // the clip at advance-time dt*0 (observed: legState engaged at 11, @@ -1843,22 +1844,23 @@ void // mode clamps, torso axes, HUD free-aim) is the real reconstructed tick, // which runs in the subsystem-roster walk below (un-skipped under this env). static const int s_realControls = BTEnvOn("BT_REAL_CONTROLS", 1); // default ON (=0 to disable) - if (s_realControls && controlsMapper != 0) + MechControlsMapper *mppr = MappingMapper(); // roster slot 0 (task #7) + if (s_realControls && mppr != 0) { // Diagnostic: what the ENGINE controls push left in the attribute since // our last write (a stale device element overwriting the bridge shows // here as pre != our previous write). - float preThrottle = controlsMapper->throttlePosition; + float preThrottle = mppr->throttlePosition; (void)preThrottle; - controlsMapper->throttlePosition = (throttle >= 0.0f) ? throttle : -throttle; - controlsMapper->reverseThrust = (throttle < 0.0f) ? 1 : 0; // ControlsButton: >=1 engaged - controlsMapper->stickPosition.x = turn; // Basic mode: turn = stick yaw - controlsMapper->stickPosition.y = 0.0f; + mppr->throttlePosition = (throttle >= 0.0f) ? throttle : -throttle; + mppr->reverseThrust = (throttle < 0.0f) ? 1 : 0; // ControlsButton: >=1 engaged + mppr->stickPosition.x = turn; // Basic mode: turn = stick yaw + mppr->stickPosition.y = 0.0f; // Consume the PREVIOUS frame's interpreted demands (the mapper ticks in // the roster walk after this block -- one frame of input latency). // turnDemand is the mode-shaped steering; speedDemand (world u/s, sign = // reverse) feeds the gait target below. - turn = controlsMapper->turnDemand; + turn = mppr->turnDemand; // BT_GOTO steering must reach the orientation integration DIRECTLY: the // mapper round-trip (stickPosition -> turnDemand) zeroes out in -net mode // (the key-bridge only shapes the local viewpoint mech there), which froze @@ -1871,15 +1873,15 @@ void if (s_mpprLog >= 1.0f) { s_mpprLog = 0.0f; - DEBUG_STREAM << "[mppr] in thr=" << controlsMapper->throttlePosition + DEBUG_STREAM << "[mppr] in thr=" << mppr->throttlePosition << " pre=" << preThrottle - << " rev=" << controlsMapper->reverseThrust - << " stickX=" << controlsMapper->stickPosition.x - << " -> speedDemand=" << controlsMapper->speedDemand - << " turnDemand=" << controlsMapper->turnDemand - << " mode=" << controlsMapper->controlMode - << " mapper=" << (void*)controlsMapper - << " &mode=" << (void*)&controlsMapper->controlMode << "\n" << std::flush; + << " rev=" << mppr->reverseThrust + << " stickX=" << mppr->stickPosition.x + << " -> speedDemand=" << mppr->speedDemand + << " turnDemand=" << mppr->turnDemand + << " mode=" << mppr->controlMode + << " mapper=" << (void*)mppr + << " &mode=" << (void*)&mppr->controlMode << "\n" << std::flush; } } @@ -2018,8 +2020,8 @@ void // while the animation transitioned gradually (the "physics don't // line up with the animation" desync). Signed, the SM decelerates // through stop -> reverse-entry exactly like the leg channel. - if (s_realControls && controlsMapper != 0) - bodyTargetSpeed = controlsMapper->speedDemand; + if (s_realControls && MappingMapper() != 0) + bodyTargetSpeed = MappingMapper()->speedDemand; else bodyTargetSpeed = ((throttle < 0.0f) ? -1.0f : 1.0f) * (wantGait ? reverseSpeedMax : walkStrideLength); @@ -2593,8 +2595,8 @@ void // Commanded-speed deadband (binary @0x4aac88): the mapper's live // speedDemand vs the last-replicated bodyTargetSpeed -> the tiny // type-2 record. This is the AUTHENTIC replicant-gait feed. - if (controlsMapper != 0 - && controlsMapper->speedDemand != bodyTargetSpeed) + if (MappingMapper() != 0 + && MappingMapper()->speedDemand != bodyTargetSpeed) { ForceUpdate(1 << MechSpeedUpdateModelBit); // type 2 } @@ -3229,13 +3231,13 @@ void Subsystem *subsystem = subsystemArray[i]; if (subsystem == 0) continue; - if (subsystem != (Subsystem *)controlsMapper) + if (i != 0) // slot 0 = the mapper (task #7) ++subsystemsPresent; if (!subsystem->IsNonReplicantExecutable()) continue; // The controls-mapping subsystem (roster slot 0 via Mech::SetMapping - // Subsystem, mirrored to controlsMapper) is TICKED under BT_REAL_CONTROLS + // Subsystem -- the [0x10d] mirror is GONE, task #7) is TICKED under BT_REAL_CONTROLS // -- its InterpretControls chain is now reconciled: FillPilotArray reads // the local player via application->GetMissionPlayer() (the old wild // application+0x6c read was THE bypass-causing AV), and the main tick @@ -3245,7 +3247,7 @@ void // stands (default behavior unchanged). { static const int s_realControlsTick = BTEnvOn("BT_REAL_CONTROLS", 1); - if (!s_realControlsTick && subsystem == (Subsystem *)controlsMapper) + if (!s_realControlsTick && i == 0) // slot 0 = the mapper (task #7) continue; } diff --git a/game/reconstructed/mechmppr.cpp b/game/reconstructed/mechmppr.cpp index e5e0af0..3e2709d 100644 --- a/game/reconstructed/mechmppr.cpp +++ b/game/reconstructed/mechmppr.cpp @@ -1242,6 +1242,31 @@ void *BTResolveRosterPilot(int slot) // sees only the forward declaration of MechControlsMapper, so the read // lives here in the complete-type TU (the house databinding pattern). // +//########################################################################### +// BTBuildMapperDemandLatch -- slot-0 latch (task #7) +// +// The binary leaves roster slot 0 NULL except on the viewpoint mech +// (MakeViewpointEntity installs the real device mapper via +// SetMappingSubsystem @0049fe40). Our port drives NON-viewpoint masters +// (the BT_GOTO/BT_AUTODRIVE harness) and animates replicant gait through +// mapper demand fields, so every mech gets a default-data BASE mapper as a +// demand LATCH at ctor end; SetMappingSubsystem deletes + replaces it on +// the viewpoint mech exactly as the binary replaces slot 0. [T3 +// accommodation -- relocates the retired 0xBD3 squatter's role to the slot +// the binary actually reads.] Complete-type TU (the mech.cpp caller +// carries a local stub under the mapper's name). +// +Subsystem *BTBuildMapperDemandLatch(Mech *mech) +{ + static MechControlsMapper::SubsystemResource latch_resource; + Str_Copy(latch_resource.subsystemName, "ControlsMapper", + sizeof(latch_resource.subsystemName)); + latch_resource.classID = (RegisteredClass::ClassID)0xf; // TrivialSubsystemClassID (btl4app idiom) + latch_resource.subsystemModelSize = sizeof(latch_resource); + return new MechControlsMapper(mech, 0, &latch_resource, + MechControlsMapper::DefaultData); +} + Scalar BTMapperSpeedDemandRaw(void *mapper) { // void* signature: mech.cpp's TU carries a local recon stub type under the diff --git a/game/reconstructed/messmgr.cpp b/game/reconstructed/messmgr.cpp index e415778..96447a7 100644 --- a/game/reconstructed/messmgr.cpp +++ b/game/reconstructed/messmgr.cpp @@ -486,3 +486,17 @@ void // resources beyond the base. // // SubsystemMessageManager::DamageInformation::~DamageInformation() {} // @0049c3e3 + +//########################################################################### +// CreateMessageManagerSubsystem -- factory bridge (task #7) +// +// mech.cpp's streamed-subsystem factory (case 0xBD3) calls this in the +// house complete-type-TU pattern (cf. CreateEmitterSubsystem et al). +// +Subsystem *CreateMessageManagerSubsystem(Mech *owner, int id, void *seg) +{ + return new (Memory::Allocate(0x130)) SubsystemMessageManager( + owner, id, + (SubsystemMessageManager::SubsystemResource *)seg, + SubsystemMessageManager::DefaultData); +}