From 29035028fdb4ab0864f9bd9a38588c923aa94329 Mon Sep 17 00:00:00 2001 From: arcattack Date: Thu, 9 Jul 2026 19:04:34 -0500 Subject: [PATCH] Cross-pod beams: replicate emitter discharge via subsystem update records User report: lasers only visible on the window firing them. The peer's replicant emitters never learned the master fired. THE AUTHENTIC PIPE (decomp-verified): - FUN_0041c350 (the "beam keepalive" ServiceDischarge/ContinueDischarge call) does TWO things: queue the LOCAL deferred beam-effect callback (@0x4bac0c) on the app+0x34 manager -- our per-weapon render walk already plays that role -- and set the subsystem DIRTY bit, which maps to the 2007 engine's updateModel / ForceUpdate(). - Replication rides SUBSYSTEM UPDATE RECORDS inside the mech's update message: the roster walk already hands the entity's stream to every subsystem's PerformAndWatch; Simulation::WriteSimulationUpdate serializes each requested updateModel bit; Entity::UpdateMessageHandler routes received records by subsystemID to the subsystem's ReadUpdateRecord. All engine machinery -- the missing pieces were the Emitter's serialize/apply pair + the triggers. CORRECTIONS to the dormant task-33-era transcriptions (never exercised -- nothing ever set updateModel -- so the latent misreads never surfaced): - The weapon-family VTABLE SLOT MAP was swapped: slot 6 = ReadUpdateRecord, 7 = WriteUpdateRecord, 9 = TakeDamage (evidence: Mech hierarchy symmetry + body semantics; @004ba568 resolves an EntityID at rec+0x30 through the entity index -- record semantics, not Damage). Renamed across MechWeapon / Emitter / ProjectileWeapon; the real Emitter::TakeDamage @004bafc8 is undecoded (inherits MechWeapon for now). - Emitter/MechWeapon Write: `*record = 0x38/0x18` is the record LENGTH, not recordID; rec+0x30 is the TARGET's EntityID (GetEntityID()), not a colour -- the old `CopyColor(targetEntity+0x184)` was also a databinding trap. - OVERRIDE-SIGNATURE TRAP: the decls used each class's own shadowing UpdateRecord typedef as the param type, silently NOT overriding the engine virtual (the base ran instead; nothing would ever have serialized). Base-typed params (Simulation__UpdateRecord*), casts inside. - Emitter::ReadUpdateRecord reconstructed (@004ba568): target EntityID resolve (drop unknown non-null targets), MechWeapon alarm apply chain, beam fields. - ServiceDischarge/ContinueDischarge: ForceUpdate() per keepalive tick + one final record at beam end (turns the peer's beam off). - Mech::DrawWeaponBeams extracted from the player-only drive block so the walk runs for REPLICANTS (+ per-mech gun-port cache -- the process-wide statics would have served the player's segment pointers as the replicant's muzzles). VERIFIED 2-node: A fires 57 volleys -> 225 emitter records -> B applies all 225 -> B draws 414 beams (PPC blue / laser red, from A's replicant's own gun ports). Solo un-regressed (150 beams, kill chain, no crash). Also preserved: the full Mech::WriteUpdateRecord @0x4a0c2c recovery (reference/decomp/mech_writeupdate_004a0c2c.disasm.txt) with all 9 record types decoded (pose/alarm/leg-state+heat with the body-channel write-through re-sync, knockdown, death, impact, movementMode) -- transcription deferred; it replicates remote knockdown/death/heat and was not needed for beams. Co-Authored-By: Claude Fable 5 --- context/multiplayer.md | 26 +- game/reconstructed/emitter.cpp | 106 ++-- game/reconstructed/emitter.hpp | 38 +- game/reconstructed/heat.hpp | 1 + game/reconstructed/mech.hpp | 5 + game/reconstructed/mech4.cpp | 187 ++++--- game/reconstructed/mechweap.cpp | 62 ++- game/reconstructed/mechweap.hpp | 29 +- game/reconstructed/projweap.cpp | 10 +- game/reconstructed/projweap.hpp | 13 +- .../mech_writeupdate_004a0c2c.disasm.txt | 467 ++++++++++++++++++ 11 files changed, 764 insertions(+), 180 deletions(-) create mode 100644 reference/decomp/mech_writeupdate_004a0c2c.disasm.txt diff --git a/context/multiplayer.md b/context/multiplayer.md index 690e681..6476f19 100644 --- a/context/multiplayer.md +++ b/context/multiplayer.md @@ -181,10 +181,28 @@ transition, HUD all landed since P6): console egg → mesh → RunningMission on 3. **Victim visuals on the shooter's screen**: the wreck swap gates on zone/graphic state the REPLICANT copy may never see (zone damage may not replicate — only pose updates confirmed). Check DamageZone/subsystem state in the update stream; else replicate the death as an event. -4. **Cross-pod beam visuals**: replicant emitters don't run the local trigger sim, so the peer's - beams never draw today. ⭐ The authentic path exists: ServiceDischarge/ContinueDischarge SEND - **beam-keepalive messages** (`FUN_0041c350`, templates @0x511e6c/@0x511e78, emitter.cpp already - stubs them) — decode + route those to drive replicant beam draws. +4. ✅ **Cross-pod beam visuals — DONE (task #51, 2026-07-09).** The keepalive theory needed one + correction: `FUN_0041c350` = (a) queue the LOCAL deferred beam-effect callback (@0x4bac0c) on the + app+0x34 manager (our per-weapon render walk plays that role) AND (b) set the subsystem DIRTY bit — + which maps to the 2007 engine's `updateModel`/`ForceUpdate()`. The REPLICATION rides **subsystem + update records inside the mech's update message**: the roster walk passes the entity's stream to + every subsystem's `PerformAndWatch`; `Emitter::WriteUpdateRecord` (@004ba65c, corrected: record + LENGTH not recordID; rec+0x30 = the TARGET's **EntityID** not a colour) serializes + firingActive/beamEndpoint/beamFlag/targetID; the engine's `Entity::UpdateMessageHandler` routes + records by `subsystemID` to `Emitter::ReadUpdateRecord` (@004ba568 — was MIStranscribed as + TakeDamage; the whole weapon-family slot map was swapped: slot 6=Read, 7=Write, 9=TakeDamage — see + mechweap.hpp). `ServiceDischarge`/`ContinueDischarge` call `ForceUpdate()` per keepalive tick + + once at beam end. The render walk was extracted to `Mech::DrawWeaponBeams` (per-mech port cache) + and runs for REPLICANTS. ⚠ two silent-failure traps fixed: the overrides originally used the + class's own shadowing `UpdateRecord` typedef as the param type → never overrode the engine virtual + (base ran, nothing serialized); and `recordLength` must be each level's sizeof or the stream + framing misparses. VERIFIED 2-node: A fires 57 volleys → 225 records → B applies 225 → draws 414 + replicant beams (PPC blue / laser red from A's replicant's gun ports). Solo un-regressed. + REMAINING (deferred): the MECH-level writer @0x4a0c2c (recovered + preserved: + `reference/decomp/mech_writeupdate_004a0c2c.disasm.txt`; 9 record types decoded — 0/1/4 pose + variants, 2 alarm, 3 leg-state+heat with body-channel write-through re-sync, 5 knockdown + (SetBodyAnimation(0x20) write-through), 6 death, 7 impact, 8 movementMode) — transcribing it + would replicate remote KNOCKDOWN/death/heat states; beams did not need it. 5. **2-window driving**: input gates on window focus (alternate windows; `BT_KEY_NOFOCUS` for harness). DEATHS scoring should light via the existing BTPostKillScore MP branch. Respawn = the P5 teardown debt (deferred until needed). diff --git a/game/reconstructed/emitter.cpp b/game/reconstructed/emitter.cpp index c08d50d..7ff51ed 100644 --- a/game/reconstructed/emitter.cpp +++ b/game/reconstructed/emitter.cpp @@ -72,6 +72,7 @@ #if !defined(TESTBT_HPP) # include #endif +#include // HostManager::GetEntityPointer -- the update-record target resolve (task #51) // E8 (bring-up): the player fire input. The controls mapper that would normally write // the weapon's fireImpulse is bypassed (mech4.cpp), so EmitterSimulation reads this @@ -413,11 +414,21 @@ void beamFlag = 0; // 0x46c dischargeTimer = dischargeTime; // 0x440 = 0x43c SetDirty(); + // replicate the beam END: one final Emitter update record turns the + // peer's copy off (the 1995 flush wrote idle-flagged subsystems too). + ForceUpdate(); return; } - // keep the beam alive for another frame (re-send the beam message). - // CROSS-FAMILY (messaging): SendMessage(PTR_LAB_00511e6c,..) -- FUN_0041c350. + // keep the beam alive for another frame -- BEAM REPLICATION (task #51): + // the 1995 keepalive (FUN_0041c350, template @0x511e6c) marked the + // subsystem dirty + queued the local deferred beam-effect callback; the + // local draw is our per-weapon render walk, and the dirty-mark maps to the + // engine's updateModel bit -- the next PerformAndWatch serializes THIS + // emitter's record (@004ba65c) into the mech's update stream, and the + // peer's replicant emitter applies it (@004ba568) so its own walk draws + // the beam. + ForceUpdate(); (void)PTR_LAB_00511e6c; (void)DAT_00511e70; (void)DAT_00511e74; } @@ -449,7 +460,9 @@ void ResetFiringState(); // @004ba9a8 return; } - // re-send the beam-keepalive message (CROSS-FAMILY messaging, FUN_0041c350) + // re-send the beam keepalive -- the engine's updateModel bit (task #51, + // same mapping as ServiceDischarge above; 1995 template @0x511e78). + ForceUpdate(); (void)PTR_LAB_00511e78; (void)DAT_00511e7c; (void)DAT_00511e80; } } @@ -552,45 +565,52 @@ void // // -// @004ba568 -- slot 6. Resolve the incoming damage's source subsystem (the -// VoltageSource link), then apply the base MechWeapon damage and replicate the -// extra Emitter fields (firingActive, beam endpoint, target). A "no-source" -// damage clears the link and sets the alarm to 2 (Loaded) / 0 (active). +// @004ba568 -- slot 6 (TASK #51 RENAME: was mistranscribed as TakeDamage; the +// body is unambiguous RECORD semantics -- it compares rec+0x30 against +// EntityID::Null (DAT_00522524), resolves it through the entity index, and +// applies alarm/beam fields). Apply an Emitter update record on the +// REPLICANT: resolve the beam TARGET id (drop the record when a non-null id is +// unknown on this host), chain the MechWeapon read (@004b964c, alarm apply), +// then the beam-replication fields. The tail `this+0x10 = this+0x14` is the +// 1995 Simulation lastUpdate=lastPerformance resync (the earlier transcription +// misread it as rechargeLevel=effectiveRange) -- the engine base read already +// maintains lastUpdate. // void - Emitter::TakeDamage(Damage &damage) + Emitter::ReadUpdateRecord(Simulation__UpdateRecord *message) { - int src = FUN_00421070(&damage, DAT_00522524); // damage source (damage+0x30) - if (src == 0) + Emitter__UpdateRecord *rec = (Emitter__UpdateRecord *)message; + + if (rec->targetID == EntityID::Null) // FUN_00421070 vs DAT_00522524 { targetEntity = 0; // 0x474 } else { - // look up the named source through the global subsystem registry - targetEntity = (Entity *)ResolveSource(&damage); // this+0x474 + targetEntity = (application != 0) + ? application->GetHostManager()->GetEntityPointer(rec->targetID) + : 0; if (targetEntity == 0) { - return; + return; // unknown target -> drop } } - MechWeapon::TakeDamage(damage); // FUN_004b964c + MechWeapon::ReadUpdateRecord(message); // FUN_004b964c (alarm apply) - // CROSS-FAMILY (replication): the shipped Emitter also pulled its beam state - // (firingActive, beamFlag, beamEndpoint, beamColor, targetLocalFlag) out of the - // networked damage record. Those fields are not present on the engine Damage; - // they arrive through the update-record path (WriteUpdateRecord) instead. - if (firingActive == 0) + firingActive = rec->firingActive; // 0x418 <- rec+0x18 + weaponAlarm.SetLevel(firingActive != 0 ? 0 : 2); // 0=Firing / 2=Loaded + beamFlag = rec->beamFlag; // 0x46c <- rec+0x28 + beamEndpoint = rec->beamEndpoint; // 0x460 <- rec+0x1c + targetLocalFlag = rec->targetLocalFlag; // 0x470 <- rec+0x2c + ClearDirty(); // this+0x28 &= ~2 (idle bit clear) + if (getenv("BT_BEAM_LOG")) { - weaponAlarm.SetLevel(2); // Loaded + static int s_rd = 0; + if ((s_rd++ % 60) == 0) + DEBUG_STREAM << "[emit-rd] " << (void*)this << " firing=" << firingActive + << " beamFlag=" << beamFlag << "\n" << std::flush; } - else - { - weaponAlarm.SetLevel(0); // Firing - } - ClearDirty(); // this+0x28 &= ~2 - rechargeLevel = effectiveRange; // this+0x10 = this+0x14 (best-effort) } // @@ -599,23 +619,31 @@ void // endpoint, beamFlag and the target's colour tag. // void - Emitter::WriteUpdateRecord(UpdateRecord *message, int update_model) + Emitter::WriteUpdateRecord(Simulation__UpdateRecord *message, int update_model) { MechWeapon::WriteUpdateRecord(message, update_model); // FUN_004b9690 - message->recordID = 0x38; // *message - message->firingActive = firingActive; // message[6] <- 0x418 - message->targetLocalFlag= targetLocalFlag; // message[10] <- 0x470 - message->beamEndpoint = beamEndpoint; // message[7] <- 0x460 - message->beamFlag = beamFlag; // message[11] <- 0x46c - - if (targetEntity == 0) // 0x474 + Emitter__UpdateRecord *rec = (Emitter__UpdateRecord *)message; + // DISASM CORRECTIONS (task #51): `*param_2 = 0x38` writes the record + // LENGTH, not recordID; and rec+0x30 receives the TARGET's EntityID + // (FUN_00420ef4 copies from targetEntity+0x184 = the 1995 entityID -- + // the old `CopyColor(...targetEntity+0x184)` was both a misread AND a + // databinding trap in our compiled Entity layout). + rec->recordLength = sizeof(Emitter__UpdateRecord); // *message = 0x38 + rec->firingActive = firingActive; // rec+0x18 <- 0x418 + rec->beamEndpoint = beamEndpoint; // rec+0x1c <- 0x460 + rec->beamFlag = beamFlag; // rec+0x28 <- 0x46c + rec->targetLocalFlag = targetLocalFlag; // rec+0x2c <- 0x470 + rec->targetID = (targetEntity != 0) + ? ((Entity *)targetEntity)->GetEntityID() + : EntityID::Null; // rec+0x30 + if (getenv("BT_BEAM_LOG")) { - CopyColor(&message->color, DAT_00522524); // FUN_00420ef4 - } - else - { - CopyColor(&message->color, (void*)((char*)targetEntity + 0x184)); + static int s_wr = 0; + if ((s_wr++ % 60) == 0) + DEBUG_STREAM << "[emit-wr] " << (void*)this << " firing=" << firingActive + << " beamFlag=" << beamFlag << " subsysID=" << (int)rec->subsystemID + << " len=" << (int)rec->recordLength << "\n" << std::flush; } } diff --git a/game/reconstructed/emitter.hpp b/game/reconstructed/emitter.hpp index 7c3c6ad..e30fb78 100644 --- a/game/reconstructed/emitter.hpp +++ b/game/reconstructed/emitter.hpp @@ -97,12 +97,18 @@ class NotationFile; struct Emitter__UpdateRecord: public MechWeapon::UpdateRecord { - int firingActive; // message[6] - int targetLocalFlag; // message[10] - Point3D beamEndpoint; // message[7] - int beamFlag; // message[11] - RGBColor color; - int payloadCount; // message+0xc (AdvanceSeekVoltage) + int firingActive; // rec+0x18 <- 0x418 + Point3D beamEndpoint; // rec+0x1c <- 0x460 + int beamFlag; // rec+0x28 <- 0x46c + int targetLocalFlag; // rec+0x2c <- 0x470 + // DISASM CORRECTION (task #51, @004ba65c/@004ba568): rec+0x30 is the + // TARGET's EntityID -- FUN_00420ef4 (an EntityID copy) from + // targetEntity+0x184 (the 1995 Entity::entityID) or DAT_00522524 + // (EntityID::Null) -- NOT an RGB colour. The reader resolves it + // through the entity index and DROPS the record when a non-null id + // is unknown on this host. + EntityID targetID; // rec+0x30 + int payloadCount; // (AdvanceSeekVoltage) }; typedef Emitter__UpdateRecord UpdateRecord; @@ -160,9 +166,14 @@ class NotationFile; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem / Simulation virtual overrides (vtable @00512078) // - // Slot indices verified against the MechWeapon vtable @00511d2c: - // slot 6 TakeDamage (MechWeapon base @004b964c) - // slot 7 WriteUpdateRecord (MechWeapon base @004b9690) + // SLOT MAP CORRECTED (task #51): slot 6 is ReadUpdateRecord, not TakeDamage + // (@004ba568 resolves an EntityID at rec+0x30 through the entity index and + // applies alarm/beam fields -- record semantics; it chains @004b964c, the + // MechWeapon slot-6 read). The real Emitter::TakeDamage is slot 9 + // @004bafc8 (undecoded -- inherit MechWeapon::TakeDamage until recovered). + // slot 6 ReadUpdateRecord @004ba568 (MechWeapon base @004b964c) + // slot 7 WriteUpdateRecord @004ba65c (MechWeapon base @004b9690) + // slot 9 TakeDamage @004bafc8 (NOT yet reconstructed) // slot 10 ResetToInitialState (MechWeapon base @004b96ec) // slot 13 PrintState (MechWeapon base @004b9d00) // slot 16 ReadyToDischarge (Simulation interface) @@ -171,7 +182,7 @@ class NotationFile; // public: void - TakeDamage(Damage &damage); // slot 6, @004ba568 + ReadUpdateRecord(Simulation__UpdateRecord *message); // slot 6, @004ba568 void ResetToInitialState(); // slot 10, @004ba4d0 void @@ -184,12 +195,13 @@ class NotationFile; int BeamOn() const { return beamFlag; } // 0x46c const Point3D &BeamEndpoint() const { return beamEndpoint; } // 0x460 - protected: + public: void WriteUpdateRecord( // slot 7, @004ba65c - UpdateRecord *message, - int update_model + Simulation__UpdateRecord *message, + int update_model ); + protected: // @004ba6e0 -- True when the supplied (or linked) voltage source can // satisfy the current seekVoltage index and is in the "ready" state (2). diff --git a/game/reconstructed/heat.hpp b/game/reconstructed/heat.hpp index 2e8451a..90134bf 100644 --- a/game/reconstructed/heat.hpp +++ b/game/reconstructed/heat.hpp @@ -96,6 +96,7 @@ public: void SetLevel(int n) { level = n; } int GetLevel() const { return level; } int Level() const { return level; } + int LevelCountB() const { return levelB; } // +0x10 (the weapon update-record "reset" source) // ReconAlarm/AlarmIndicator API aliases (callers that predate the retype use these): void SetState(unsigned n){ level = (int)n; } unsigned GetState() const { return (unsigned)level; } diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index fe853d5..67342bc 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -487,6 +487,11 @@ public: // -> world hit point. Defined in mech.cpp. Logical PickRayHit(const Point3D &start, const Vector3D &dir, Scalar max_range, Point3D *hit_world); + + // Per-weapon beam render walk (task #33; extracted task #51 so it runs for + // EVERY mech -- player, dummy masters, and MP replicants whose emitters + // carry replicated discharge state). Defined in mech4.cpp. + void DrawWeaponBeams(Scalar dt); protected: //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 04c0ed0..c64f173 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1237,6 +1237,11 @@ void if (legCycleSpeed < -100.0f || legCycleSpeed > 200.0f) legCycleSpeed = 0.0f; (void)AdvanceLegAnimation(dt); // joints only; travel = DeadReckon + + // REPLICANT BEAMS (task #51): the emitters carry live replicated + // discharge state (Emitter::ReadUpdateRecord); draw them with the + // same per-weapon walk the master uses. + DrawWeaponBeams(dt); } if (getenv("BT_REPL_LOG")) @@ -2461,88 +2466,7 @@ void // PPCs) instead of the old hardcoded single-look stagger, so every // mech type fires like its data says. (Old stagger/keepalive block // removed; see the git history for the bring-up scaffolding.) - { - extern void BTPushBeam(float,float,float, float,float,float, unsigned, float, float); - const Scalar ttl1 = (dt > 1e-4f) ? dt : 1e-4f; // one-frame life (redrawn while on) - static int s_beamStateLog = 0; - int energyOrdinal = -1; // Nth energy weapon (port assignment) - for (int wi = 0; wi < GetSubsystemCount(); ++wi) - { - Subsystem *ws = GetSubsystem(wi); - if (ws == 0) - continue; - // EXACT class filter: Emitter (0xBC8=3016) or PPC (0xBD4=3028). - // The derivation check matched too broadly here (the recon - // derivation chains are shared stubs for some subsystems -- - // a Sensor and the MissileLauncher passed and drew garbage - // from misinterpreted offsets). - const int wcid = (int)ws->GetClassID(); - if (wcid != 3016 && wcid != 3028) - continue; - ++energyOrdinal; - Emitter *em = (Emitter *)ws; - if (!em->BeamOn()) - continue; - Point3D mz; - em->MuzzlePoint(mz); // LIVE muzzle (tracks the gun) - // MOUNT FALLBACK: when the weapon's mount segment doesn't - // resolve, GetMuzzlePoint returns the mech ORIGIN (feet). - // Assign this weapon its own gun-port segment by roster - // ordinal (the same port set the old visual used) so each - // energy weapon keeps a stable muzzle on the arms. - if (mz.y - localOrigin.linearPosition.y < 1.0f) - { - static const char *const kGunPorts[] = - { "siterugunport", "sitelugunport", "siterdgunport", - "siteldgunport", "siterbgunport", "sitelbgunport" }; - static EntitySegment *s_portCache[64]; - static int s_portTried[64]; - if (energyOrdinal >= 0 && energyOrdinal < 64) - { - if (!s_portTried[energyOrdinal]) - { - s_portTried[energyOrdinal] = 1; - s_portCache[energyOrdinal] = GetSegment( - CString(kGunPorts[energyOrdinal % 6])); - } - if (s_portCache[energyOrdinal] != 0) - { - AffineMatrix mw; - mw.Multiply(s_portCache[energyOrdinal]->GetSegmentToEntity(), - localToWorld); - mz = mw; // Point3D = matrix translation - } - } - } - const Point3D &bend = em->BeamEndpoint(); // the fire's world hit point - // authored per-weapon colour; unset (-1) -> the ER-laser red - RGBColor pc = em->PipColor(); - float r = (float)pc.Red, g = (float)pc.Green, b = (float)pc.Blue; - if (r < 0.0f || g < 0.0f || b < 0.0f) { r = 0.78f; g = 0.08f; b = 0.02f; } - // PPC (classID 0xBD4 = 3028): a thicker, brighter bolt than a laser tube. - const int isPPC = ((int)em->GetClassID() == 3028); - // ONE draw per beam, at the model's NATURAL width: the weapon's - // own tube (ERMLASER radius 0.22u, PPC bolt 0.62u) IS the beam - // -- the old inflated two-layer glow/core (3.0x + 0.9x widths) - // drew fat cartoon cylinders 13x the authored size. The tint - // modulates the scrolling grit; thin natural tubes stay under - // saturation without a hand-dimmed core. - extern void BTPushBeamKind(float,float,float, float,float,float, - unsigned, float, float, int); - unsigned tint = - (((unsigned)(40.0f + r * 215.0f) & 0xFF) << 16) | - (((unsigned)(40.0f + g * 215.0f) & 0xFF) << 8) | - ((unsigned)(40.0f + b * 215.0f) & 0xFF); - BTPushBeamKind(mz.x, mz.y, mz.z, bend.x, bend.y, bend.z, - tint, ttl1, 1.0f /* natural model width */, - isPPC ? 1 : 0 /* ppc.bgf : ermlaser.bgf */); - if (getenv("BT_BEAM_LOG") && (s_beamStateLog++ % 31) == 0) // 31: coprime with the 5-beam volley (a %30 sampler aliased to one weapon) - DEBUG_STREAM << "[beam] " << (isPPC ? "PPC" : "laser") << " #" << wi - << " mz=(" << mz.x << "," << mz.y << "," << mz.z - << ") end=(" << bend.x << "," << bend.y << "," << bend.z - << ") rgb=(" << r << "," << g << "," << b << ")\n" << std::flush; - } - } + DrawWeaponBeams(dt); // task #51: extracted (runs for replicants too) } // --- TARGETING: the WORLD-PICK model (task #41, the reconciliation of @@ -3538,3 +3462,102 @@ static const DamageStateEntry kDamageStateTable[] = //===========================================================================// // End of recovered mech4.cpp slice. //===========================================================================// + +// +// DrawWeaponBeams (task #51 extraction) -- the per-weapon beam render walk +// (task #33), extracted from the player-only drive block so it runs for +// EVERY mech: the local player, the solo dummy, and MP REPLICANTS (whose +// emitters now carry live replicated discharge state via the Emitter update +// records; without this the peer's beams applied but never drew). +// +void + Mech::DrawWeaponBeams(Scalar dt) +{ + extern void BTPushBeam(float,float,float, float,float,float, unsigned, float, float); + const Scalar ttl1 = (dt > 1e-4f) ? dt : 1e-4f; // one-frame life (redrawn while on) + static int s_beamStateLog = 0; + int energyOrdinal = -1; // Nth energy weapon (port assignment) + for (int wi = 0; wi < GetSubsystemCount(); ++wi) + { + Subsystem *ws = GetSubsystem(wi); + if (ws == 0) + continue; + // EXACT class filter: Emitter (0xBC8=3016) or PPC (0xBD4=3028). + // The derivation check matched too broadly here (the recon + // derivation chains are shared stubs for some subsystems -- + // a Sensor and the MissileLauncher passed and drew garbage + // from misinterpreted offsets). + const int wcid = (int)ws->GetClassID(); + if (wcid != 3016 && wcid != 3028) + continue; + ++energyOrdinal; + Emitter *em = (Emitter *)ws; + if (!em->BeamOn()) + continue; + Point3D mz; + em->MuzzlePoint(mz); // LIVE muzzle (tracks the gun) + // MOUNT FALLBACK: when the weapon's mount segment doesn't + // resolve, GetMuzzlePoint returns the mech ORIGIN (feet). + // Assign this weapon its own gun-port segment by roster + // ordinal (the same port set the old visual used) so each + // energy weapon keeps a stable muzzle on the arms. + if (mz.y - localOrigin.linearPosition.y < 1.0f) + { + static const char *const kGunPorts[] = + { "siterugunport", "sitelugunport", "siterdgunport", + "siteldgunport", "siterbgunport", "sitelbgunport" }; + // PER-MECH cache (task #51): the walk now runs for EVERY mech + // (player + replicants); the old process-wide statics would serve + // the PLAYER's segment pointers as the replicant's muzzles. Key + // the slot by owner and re-resolve on mismatch. + static EntitySegment *s_portCache[64]; + static int s_portTried[64]; + static Mech *s_portOwner[64]; + if (energyOrdinal >= 0 && energyOrdinal < 64) + { + if (!s_portTried[energyOrdinal] + || s_portOwner[energyOrdinal] != this) + { + s_portTried[energyOrdinal] = 1; + s_portOwner[energyOrdinal] = this; + s_portCache[energyOrdinal] = GetSegment( + CString(kGunPorts[energyOrdinal % 6])); + } + if (s_portCache[energyOrdinal] != 0) + { + AffineMatrix mw; + mw.Multiply(s_portCache[energyOrdinal]->GetSegmentToEntity(), + localToWorld); + mz = mw; // Point3D = matrix translation + } + } + } + const Point3D &bend = em->BeamEndpoint(); // the fire's world hit point + // authored per-weapon colour; unset (-1) -> the ER-laser red + RGBColor pc = em->PipColor(); + float r = (float)pc.Red, g = (float)pc.Green, b = (float)pc.Blue; + if (r < 0.0f || g < 0.0f || b < 0.0f) { r = 0.78f; g = 0.08f; b = 0.02f; } + // PPC (classID 0xBD4 = 3028): a thicker, brighter bolt than a laser tube. + const int isPPC = ((int)em->GetClassID() == 3028); + // ONE draw per beam, at the model's NATURAL width: the weapon's + // own tube (ERMLASER radius 0.22u, PPC bolt 0.62u) IS the beam + // -- the old inflated two-layer glow/core (3.0x + 0.9x widths) + // drew fat cartoon cylinders 13x the authored size. The tint + // modulates the scrolling grit; thin natural tubes stay under + // saturation without a hand-dimmed core. + extern void BTPushBeamKind(float,float,float, float,float,float, + unsigned, float, float, int); + unsigned tint = + (((unsigned)(40.0f + r * 215.0f) & 0xFF) << 16) | + (((unsigned)(40.0f + g * 215.0f) & 0xFF) << 8) | + ((unsigned)(40.0f + b * 215.0f) & 0xFF); + BTPushBeamKind(mz.x, mz.y, mz.z, bend.x, bend.y, bend.z, + tint, ttl1, 1.0f /* natural model width */, + isPPC ? 1 : 0 /* ppc.bgf : ermlaser.bgf */); + if (getenv("BT_BEAM_LOG") && (s_beamStateLog++ % 31) == 0) // 31: coprime with the 5-beam volley (a %30 sampler aliased to one weapon) + DEBUG_STREAM << "[beam] " << (isPPC ? "PPC" : "laser") << " #" << wi + << " mz=(" << mz.x << "," << mz.y << "," << mz.z + << ") end=(" << bend.x << "," << bend.y << "," << bend.z + << ") rgb=(" << r << "," << g << "," << b << ")\n" << std::flush; + } +} diff --git a/game/reconstructed/mechweap.cpp b/game/reconstructed/mechweap.cpp index 433b80e..ee49655 100644 --- a/game/reconstructed/mechweap.cpp +++ b/game/reconstructed/mechweap.cpp @@ -233,59 +233,73 @@ Logical // // -// @004b964c -- slot 6. Apply damage through the base, then set the weapon's -// display alarm: a destroyed weapon (damage->destroyed flag, +0x14) clears the -// alarm to 0, otherwise the alarm tracks the incoming damage level (+0x10). +// @004b96d4 -- slot 9 (TASK #51 RENAME: this body was mislabeled slot-6 +// ReadUpdateRecord; @004b96d4 chains @004b0efc = PoweredSubsystem::TakeDamage, +// which dispatches on *param==4 == Damage::damageType -- damage semantics). +// The PoweredSubsystem::TakeDamage body (electrical-short handling) is not yet +// reconstructed; chain to the engine Subsystem base, and keep the damage-zone +// alarm tracking this port has shipped with (best-effort; the binary's alarm +// display on weapon damage flows through the unreconstructed @004b0efc chain). // void MechWeapon::TakeDamage(Damage &damage) { - Subsystem::TakeDamage(damage); // FUN_0041bd34 + Subsystem::TakeDamage(damage); // engine base (real chain: @004b0efc) - // A destroyed weapon clears its display alarm; otherwise the alarm tracks - // the damage-zone state set by the base TakeDamage above. (The decomp read - // the post-damage destroyed/level flags out of the damage record; here we - // read them back off the resolved damage zone.) - // Read the ENGINE Subsystem::damageZone (the real built zone), qualified -- the - // nearer MechSubsystem::damageZone is a ReconDamageZone-shim SHADOW of it (exposed - // once HeatableSubsystem re-based onto MechSubsystem). TODO: remove that shadow. + // Port behavior (kept): a destroyed weapon clears its display alarm; + // otherwise the alarm tracks the resolved damage-zone state. ::DamageZone *dz = this->Subsystem::damageZone; if (dz != 0 && dz->GetGraphicState() == DamageZone::DestroyedGraphicState) { - weaponAlarm.SetLevel(0); // FUN_0041bbd8(this+0xd4, 0) + weaponAlarm.SetLevel(0); } else { weaponAlarm.SetLevel( (dz != 0 && dz->damageLevel > 0.0f) ? 1 : 0 - ); // FUN_0041bbd8(this+0xd4, level) + ); } } // // @004b9690 -- slot 7. Append the weapon's replication fields to the update -// record: record type 0x18, the alarm state (this+0x364), and a "ready/idle" -// flag derived from this+0x360. +// record. DISASM CORRECTIONS (task #51): `*message = 0x18` writes the record +// LENGTH (record[0] = recordLength), not recordID; and message[5] derives from +// this+0x360 == weaponAlarm+0x10 (LevelCountB), not from the level. BASE-TYPED +// param: with the class's own UpdateRecord typedef this silently failed to +// override the engine virtual, so the engine base ran and no weapon field +// ever serialized. // void - MechWeapon::WriteUpdateRecord(UpdateRecord *message, int update_model) + MechWeapon::WriteUpdateRecord(Simulation__UpdateRecord *message, int update_model) { - Subsystem::WriteUpdateRecord(message, update_model); // FUN_0041c500 + Subsystem::WriteUpdateRecord(message, update_model); // FUN_0041c500 (header + subsystemID) - message->recordID = 0x18; // *message = 0x18 - message->alarmState = weaponAlarm.GetLevel(); // message[4] = *(this+0x364) - message->weaponIdle = (weaponAlarm.GetLevel() == 0) ? 1 : 0; // message[5] + MechWeapon__UpdateRecord *rec = (MechWeapon__UpdateRecord *)message; + rec->recordLength = sizeof(MechWeapon__UpdateRecord); // *message = 0x18 + rec->alarmState = weaponAlarm.GetLevel(); // message[4] = *(this+0x364) + rec->weaponIdle = (weaponAlarm.LevelCountB() == 0) ? 1 : 0; // message[5] = (*(this+0x360)==0) } // -// @004b96d4 -- slot 9. MechWeapon adds nothing of its own; defers to the -// PoweredSubsystem implementation (paired with WriteUpdateRecord above). +// @004b964c -- slot 6 (TASK #51 RENAME: was mislabeled TakeDamage). Apply a +// weapon update record on the REPLICANT: chain the Simulation base +// (@0041bd34 -- lastUpdate + simulationState), then the weapon alarm -- +// reset-through-0 first when the record's weaponIdle flag is set, then the +// replicated alarm level. // void - MechWeapon::ReadUpdateRecord(UpdateRecord *message) + MechWeapon::ReadUpdateRecord(Simulation__UpdateRecord *message) { - PoweredSubsystem::ReadUpdateRecord(message); // FUN_004b0efc + Simulation::ReadUpdateRecord(message); // FUN_0041bd34 + + MechWeapon__UpdateRecord *rec = (MechWeapon__UpdateRecord *)message; + if (rec->weaponIdle != 0) // record+0x14 + { + weaponAlarm.SetLevel(0); + } + weaponAlarm.SetLevel(rec->alarmState); // record+0x10 } // diff --git a/game/reconstructed/mechweap.hpp b/game/reconstructed/mechweap.hpp index 346dc9e..f83bdb6 100644 --- a/game/reconstructed/mechweap.hpp +++ b/game/reconstructed/mechweap.hpp @@ -159,30 +159,39 @@ class CockpitHud; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem virtual overrides (slots on vtable @00511d2c) // - // Slot indices verified by diffing against the Subsystem vtable tail of - // @0050e170 and the PoweredSubsystem vtable @0050f9d8: - // slot 6 TakeDamage(Damage&) (Subsystem base @0041bd34) - // slot 7 WriteUpdateRecord(...) (Subsystem base @0041c500) - // slot 9 ReadUpdateRecord(...) (PoweredSubsystem base @004b0efc) + // SLOT MAP CORRECTED (task #51): the Read/TakeDamage attributions were + // SWAPPED. By symmetry with the Entity hierarchy (Mech slot 6 = + // ReadUpdateRecord @004a1232, slot 7 = WriteUpdateRecord @004a0c2c, both + // verified) and by body semantics (@004b964c reads record+0x10/+0x14 into + // the weapon alarm after chaining @0041bd34 = Simulation::ReadUpdateRecord; + // @004b0efc dispatches on *param==4 = Damage::damageType): + // slot 6 ReadUpdateRecord(...) @004b964c (base @0041bd34 = Simulation::ReadUpdateRecord) + // slot 7 WriteUpdateRecord(...) @004b9690 (base @0041c500 = Subsystem::WriteUpdateRecord) + // slot 9 TakeDamage(Damage&) @004b96d4 (chains @004b0efc = PoweredSubsystem::TakeDamage) // slot 10 ResetToInitialState() (PoweredSubsystem base @004b0e6c) // slot 13 PrintState() (PoweredSubsystem base @004b1224) // + // NOTE the ENGINE-OVERRIDE signature rule: the virtuals are declared on + // Simulation with Simulation::UpdateRecord* (SIMULATE.h:127-131). Declaring + // them here with THIS class's shadowing `UpdateRecord` typedef silently + // fails to override (the engine base runs instead and none of the weapon + // fields ever serialize) -- base-typed params, cast inside. + // public: void - TakeDamage(Damage &damage); // slot 6, @004b964c + TakeDamage(Damage &damage); // slot 9, @004b96d4 void ResetToInitialState(); // slot 10, @004b96ec void PrintState(); // slot 13, @004b9d00 - protected: void WriteUpdateRecord( // slot 7, @004b9690 - UpdateRecord *message, - int update_model + Simulation__UpdateRecord *message, + int update_model ); void - ReadUpdateRecord(UpdateRecord *message); // slot 9, @004b96d4 + ReadUpdateRecord(Simulation__UpdateRecord *message); // slot 6, @004b964c //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support diff --git a/game/reconstructed/projweap.cpp b/game/reconstructed/projweap.cpp index ecfef56..8354dc3 100644 --- a/game/reconstructed/projweap.cpp +++ b/game/reconstructed/projweap.cpp @@ -314,13 +314,15 @@ Logical } // -// @004bbae0 -- slot 9. ProjectileWeapon adds nothing of its own; defers to the -// MechWeapon implementation. +// @004bbae0 -- slot 9 (TASK #51 RENAME: slot 9 is TakeDamage, not +// ReadUpdateRecord -- see the corrected slot map in mechweap.hpp). +// ProjectileWeapon adds nothing of its own; defers to the MechWeapon +// implementation (@004b96d4 chains @004b0efc = PoweredSubsystem::TakeDamage). // void - ProjectileWeapon::ReadUpdateRecord(UpdateRecord *message) + ProjectileWeapon::TakeDamage(Damage &damage) { - MechWeapon::ReadUpdateRecord(message); // FUN_004b96d4 + MechWeapon::TakeDamage(damage); // FUN_004b96d4 } // diff --git a/game/reconstructed/projweap.hpp b/game/reconstructed/projweap.hpp index a29b204..6d54876 100644 --- a/game/reconstructed/projweap.hpp +++ b/game/reconstructed/projweap.hpp @@ -172,11 +172,13 @@ class NotationFile; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem virtual overrides (slots on vtable @0051242c) // - // Slot indices verified by decoding the raw vtable bytes (section_dump): - // slot 6 TakeDamage INHERITED MechWeapon @004b964c + // Slot indices verified by decoding the raw vtable bytes (section_dump). + // SLOT MAP CORRECTED (task #51): 6=ReadUpdateRecord, 9=TakeDamage (the + // old attributions were swapped -- see mechweap.hpp for the evidence): + // slot 6 ReadUpdateRecord INHERITED MechWeapon @004b964c // slot 7 WriteUpdateRecord INHERITED MechWeapon @004b9690 // slot 8 HandleMessage OVERRIDE @004bcabc - // slot 9 ReadUpdateRecord OVERRIDE @004bbae0 (thin -> MechWeapon) + // slot 9 TakeDamage OVERRIDE @004bbae0 (thin -> MechWeapon) // slot 10 ResetToInitialState OVERRIDE @004bbaf8 // slot 12 GetStatusFlags OVERRIDE @004bbf88 (body not recovered) // slot 13 PrintState OVERRIDE @004bc6c8 @@ -194,8 +196,11 @@ class NotationFile; PrintState(); // slot 13, @004bc6c8 protected: + // TASK #51 RENAME: slot 9 is TakeDamage (the old ReadUpdateRecord + // attribution was swapped; the record read is INHERITED from + // MechWeapon slot 6 @004b964c -- see mechweap.hpp for the evidence). void - ReadUpdateRecord(UpdateRecord *message); // slot 9, @004bbae0 + TakeDamage(Damage &damage); // slot 9, @004bbae0 // slot 16, @004bbc20 -- MechWeapon-family per-frame hook (overrides // MechWeapon @004b0b5c). Body not recovered. TODO: confirm role. diff --git a/reference/decomp/mech_writeupdate_004a0c2c.disasm.txt b/reference/decomp/mech_writeupdate_004a0c2c.disasm.txt new file mode 100644 index 0000000..f1f150f --- /dev/null +++ b/reference/decomp/mech_writeupdate_004a0c2c.disasm.txt @@ -0,0 +1,467 @@ +=== Mech::WriteUpdateRecord @0x4a0c2c (vtable slot 7) === +=== args: (this=ebp+8, record=ebp+0xC, type=ebp+0x10); jump table @0x4a0c55 on type 0..8 === +=== table: 0:0x4a0c79 1:0x4a11cb 2:0x4a0e5b 3:0x4a0d84 4:0x4a0e9e 5:0x4a0fab 6:0x4a104d 7:0x4a10ff 8:0x4a117f === + +===== prologue + dispatch @0x4a0c2c..0x4a0c55 ===== +0x4a0c2c: push ebp +0x4a0c2d: mov ebp, esp +0x4a0c2f: add esp, -0x68 +0x4a0c32: push ebx +0x4a0c33: push esi +0x4a0c34: push edi +0x4a0c35: mov edi, dword ptr [ebp + 0x10] +0x4a0c38: mov ebx, dword ptr [ebp + 8] +0x4a0c3b: mov eax, dword ptr [ebx + 0x128] +0x4a0c41: mov esi, dword ptr [eax] +0x4a0c43: mov eax, edi +0x4a0c45: cmp eax, 8 +0x4a0c48: ja 0x4a11cb +0x4a0c4e: jmp dword ptr [eax*4 + 0x4a0c55] + +===== case 0 (full pose snapshot) @0x4a0c79..0x4a0d84 ===== +0x4a0c79: lea edx, [ebx + 0x138] +0x4a0c7f: push edx +0x4a0c80: lea ecx, [ebp - 0x44] +0x4a0c83: push ecx +0x4a0c84: call 0x408f44 +0x4a0c89: add esp, 8 +0x4a0c8c: lea eax, [ebx + 0x2d4] +0x4a0c92: push eax +0x4a0c93: lea edx, [ebp - 0x50] +0x4a0c96: push edx +0x4a0c97: call 0x408440 +0x4a0c9c: add esp, 8 +0x4a0c9f: push edi +0x4a0ca0: mov ecx, dword ptr [ebp + 0xc] +0x4a0ca3: push ecx +0x4a0ca4: push ebx +0x4a0ca5: call 0x4225a4 +0x4a0caa: mov edi, dword ptr [ebp + 0xc] +0x4a0cad: add esp, 0xc +0x4a0cb0: lea eax, [ebp - 0x44] +0x4a0cb3: lea edx, [ebx + 0x138] +0x4a0cb9: mov word ptr [edi + 4], 0 +0x4a0cbf: mov dword ptr [ebx + 0x77c], 1 +0x4a0cc9: push eax +0x4a0cca: push edx +0x4a0ccb: call 0x409a00 +0x4a0cd0: add esp, 8 +0x4a0cd3: lea ecx, [ebx + 0x2d4] +0x4a0cd9: mov dword ptr [ebp - 4], ecx +0x4a0cdc: lea eax, [ebp - 0x50] +0x4a0cdf: push eax +0x4a0ce0: mov edx, dword ptr [ebp - 4] +0x4a0ce3: push edx +0x4a0ce4: call 0x408440 +0x4a0ce9: add esp, 8 +0x4a0cec: mov ecx, dword ptr [ebx + 0x3b0] +0x4a0cf2: cmp ecx, 2 +0x4a0cf5: je 0x4a0d02 +0x4a0cf7: mov eax, dword ptr [ebx + 0x3b0] +0x4a0cfd: cmp eax, 3 +0x4a0d00: jne 0x4a0d16 +0x4a0d02: push 0x4e0fd4 ; [0x4e0fd4]=0f +0x4a0d07: lea edx, [ebx + 0x298] +0x4a0d0d: push edx +0x4a0d0e: call 0x40a7f4 +0x4a0d13: add esp, 8 +0x4a0d16: mov ecx, dword ptr [ebx + 0x40] +0x4a0d19: mov eax, dword ptr [ebx + 0x3c] +0x4a0d1c: cmp ecx, eax +0x4a0d1e: je 0x4a0d64 +0x4a0d20: mov edx, dword ptr [ebx + 0x3c] +0x4a0d23: cmp edx, 2 +0x4a0d26: je 0x4a0d30 +0x4a0d28: mov ecx, dword ptr [ebx + 0x40] +0x4a0d2b: cmp ecx, 2 +0x4a0d2e: jne 0x4a0d64 +0x4a0d30: lea eax, [ebx + 0x12c] +0x4a0d36: push eax +0x4a0d37: lea edx, [ebx + 0x260] +0x4a0d3d: push edx +0x4a0d3e: call 0x40a938 +0x4a0d43: add esp, 8 +0x4a0d46: lea ecx, [ebx + 0x2c8] +0x4a0d4c: push ecx +0x4a0d4d: lea eax, [ebx + 0x298] +0x4a0d53: push eax +0x4a0d54: call 0x40a7f4 +0x4a0d59: add esp, 8 +0x4a0d5c: xor edx, edx +0x4a0d5e: mov dword ptr [ebx + 0x77c], edx +0x4a0d64: mov ecx, dword ptr [esi + 0x128] +0x4a0d6a: mov dword ptr [edi + 0x74], ecx +0x4a0d6d: mov eax, dword ptr [esi + 0x128] +0x4a0d73: mov dword ptr [ebx + 0x6b4], eax +0x4a0d79: mov dword ptr [edi], 0x78 +0x4a0d7f: jmp 0x4a11d9 + +===== case 3 (score/heat) @0x4a0d84..0x4a0e5b ===== +0x4a0d84: mov edx, dword ptr [ebp + 0xc] +0x4a0d87: mov dword ptr [ebp - 8], edx +0x4a0d8a: push edi +0x4a0d8b: mov ecx, dword ptr [ebp - 8] +0x4a0d8e: push ecx +0x4a0d8f: push ebx +0x4a0d90: call 0x41bd60 +0x4a0d95: add esp, 0xc +0x4a0d98: mov eax, dword ptr [ebp - 8] +0x4a0d9b: mov dword ptr [eax], 0x20 +0x4a0da1: mov edx, dword ptr [ebp - 8] +0x4a0da4: mov word ptr [edx + 4], 0 +0x4a0daa: mov ecx, dword ptr [ebp - 8] +0x4a0dad: mov eax, dword ptr [ebx + 0x654] +0x4a0db3: mov dword ptr [ecx + 0x10], eax +0x4a0db6: mov edx, dword ptr [ebp - 8] +0x4a0db9: mov ecx, dword ptr [ebx + 0x3b0] +0x4a0dbf: mov dword ptr [edx + 0x14], ecx +0x4a0dc2: mov eax, dword ptr [ebp - 8] +0x4a0dc5: mov edx, dword ptr [ebx + 0x4d8] +0x4a0dcb: mov dword ptr [eax + 0x18], edx +0x4a0dce: mov ecx, dword ptr [ebp - 8] +0x4a0dd1: mov eax, dword ptr [ecx + 0x10] +0x4a0dd4: mov dword ptr [ebx + 0x658], eax +0x4a0dda: mov edx, dword ptr [ebp - 8] +0x4a0ddd: mov ecx, dword ptr [edx + 0x14] +0x4a0de0: test ecx, ecx +0x4a0de2: jne 0x4a0e0d +0x4a0de4: push 1 +0x4a0de6: lea eax, [ebx + 0x65c] +0x4a0dec: push eax +0x4a0ded: call 0x4283b8 +0x4a0df2: add esp, 8 +0x4a0df5: mov edx, dword ptr [ebp - 8] +0x4a0df8: mov ecx, dword ptr [edx + 0x14] +0x4a0dfb: push ecx +0x4a0dfc: lea eax, [ebx + 0x714] +0x4a0e02: push eax +0x4a0e03: call 0x41bbd8 +0x4a0e08: add esp, 8 +0x4a0e0b: jmp 0x4a0e3e +0x4a0e0d: mov edx, dword ptr [ebp - 8] +0x4a0e10: mov ecx, dword ptr [edx + 0x14] +0x4a0e13: dec ecx +0x4a0e14: jne 0x4a0e2e +0x4a0e16: mov eax, dword ptr [ebp - 8] +0x4a0e19: mov edx, dword ptr [eax + 0x14] +0x4a0e1c: push edx +0x4a0e1d: lea ecx, [ebx + 0x714] +0x4a0e23: push ecx +0x4a0e24: call 0x41bbd8 +0x4a0e29: add esp, 8 +0x4a0e2c: jmp 0x4a0e3e +0x4a0e2e: mov eax, dword ptr [ebp - 8] +0x4a0e31: mov edx, dword ptr [eax + 0x14] +0x4a0e34: push edx +0x4a0e35: push ebx +0x4a0e36: call 0x4a800c +0x4a0e3b: add esp, 8 +0x4a0e3e: mov ecx, dword ptr [ebp - 8] +0x4a0e41: mov eax, dword ptr [esi + 0x128] +0x4a0e47: mov dword ptr [ecx + 0x1c], eax +0x4a0e4a: mov edx, dword ptr [esi + 0x128] +0x4a0e50: mov dword ptr [ebx + 0x6b4], edx +0x4a0e56: jmp 0x4a11d9 + +===== case 2 (alarm-only) @0x4a0e5b..0x4a0e9e ===== +0x4a0e5b: mov ecx, dword ptr [ebp + 0xc] +0x4a0e5e: mov dword ptr [ebp - 0xc], ecx +0x4a0e61: push edi +0x4a0e62: mov eax, dword ptr [ebp - 0xc] +0x4a0e65: push eax +0x4a0e66: push ebx +0x4a0e67: call 0x41bd60 +0x4a0e6c: add esp, 0xc +0x4a0e6f: mov edx, dword ptr [ebp - 0xc] +0x4a0e72: mov dword ptr [edx], 0x14 +0x4a0e78: mov ecx, dword ptr [ebp - 0xc] +0x4a0e7b: mov word ptr [ecx + 4], 0 +0x4a0e81: mov eax, dword ptr [ebp - 0xc] +0x4a0e84: mov edx, dword ptr [esi + 0x128] +0x4a0e8a: mov dword ptr [eax + 0x10], edx +0x4a0e8d: mov ecx, dword ptr [esi + 0x128] +0x4a0e93: mov dword ptr [ebx + 0x6b4], ecx +0x4a0e99: jmp 0x4a11d9 + +===== case 4 (pose + time re-sync) @0x4a0e9e..0x4a0fab ===== +0x4a0e9e: mov eax, dword ptr [ebp + 0xc] +0x4a0ea1: mov dword ptr [ebp - 0x10], eax +0x4a0ea4: push edi +0x4a0ea5: mov edx, dword ptr [ebp - 0x10] +0x4a0ea8: push edx +0x4a0ea9: push ebx +0x4a0eaa: call 0x41bd60 +0x4a0eaf: mov ecx, dword ptr [ebp - 0x10] +0x4a0eb2: add esp, 0xc +0x4a0eb5: lea edx, [ebx + 0x10c] +0x4a0ebb: mov dword ptr [ecx], 0x2c +0x4a0ec1: mov eax, dword ptr [ebp - 0x10] +0x4a0ec4: mov word ptr [eax + 4], 0 +0x4a0eca: push edx +0x4a0ecb: call 0x40a138 +0x4a0ed0: pop ecx +0x4a0ed1: lea ecx, [ebx + 0x10c] +0x4a0ed7: push ecx +0x4a0ed8: mov eax, dword ptr [ebp - 0x10] +0x4a0edb: add eax, 0x10 +0x4a0ede: push eax +0x4a0edf: call 0x408f44 +0x4a0ee4: add esp, 8 +0x4a0ee7: lea eax, [ebx + 0x1d0] +0x4a0eed: mov edi, dword ptr [ebp - 0x10] +0x4a0ef0: add edi, 0x1c +0x4a0ef3: push eax +0x4a0ef4: push edi +0x4a0ef5: call 0x408440 +0x4a0efa: add esp, 8 +0x4a0efd: call 0x414b60 +0x4a0f02: mov dword ptr [ebp - 0x14], eax +0x4a0f05: lea eax, [ebx + 0x778] +0x4a0f0b: mov edx, dword ptr [ebp - 0x14] +0x4a0f0e: mov ecx, dword ptr [edx] +0x4a0f10: mov dword ptr [eax], ecx +0x4a0f12: call 0x414b60 +0x4a0f17: mov dword ptr [ebp - 0x18], eax +0x4a0f1a: lea eax, [ebx + 0x2e0] +0x4a0f20: mov edx, dword ptr [ebp - 0x18] +0x4a0f23: mov ecx, dword ptr [edx] +0x4a0f25: mov dword ptr [eax], ecx +0x4a0f27: lea eax, [ebx + 0x14] +0x4a0f2a: mov edx, dword ptr [ebx + 0x2e0] +0x4a0f30: mov ecx, dword ptr [eax] +0x4a0f32: sub edx, ecx +0x4a0f34: mov dword ptr [ebp - 0x68], edx +0x4a0f37: fild dword ptr [ebp - 0x68] +0x4a0f3a: fdiv dword ptr [0x52140c] +0x4a0f40: fstp dword ptr [ebp - 0x1c] +0x4a0f43: fld dword ptr [ebp - 0x1c] +0x4a0f46: fcomp dword ptr [0x4a1228] +0x4a0f4c: fnstsw ax +0x4a0f4e: sahf +0x4a0f4f: jae 0x4a0f62 +0x4a0f51: mov edx, dword ptr [ebx + 0x2e0] +0x4a0f57: mov ecx, dword ptr [ebx + 0x14] +0x4a0f5a: sub edx, ecx +0x4a0f5c: add dword ptr [ebx + 0x2e0], edx +0x4a0f62: lea eax, [ebx + 0x10c] +0x4a0f68: push eax +0x4a0f69: lea edx, [ebx + 0x138] +0x4a0f6f: push edx +0x4a0f70: call 0x409968 +0x4a0f75: add esp, 8 +0x4a0f78: lea eax, [ebx + 0x1d0] +0x4a0f7e: lea edi, [ebx + 0x2d4] +0x4a0f84: push eax +0x4a0f85: push edi +0x4a0f86: call 0x408440 +0x4a0f8b: add esp, 8 +0x4a0f8e: mov eax, dword ptr [ebp - 0x10] +0x4a0f91: mov edx, dword ptr [esi + 0x128] +0x4a0f97: mov dword ptr [eax + 0x28], edx +0x4a0f9a: mov ecx, dword ptr [esi + 0x128] +0x4a0fa0: mov dword ptr [ebx + 0x6b4], ecx +0x4a0fa6: jmp 0x4a11d9 + +===== case 5 (subsystem alarm A) @0x4a0fab..0x4a104d ===== +0x4a0fab: mov eax, dword ptr [ebp + 0xc] +0x4a0fae: mov dword ptr [ebp - 0x20], eax +0x4a0fb1: push edi +0x4a0fb2: mov edx, dword ptr [ebp - 0x20] +0x4a0fb5: push edx +0x4a0fb6: push ebx +0x4a0fb7: call 0x41bd60 +0x4a0fbc: mov ecx, dword ptr [ebp - 0x20] +0x4a0fbf: add esp, 0xc +0x4a0fc2: mov dword ptr [ecx], 0x2c +0x4a0fc8: mov eax, dword ptr [ebp - 0x20] +0x4a0fcb: mov word ptr [eax + 4], 0 +0x4a0fd1: mov edx, dword ptr [ebp - 0x20] +0x4a0fd4: mov ecx, dword ptr [ebx + 0x464] +0x4a0fda: mov dword ptr [edx + 0x10], ecx +0x4a0fdd: lea ecx, [ebx + 0x4a8] +0x4a0fe3: mov eax, dword ptr [ebp - 0x20] +0x4a0fe6: mov edx, dword ptr [ebx + 0x4a4] +0x4a0fec: mov dword ptr [eax + 0x14], edx +0x4a0fef: mov dword ptr [ebp - 0x24], ecx +0x4a0ff2: mov edi, dword ptr [ebp - 0x20] +0x4a0ff5: mov eax, dword ptr [ebp - 0x24] +0x4a0ff8: push eax +0x4a0ff9: add edi, 0x18 +0x4a0ffc: push edi +0x4a0ffd: call 0x408440 +0x4a1002: add esp, 8 +0x4a1005: mov edx, dword ptr [ebp - 0x20] +0x4a1008: mov ecx, dword ptr [ebx + 0x4b4] +0x4a100e: mov dword ptr [edx + 0x24], ecx +0x4a1011: push 0x20 +0x4a1013: push ebx +0x4a1014: call 0x4a800c +0x4a1019: add esp, 8 +0x4a101c: push 0x4e0fd4 ; [0x4e0fd4]=0f +0x4a1021: lea eax, [ebx + 0x298] +0x4a1027: push eax +0x4a1028: call 0x40a7f4 +0x4a102d: add esp, 8 +0x4a1030: mov edx, dword ptr [ebp - 0x20] +0x4a1033: mov ecx, dword ptr [esi + 0x128] +0x4a1039: mov dword ptr [edx + 0x28], ecx +0x4a103c: mov eax, dword ptr [esi + 0x128] +0x4a1042: mov dword ptr [ebx + 0x6b4], eax +0x4a1048: jmp 0x4a11d9 + +===== case 6 (subsystem alarm B/death) @0x4a104d..0x4a10ff ===== +0x4a104d: mov edx, dword ptr [ebp + 0xc] +0x4a1050: mov dword ptr [ebp - 0x28], edx +0x4a1053: push edi +0x4a1054: mov ecx, dword ptr [ebp - 0x28] +0x4a1057: push ecx +0x4a1058: push ebx +0x4a1059: call 0x41bd60 +0x4a105e: mov eax, dword ptr [ebp - 0x28] +0x4a1061: add esp, 0xc +0x4a1064: mov dword ptr [eax], 0x2c +0x4a106a: mov edx, dword ptr [ebp - 0x28] +0x4a106d: mov word ptr [edx + 4], 0 +0x4a1073: mov ecx, dword ptr [ebp - 0x28] +0x4a1076: mov eax, dword ptr [ebx + 0x464] +0x4a107c: mov dword ptr [ecx + 0x10], eax +0x4a107f: lea eax, [ebx + 0x4a8] +0x4a1085: mov edx, dword ptr [ebp - 0x28] +0x4a1088: mov ecx, dword ptr [ebx + 0x4a4] +0x4a108e: mov dword ptr [edx + 0x14], ecx +0x4a1091: mov dword ptr [ebp - 0x2c], eax +0x4a1094: mov edi, dword ptr [ebp - 0x28] +0x4a1097: mov eax, dword ptr [ebp - 0x2c] +0x4a109a: push eax +0x4a109b: add edi, 0x18 +0x4a109e: push edi +0x4a109f: call 0x408440 +0x4a10a4: mov edx, dword ptr [ebp - 0x28] +0x4a10a7: add esp, 8 +0x4a10aa: mov ecx, dword ptr [ebx + 0x4b4] +0x4a10b0: lea eax, [ebx + 0x714] +0x4a10b6: mov dword ptr [edx + 0x24], ecx +0x4a10b9: push 0 +0x4a10bb: push eax +0x4a10bc: call 0x41bbd8 +0x4a10c1: add esp, 8 +0x4a10c4: mov dword ptr [ebx + 0x658], 1 +0x4a10ce: push 0x4e0fd4 ; [0x4e0fd4]=0f +0x4a10d3: lea edx, [ebx + 0x298] +0x4a10d9: push edx +0x4a10da: call 0x40a7f4 +0x4a10df: add esp, 8 +0x4a10e2: mov ecx, dword ptr [ebp - 0x28] +0x4a10e5: mov eax, dword ptr [esi + 0x128] +0x4a10eb: mov dword ptr [ecx + 0x28], eax +0x4a10ee: mov edx, dword ptr [esi + 0x128] +0x4a10f4: mov dword ptr [ebx + 0x6b4], edx +0x4a10fa: jmp 0x4a11d9 + +===== case 7 (subsystem alarm C) @0x4a10ff..0x4a117f ===== +0x4a10ff: mov ecx, dword ptr [ebp + 0xc] +0x4a1102: mov dword ptr [ebp - 0x30], ecx +0x4a1105: push edi +0x4a1106: mov eax, dword ptr [ebp - 0x30] +0x4a1109: push eax +0x4a110a: push ebx +0x4a110b: call 0x41bd60 +0x4a1110: mov edx, dword ptr [ebp - 0x30] +0x4a1113: add esp, 0xc +0x4a1116: mov dword ptr [edx], 0x2c +0x4a111c: mov ecx, dword ptr [ebp - 0x30] +0x4a111f: mov word ptr [ecx + 4], 0 +0x4a1125: mov eax, dword ptr [ebp - 0x30] +0x4a1128: mov edx, dword ptr [ebx + 0x464] +0x4a112e: mov dword ptr [eax + 0x10], edx +0x4a1131: lea edx, [ebx + 0x4a8] +0x4a1137: mov ecx, dword ptr [ebp - 0x30] +0x4a113a: mov eax, dword ptr [ebx + 0x4a4] +0x4a1140: mov dword ptr [ecx + 0x14], eax +0x4a1143: mov dword ptr [ebp - 0x34], edx +0x4a1146: mov edi, dword ptr [ebp - 0x30] +0x4a1149: mov eax, dword ptr [ebp - 0x34] +0x4a114c: push eax +0x4a114d: add edi, 0x18 +0x4a1150: push edi +0x4a1151: call 0x408440 +0x4a1156: add esp, 8 +0x4a1159: mov edx, dword ptr [ebp - 0x30] +0x4a115c: mov ecx, dword ptr [ebx + 0x4b4] +0x4a1162: mov dword ptr [edx + 0x24], ecx +0x4a1165: mov eax, dword ptr [ebp - 0x30] +0x4a1168: mov edx, dword ptr [esi + 0x128] +0x4a116e: mov dword ptr [eax + 0x28], edx +0x4a1171: mov ecx, dword ptr [esi + 0x128] +0x4a1177: mov dword ptr [ebx + 0x6b4], ecx +0x4a117d: jmp 0x4a11d9 + +===== case 8 (single scalar) @0x4a117f..0x4a11cb ===== +0x4a117f: mov eax, dword ptr [ebp + 0xc] +0x4a1182: mov dword ptr [ebp - 0x38], eax +0x4a1185: push edi +0x4a1186: mov edx, dword ptr [ebp - 0x38] +0x4a1189: push edx +0x4a118a: push ebx +0x4a118b: call 0x41bd60 +0x4a1190: add esp, 0xc +0x4a1193: mov ecx, dword ptr [ebp - 0x38] +0x4a1196: mov dword ptr [ecx], 0x18 +0x4a119c: mov eax, dword ptr [ebp - 0x38] +0x4a119f: mov word ptr [eax + 4], 0 +0x4a11a5: mov edx, dword ptr [ebp - 0x38] +0x4a11a8: mov ecx, dword ptr [ebx + 0x3f4] +0x4a11ae: mov dword ptr [edx + 0x10], ecx +0x4a11b1: mov eax, dword ptr [ebp - 0x38] +0x4a11b4: mov edx, dword ptr [esi + 0x128] +0x4a11ba: mov dword ptr [eax + 0x14], edx +0x4a11bd: mov ecx, dword ptr [esi + 0x128] +0x4a11c3: mov dword ptr [ebx + 0x6b4], ecx +0x4a11c9: jmp 0x4a11d9 + +===== case 1 + default + epilogue @0x4a11cb..0x4a1232 ===== +0x4a11cb: push edi +0x4a11cc: mov eax, dword ptr [ebp + 0xc] +0x4a11cf: push eax +0x4a11d0: push ebx +0x4a11d1: call 0x4225a4 +0x4a11d6: add esp, 0xc +0x4a11d9: mov eax, dword ptr [ebx + 0x3c] +0x4a11dc: mov edx, dword ptr [ebx + 0x40] +0x4a11df: cmp eax, edx +0x4a11e1: je 0x4a121e +0x4a11e3: cmp eax, 2 +0x4a11e6: jne 0x4a121e +0x4a11e8: mov esi, ebx +0x4a11ea: push esi +0x4a11eb: lea eax, [ebp - 0x64] +0x4a11ee: push eax +0x4a11ef: call 0x436668 +0x4a11f4: add esp, 8 +0x4a11f7: lea edx, [ebp - 0x64] +0x4a11fa: push edx +0x4a11fb: push esi +0x4a11fc: mov ecx, dword ptr [0x4efc94] ; [0x4efc94]=0f +0x4a1202: mov eax, dword ptr [ecx + 0x38] +0x4a1205: push eax +0x4a1206: call 0x4364e4 +0x4a120b: add esp, 0xc +0x4a120e: mov eax, dword ptr [ebx + 0x40] +0x4a1211: push eax +0x4a1212: add ebx, 0x2c +0x4a1215: push ebx +0x4a1216: call 0x41bbd8 +0x4a121b: add esp, 8 +0x4a121e: pop edi +0x4a121f: pop esi +0x4a1220: pop ebx +0x4a1221: mov esp, ebp +0x4a1223: pop ebp +0x4a1224: ret +0x4a1225: add byte ptr [eax], al +0x4a1227: add byte ptr [eax], al +0x4a1229: add byte ptr [eax], ah +0x4a122b: inc ecx +0x4a122c: push ebp +0x4a122d: mov ebp, esp +0x4a122f: add esp, -0x34