From cc2b109cc8c4280544fd38a0b2b668f1b68d5069 Mon Sep 17 00:00:00 2001 From: arcattack Date: Thu, 16 Jul 2026 13:58:08 -0500 Subject: [PATCH] Audio Phase 4a (AUDIO_FIDELITY F6/F7): coolant-leak warning + missile alarm F6 ReportLeak (the largest dead authored block -- 38 of 54 match watchers): recovered the binary HeatSink attribute table (16-byte {id,name,off+1} rows @0x50e438..0x50e4c8, ids 3..12) [T1] -- it confirms EVERY existing heat binding (CurrentTemperature@0x114 .. CoolantMassLeakRate@0x130, HeatSink@ 0x164) and adds the one row we never published: ReportLeak (id 12) -> +0x138 = coolantActive, the INT leak hysteresis flag UpdateCoolant (@004adbf8) drives 1/0 around draw 0.003/0.0025. PoweredSubsystem derives from HeatSink, so the single row serves all 19 authored leak watchers through the chained index, exactly like the binary. MechWeapon's pinned-id pad absorbed the +1 chain shift (0x0E -> 0x0F, tripwire fired as designed). F7 IncomingLock/DistanceToMissile (missile alarm): binary Mech table walked in full (ids 21..56 [T1] -- also settles FootStep@0x394, CollisionSpeed@ 0x4B4, UnstablePercentage@0x3F0, ReduceButton@0x340 for the next findings). IncomingLock id 54 @0x3fc, DistanceToMissile id 56 @0x400; the old "maxSpeed @0x400 = FLT_MAX" member was a MISREAD of the far default and is retired (its 1000.0f "override" was RadarRange id 47 @0x404, already published). Real members + accumulators: Missile::MoveAndCollide reports target + range each tick (BTReportIncomingMissile bridge); PerformAndWatch latches per frame. The authored beeper (match 1/0) + range->TEMPO scale (100..800 -> 600..10, accelerating as the missile closes) read them unchanged. Drive is intent-level [T3]; init 0/FLT_MAX matches the binary reset (part_012.c:9446-9447). Regression (30s): stable; ReportLeak binds real on every subsystem (0 pad redirects); DistanceToMissile binds with FLT_MAX live; attrnull 41 -> 3. Co-Authored-By: Claude Opus 4.8 (1M context) --- game/reconstructed/heat.cpp | 8 +++++++- game/reconstructed/heat.hpp | 8 ++++++++ game/reconstructed/mech.cpp | 22 ++++++++++++++++++---- game/reconstructed/mech.hpp | 28 ++++++++++++++++++++++++++-- game/reconstructed/mech4.cpp | 25 +++++++++++++++++++++++++ game/reconstructed/mechweap.cpp | 10 ++++------ game/reconstructed/mechweap.hpp | 8 +++++--- game/reconstructed/missile.cpp | 11 +++++++++++ 8 files changed, 104 insertions(+), 16 deletions(-) diff --git a/game/reconstructed/heat.cpp b/game/reconstructed/heat.cpp index 923a195..5bd5c62 100644 --- a/game/reconstructed/heat.cpp +++ b/game/reconstructed/heat.cpp @@ -417,7 +417,13 @@ const HeatSink::IndexEntry ATTRIBUTE_ENTRY(HeatSink, DegradationPressure, coolantEfficiency), // @0x124 ATTRIBUTE_ENTRY(HeatSink, CoolantMassLeakRate, coolantDraw), // @0x130 (LeakGauge; damage-driven) ATTRIBUTE_ENTRY(HeatSink, HeatSink, linkedSinks), // @0x164 (Eng linked-sink temp readout) - ATTRIBUTE_ENTRY(HeatSink, ValveSetting, coolantFlowScale) // @0x15C (condenser valve slider @2; init 1.0f) + ATTRIBUTE_ENTRY(HeatSink, ValveSetting, coolantFlowScale), // @0x15C (condenser valve slider @2; init 1.0f) + // (AUDIO_FIDELITY F6) the coolant-leak warning: binary id 12 @0x138 = the + // coolantActive leak flag (binary table @0x50e4c8; watchers match ==1/==0). + // PoweredSubsystem (weapons/sensor/myomers) DERIVES from HeatSink, so this + // single row serves ALL 19 authored leak watchers through the chained + // index -- exactly like the binary (one ReportLeak row in the image). + ATTRIBUTE_ENTRY(HeatSink, ReportLeak, coolantActive) // @0x138 (leak-active hysteresis flag) }; HeatSink::AttributeIndexSet& diff --git a/game/reconstructed/heat.hpp b/game/reconstructed/heat.hpp index 9228691..8448fe7 100644 --- a/game/reconstructed/heat.hpp +++ b/game/reconstructed/heat.hpp @@ -369,6 +369,14 @@ inline int CoolantMassLeakRateAttributeID, // @0x130 coolantDraw (damage-driven leak) HeatSinkAttributeID, // @0x164 linkedSinks (link to master sink) ValveSettingAttributeID, // @0x15C coolantFlowScale (condenser valve slider @2) + // (AUDIO_FIDELITY F6) the binary heat attribute table (16-byte rows + // {id, name, off+1, pad} @0x50e438..0x50e4c8, ids 3..12) confirms + // EVERY existing binding above [T1] and adds ReportLeak (id 12) -> + // +0x138 = coolantActive, the INT leak hysteresis flag UpdateCoolant + // (@004adbf8 [0x4e]) drives 1 above draw 0.003 / 0 below 0.0025. + // The authored Logical match watchers (19 subsystems, ==1 Start / + // ==0 Stop of the looped 3-note leak sequence) fire on it directly. + ReportLeakAttributeID, NextAttributeID }; private: diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 8f227cf..283250b 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -735,9 +735,13 @@ const Mech::IndexEntry ATTRIBUTE_ENTRY(Mech, RequestDuckAnimation, attrPad), // 0x33 ATTRIBUTE_ENTRY(Mech, UnstablePercentage, attrPad), // 0x34 ATTRIBUTE_ENTRY(Mech, SuperStop, attrPad), // 0x35 - ATTRIBUTE_ENTRY(Mech, IncomingLock, attrPad), // 0x36 + // (AUDIO_FIDELITY F7) real missile-alarm backing: binary ids 54/56 @0x3fc/ + // 0x400 [T1 table @0x50c0b0/0x50c0d0]; the authored beeper (match 1/0) + + // tempo scale (range 100..800 -> tempo 600..10) read these. Driven by + // Missile::MoveAndCollide via BTReportIncomingMissile (mech4.cpp). + ATTRIBUTE_ENTRY(Mech, IncomingLock, incomingLock), // 0x36 ATTRIBUTE_ENTRY(Mech, DuckState, duckState), // 0x37 (crouch posture, int) - ATTRIBUTE_ENTRY(Mech, DistanceToMissile, attrPad) // 0x38 + ATTRIBUTE_ENTRY(Mech, DistanceToMissile, distanceToMissile) // 0x38 }; Mech::AttributeIndexSet& @@ -850,7 +854,9 @@ Mech::Mech( mechNameFilter.Initialize(); // FUN_00435a7c(this+0xdb) masterAlarm = AlarmIndicator(0x21); // FUN_0041b9ec(this+0xe7,0x21) stateFlags = 0; // Wword(0xff) - maxSpeed = DAT_005209d0; // FLT_MAX -> Wword(0x100) + // (F7 correction) the binary's 0x400 = FLT_MAX init is DistanceToMissile's + // "no missile" far default (attr id 56), NOT a maxSpeed -- the old member + // is retired; distanceToMissile (init below) owns the slot's meaning. Wword(0x104) = 0; Wword(0x106).Construct(0); // FUN_004a4d60 (slot member) Wword(0x10f).LinkTo(this + 0x61); // FUN_00420ea4 @@ -934,6 +940,12 @@ Mech::Mech( radarLinearPosition = &localOrigin.linearPosition; // map reads the mech's live world position... radarAngularPosition= &localOrigin.angularPosition; // ...and orientation (pointers into the base origin) duckState = 0; // not crouching + // (AUDIO_FIDELITY F7) missile alarm: the binary reset writes 0 / FLT_MAX + // (part_012.c:9446-9447; FLT_MAX = "no missile" far default) + incomingLock = 0; + distanceToMissile = FLT_MAX; + incomingLockNext = 0; + distanceToMissileNext = FLT_MAX; ZeroBlock(this + 0xca, 6); // this[0xca..0xcf] = 0 ZeroBlock(this + 0x153, 6); // this[0x153..0x158] = 0 @@ -1014,7 +1026,9 @@ Mech::Mech( SetPerformance(PTR_LAB_0050c0f4); // master performance } - maxSpeed = 0x447a0000; // 1000.0f -> Wword(0x101) (override) + // (F7 correction) the old "maxSpeed = 1000.0f override" here targeted + // Wword(0x101) = +0x404 -- the binary table says that is RadarRange + // (id 47), already initialized as radarRange = 1000.0f above. Wword(0x102) = 0; Wword(0x103) = (int)(this + 0x43); diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index ac78da9..bd21217 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -723,8 +723,10 @@ protected: // protected: BTVal mechName; // @0x360 this[0xd8] (proxy: .Copy) - Scalar maxSpeed; // @0x400 this[0x100] = FLT_MAX - int stateFlags; // @0x410 this[0x104] + // (F7) `Scalar maxSpeed @0x400` was a MISREAD -- the binary attribute + // table names 0x400 DistanceToMissile (id 56; FLT_MAX = far default); + // the member is retired in favor of distanceToMissile below. + int stateFlags; // @0x410 this[0x104] (binary attr table: RearFiring, id 50) int throttleState; // @0x4a4 this[0x129] = 2 (initial) int heatLevel; // @0x518 this[0x146] int heatCapacity; // @0x51c this[0x147] = 0.6 * heatLevel @@ -865,6 +867,19 @@ protected: Point3D *radarLinearPosition; // 0x30 RadarLinearPosition Quaternion *radarAngularPosition; // 0x31 RadarAngularPosition int duckState; // 0x37 DuckState (crouch posture) + // (AUDIO_FIDELITY F7) the incoming-missile alarm attributes. Binary + // Mech table [T1]: IncomingLock id 54 @0x3fc (Logical; authored match + // ==1 Start / ==0 Stop of the looped beeper), DistanceToMissile id 56 + // @0x400 (Scalar; authored scale maps range 100..800 -> TEMPO 600..10, + // the beep accelerating as the missile closes). The mech reset inits + // them 0 / FLT_MAX (part_012.c:9446-9447); the live writer is in an + // un-exported gap, so the drive is intent-level [T3]: every homing + // Missile reports its target + range per MoveAndCollide tick into the + // *Next accumulators; PerformAndWatch latches them each frame. + int incomingLock; // 0x36 IncomingLock (1 = missile inbound) + Scalar distanceToMissile; // 0x38 DistanceToMissile (FLT_MAX = none) + int incomingLockNext; // accumulator (missiles OR into it) + Scalar distanceToMissileNext; // accumulator (missiles MIN into it) // --- raw / engine-shim members touched by the ctor slice ------------- void *vtable; // installed Mech vtable ptr @@ -890,6 +905,15 @@ 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. + void ReportIncomingMissile(Scalar range) + { + incomingLockNext = 1; + if (range < distanceToMissileNext) + distanceToMissileNext = range; + } // (the members below were and stay PUBLIC -- the recon TUs // (btplayer/dmgtable/mechmppr/emitter) reach them directly) int movementFlags; diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index d902dd9..a3aec76 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -5274,6 +5274,15 @@ void } } + // (AUDIO_FIDELITY F7) latch the incoming-missile report accumulated by + // 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. + incomingLock = incomingLockNext; + distanceToMissile = distanceToMissileNext; + incomingLockNext = 0; + distanceToMissileNext = FLT_MAX; + if (getenv("BT_AUDIO_LOG")) { static int s_wd=0; if ((s_wd++ % 300)==0) DEBUG_STREAM << "[audio] mech watcher poll: delayed=" << (int)AreWatchersDelayed() << " audioWatchers=" << DebugAudioWatcherCount() << " simFlags=0x" << std::hex << (unsigned)simulationFlags << std::dec @@ -5971,3 +5980,19 @@ void << ") rgb=(" << r << "," << g << "," << b << ")" << std::endl; } } + +//########################################################################### +// BTReportIncomingMissile -- complete-type bridge (F7) +// +// 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. +//########################################################################### +void BTReportIncomingMissile(Entity *target, Scalar range) +{ + if (target != 0) + { + ((Mech *)target)->ReportIncomingMissile(range); + } +} diff --git a/game/reconstructed/mechweap.cpp b/game/reconstructed/mechweap.cpp index f19b041..d6d55c1 100644 --- a/game/reconstructed/mechweap.cpp +++ b/game/reconstructed/mechweap.cpp @@ -123,16 +123,14 @@ const MechWeapon::IndexEntry // binary's (layout static_assert-locked); TriggerState stays the streamed // fire-button binding (id 0x13 PINNED). // - // The five PADS fill our chain-vs-binary id gap (0x0D..0x11) with valid, + // The PADS fill our chain-vs-binary id gap (now 0x0F..0x11) with valid, // named, never-bound entries (the binary's own ids here are the powered/ // aux-screen family we don't publish); target = rechargeLevel, harmless. - { (int)MechWeapon::MechWeaponPadFirstAttributeID + 0, "MechWeaponPad0E", + { (int)MechWeapon::MechWeaponPadFirstAttributeID + 0, "MechWeaponPad0F", (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, - { (int)MechWeapon::MechWeaponPadFirstAttributeID + 1, "MechWeaponPad0F", + { (int)MechWeapon::MechWeaponPadFirstAttributeID + 1, "MechWeaponPad10", (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, - { (int)MechWeapon::MechWeaponPadFirstAttributeID + 2, "MechWeaponPad10", - (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, - { (int)MechWeapon::MechWeaponPadFirstAttributeID + 3, "MechWeaponPad11", + { (int)MechWeapon::MechWeaponPadFirstAttributeID + 2, "MechWeaponPad11", (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, ATTRIBUTE_ENTRY(MechWeapon, PercentDone, rechargeLevel), // 0x12 @0x320 ATTRIBUTE_ENTRY(MechWeapon, TriggerState, fireImpulse), // 0x13 @0x31C diff --git a/game/reconstructed/mechweap.hpp b/game/reconstructed/mechweap.hpp index 468a899..5a9a9f3 100644 --- a/game/reconstructed/mechweap.hpp +++ b/game/reconstructed/mechweap.hpp @@ -184,12 +184,14 @@ class CockpitHud; // is a latent AV (gotcha #11; the old pinned table survived on // heap luck -- the renumber reshuffled allocations and it fired // live in WeaponCluster's "PercentDone" resolve). Pads below fill - // ids 0x0E..0x11 (the mech.cpp attrPad idiom). F15: the base + // ids 0x0F..0x11 (the mech.cpp attrPad idiom). F15: the base // MechSubsystem now publishes ConfigureActivePress (binary id 2), // shifting the parent chain end 0x0D -> 0x0E -- which matches the // binary's own numbering (the old 0x0D chain was one LOW because - // the base attribute was missing). One pad absorbed. - MechWeaponPadFirstAttributeID = 0x0E, // == PoweredSubsystem::NextAttributeID (locked below) + // the base attribute was missing). One pad absorbed. F6: HeatSink + // now publishes ReportLeak (binary id 12) -> 0x0E -> 0x0F; another + // pad absorbed (each authentic parent attr closes the gap by one). + MechWeaponPadFirstAttributeID = 0x0F, // == PoweredSubsystem::NextAttributeID (locked below) PercentDoneAttributeID = 0x12, // @0x320 rechargeLevel TriggerStateAttributeID, // 0x13 @0x31C (PINNED -- the fire-button binding) DistanceToTargetAttributeID, // 0x14 @0x324 rangeToTarget diff --git a/game/reconstructed/missile.cpp b/game/reconstructed/missile.cpp index b4a531a..4b3faa3 100644 --- a/game/reconstructed/missile.cpp +++ b/game/reconstructed/missile.cpp @@ -219,6 +219,17 @@ 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) + { + extern void BTReportIncomingMissile(Entity *target, Scalar range); + Vector3D to_target; + to_target.Subtract(seeker->targetPosition, localOrigin.linearPosition); + BTReportIncomingMissile(seeker->targetEntity, to_target.Length()); + } + // --- guidance: build steering toward the Seeker's lead point ---------- Vector3D forward; GetForward(forward); // FUN_0040d150(.., +0x100)