From b00fa39af7400d95f53c6784abab1f2a4e8438f4 Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Thu, 13 Aug 2026 22:25:38 -0500 Subject: [PATCH] #83 FIXED: the incoming-missile alarm sounds again -- the July F7 driver was wired into DEAD CODE. Root cause: the alarm feed (cc2b109) lived in Missile::MoveAndCollide, but the world-entity Missile is a BLOCKED TRANSCRIPTION (2007 Entity-base mismatch) that never flies; live flight is the BTPushProjectile simulator in mech4.cpp -- so IncomingLock/DistanceToMissile stayed 0/FLT_MAX in every field build (solo AND MP) while the doc header said F7 fixed. Fix: the feed moves to the push simulator's per-frame update -- salvo LEAD only (the port's cluster = the binary's ONE Missile per trigger, @004bf5b4 ctor-registration into target+0x418 read by the target tick @004a9b5c: lock=1, dist=|missile-mech| entity positions); mirror leads on peer nodes report identically (mislanch resolves the live target by replicated ID), which is what puts the alarm in the VICTIM's cockpit -- inbound missiles are always mirrors there. Bridge hardened: BTIsRegisteredMech class check (the binary's 0x50bdb4 check) + the port's latched-death predicate 2||9 (@0049fb54). New: [incoming] receipt (BT_INCOMING_LOG, edges + 1Hz held) and BT_DESIGNATE=enemy (bench designator: production designation slots stamped with the nearest live mech on no-mech frames only -- standoff locks the boresight pick can't hold). Transcription block kept in missile.cpp with an INERT banner so the trap can't re-bite. Bench (2-node standoff LRM duel, 700-800u): 54/55 lock edges per node, dist closing 792->87 over ~2.2s, beeper STARTs with LIVE authored tempo (10 far -> 435 close, the 100..800->600..10 map) -- on replicant mirrors, i.e. the MP case. Era witness: Oracle identified [1:74] IncomingAlarm01 n58 on the soundboard same day ('sped up in the pods'). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD --- game/reconstructed/mech.hpp | 11 +++- game/reconstructed/mech4.cpp | 108 ++++++++++++++++++++++++++++++--- game/reconstructed/missile.cpp | 29 ++++++--- 3 files changed, 129 insertions(+), 19 deletions(-) diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index c0e736d..74ce490 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -1019,9 +1019,14 @@ protected: public: int MovementMode() { return (int)GetSimulationState(); } void SetMovementMode(int m) { SetSimulationState((unsigned)m); } - // (AUDIO_FIDELITY F7) called by each homing Missile per MoveAndCollide - // tick; PerformAndWatch latches the accumulators into the published - // IncomingLock/DistanceToMissile attributes each frame. + // (AUDIO_FIDELITY F7 / #83) called by each inbound Missile per + // MoveAndCollide tick; PerformAndWatch latches the accumulators into + // the published IncomingLock/DistanceToMissile attributes each frame. + // [T3 micro-semantic] the binary (@004a9b5c) reads the FIRST member + // of the mech's inbound socket list, not the minimum; the list's + // insertion order is unresolved (Node/Plug layer), so we keep MIN -- + // observable only with 2+ simultaneous inbound missiles, where min + // = nearest = fastest beep (the conservative reading). void ReportIncomingMissile(Scalar range) { incomingLockNext = 1; diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index c484fe2..107fbd1 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1793,6 +1793,23 @@ static void p.target = 0; // fuze disarmed with the lock } } + // (#83/F7) THE INCOMING-MISSILE ALARM FEED lives HERE, in the live + // flight simulator -- NOT in missile.cpp (the world-entity Missile is + // a blocked transcription that never flies; the July F7 driver rode + // it, which is why the alarm stayed silent in every field build). + // Binary contract: ONE cluster Missile per trigger registers into its + // target mech's inbound socket list at ctor (@004bf5b4 tail) and the + // TARGET's tick reads it (@004a9b5c: lock=1, dist=|missile-mech|). + // The salvo LEAD is the port's cluster (task #62), so lead-only = + // one report per salvo = the binary's one registration; mirror leads + // on peer nodes report identically (mislanch.cpp resolves the live + // target by replicated ID), which is what puts the alarm in the + // VICTIM's cockpit -- inbound missiles are always mirrors there. + if (p.guided && p.salvoLead && p.target != 0) + { + extern void BTReportIncomingMissile(Entity *target, const Point3D &missile_position); + BTReportIncomingMissile((Entity *)p.target, p.pos); + } if (p.guided) { Point3D aim = p.targetPos; @@ -6588,12 +6605,59 @@ void targetReticle.rayIntersection = farPt; rlPickClass = 4; // BT_RANGE_LOG: max-range default (issue #4) } + else { MECH_TARGET_ENTITY(this) = 0; // no world sentinel -> no target MECH_TARGET_SUBIDX(this) = -1; } + // BENCH DESIGNATOR (#83 rig, env-gated, off by default): + // BT_DESIGNATE=enemy overrides the frame's designation with the + // nearest live mech whenever the boresight pick found NO mech -- + // a standoff missile duel needs locks at ranges where the enemy + // subtends too small an angle for the self-driven aim to hold. + // A real mech pick still wins (this fills no-mech frames only), + // and the stamped state is exactly the production designation + // (entity slot + aim point + zone -1), so everything downstream + // -- fire gates, seeker, incoming-alarm feed -- runs authentic. + // The HUD reticle is left alone (no fake lock ring). + { + static int s_desig = -1; + if (s_desig < 0) + { + const char *dv = getenv("BT_DESIGNATE"); + s_desig = (dv != 0 && !stricmp(dv, "enemy")) ? 1 : 0; + } + extern int BTIsRegisteredMech(Entity *e); + if (s_desig == 1 + && (pickTarget == 0 || !BTIsRegisteredMech(pickTarget))) + { + extern int BTGetTargetCandidates(Entity *shooter, Entity **out, int maxOut); + Entity *ec[32]; + const int enc = BTGetTargetCandidates((Entity *)this, ec, 32); + float best = 1e30f; + Mech *bm = 0; + for (int ei = 0; ei < enc; ++ei) + { + Mech *em = (Mech *)ec[ei]; + if (em == 0 || em->IsMechDestroyed()) continue; + float edx = (float)em->localOrigin.linearPosition.x + - (float)localOrigin.linearPosition.x; + float edz = (float)em->localOrigin.linearPosition.z + - (float)localOrigin.linearPosition.z; + float ed2 = edx*edx + edz*edz; + if (ed2 < best) { best = ed2; bm = em; } + } + if (bm != 0) + { + MECH_TARGET_ENTITY(this) = (Entity *)bm; + MECH_TARGET_SUBIDX(this) = -1; + MECH_TARGET_POS(this) = bm->localOrigin.linearPosition; + } + } + } + // [parallax] diag (issue #16): project the pick point back into the // RENDER view -- the frame the crosshair is drawn in. Reticle +y is // down, so ry > 0 = the pick lands BELOW the crosshair. A healthy @@ -8868,6 +8932,20 @@ void // Missile::MoveAndCollide since our last frame, then re-arm. The authored // beeper matches incomingLock 1/0 and the tempo scale reads the range -- // both polled by ExecuteWatchers below. + // (#83 receipt) BT_INCOMING_LOG: lock edges always; ~1Hz while locked. + if (getenv("BT_INCOMING_LOG")) + { + static int s_inCount = 0; + if ((incomingLock == 0) != (incomingLockNext == 0)) + DEBUG_STREAM << "[incoming] " << (incomingLockNext ? "LOCK" : "clear") + << " mech=" << (void *)this + << " dist=" << (float)distanceToMissileNext + << " t=" << (GetTickCount() % 1000000) << "\n" << std::flush; + else if (incomingLockNext != 0 && (s_inCount++ % 60) == 0) + DEBUG_STREAM << "[incoming] held mech=" << (void *)this + << " dist=" << (float)distanceToMissileNext + << " t=" << (GetTickCount() % 1000000) << "\n" << std::flush; + } incomingLock = incomingLockNext; distanceToMissile = distanceToMissileNext; incomingLockNext = 0; @@ -9720,18 +9798,32 @@ void } //########################################################################### -// BTReportIncomingMissile -- complete-type bridge (F7) +// BTReportIncomingMissile -- complete-type bridge (F7/#83) // // Missile::MoveAndCollide (missile.cpp, forward-declared Mech only) reports -// its target + range each tick; the missile-alarm attributes latch in -// Mech::PerformAndWatch. Seeker targets are always mechs in this game -// (the launcher's lock); null-guarded. +// its CONSTRUCTION target each tick; the missile-alarm attributes latch in +// Mech::PerformAndWatch. Binary contract (@004bf5b4 ctor registration + +// @004a9b5c target-tick read): the ctor class-checks the target as a Mech +// (vs 0x50bdb4) before the socket Add -- seeker targets are always mechs +// in this game; distance = |missilePos - mechPos| from the ENTITY +// positions (not the seeker lead); a dead target's inbound list is +// emptied on the death path (states 9/10), mirrored here as a skip. //########################################################################### -void BTReportIncomingMissile(Entity *target, Scalar range) +void BTReportIncomingMissile(Entity *target, const Point3D &missile_position) { - if (target != 0) - { - ((Mech *)target)->ReportIncomingMissile(range); + extern int BTIsRegisteredMech(Entity *e); + if (target != 0 && BTIsRegisteredMech(target)) // the binary class-checks the + { // ctor target vs Mech (0x50bdb4) + Mech *mech = (Mech *)target; + const int mm = mech->MovementMode(); + if (mm == 2 || mm == 9) // the port's latched death modes + return; // (@0049fb54 predicate) -- the + // binary empties the inbound list + // on the death path + Vector3D to_missile; + to_missile.Subtract(missile_position, + mech->localOrigin.linearPosition); + mech->ReportIncomingMissile(to_missile.Length()); } } diff --git a/game/reconstructed/missile.cpp b/game/reconstructed/missile.cpp index 9ac1ebb..0248186 100644 --- a/game/reconstructed/missile.cpp +++ b/game/reconstructed/missile.cpp @@ -235,15 +235,28 @@ void Missile::MoveAndCollide(Scalar time_slice) if (thruster != 0) thruster->MissileThrusterSimulation(time_slice); // bleed burnTimeRemaining - // (AUDIO_FIDELITY F7) report the threat to the homing target: the mech's - // IncomingLock/DistanceToMissile attributes (binary @0x3fc/0x400) drive - // the authored missile-alarm beeper whose TEMPO rises as we close. - if (seeker != 0 && seeker->targetEntity != 0 && (simulationFlags & 1) == 0) + // (AUDIO_FIDELITY F7 / #83) *** TRANSCRIPTION ONLY -- THIS NEVER RUNS *** + // The world-entity Missile is blocked (2007 engine Entity-base mismatch); + // live flight is the BTPushProjectile simulator, and the WORKING alarm + // feed is there (mech4.cpp, the salvo-lead report in the push update + // loop). The July F7 driver lived only here -- a month of field silence + // (#83) because the reporter rode a class that never flies. Kept for + // the day the entity unblocks: + // report the threat to the CONSTRUCTION target, + // not the seeker's live target: the binary registers the missile into the + // target mech's inbound socket list in the ctor tail (@004bf5b4: + // this+0x34c class-checked as Mech, then target+0x418 list Add) and the + // TARGET'S OWN tick (@004a9b5c) reads that list -- IncomingLock=1 + + // DistanceToMissile = |missilePos - mechPos| (entity positions, NOT the + // seeker lead point). Registration survives a seeker lock-drop; it ends + // only with the missile (socket weak-ref) or the target's death (the + // death path walks + empties the list -- the bridge mirrors that with a + // dead-target skip). The old seeker-gated lead-point report understated + // range and went silent on lock-drop. + if (targetConnection != 0 && (simulationFlags & 1) == 0) { - extern void BTReportIncomingMissile(Entity *target, Scalar range); - Vector3D to_target; - to_target.Subtract(seeker->targetPosition, localOrigin.linearPosition); - BTReportIncomingMissile(seeker->targetEntity, to_target.Length()); + extern void BTReportIncomingMissile(Entity *target, const Point3D &missile_position); + BTReportIncomingMissile((Entity *)targetConnection, localOrigin.linearPosition); } // --- guidance: build steering toward the Seeker's lead point ----------