Torso: the TWIST goes LIVE -- electrical watchdog chain, centered crosshair, coherent controls (task #57/#58)

The MadCat torso twists, the view turns with it, and targeting follows.
Three reconstruction fronts closed:

THE ELECTRICAL WATCHDOG CHAIN (why the torso never powered up):
- PowerWatcher::UpdateWatch reconstructed (@004b181c, the REAL registered
  Performance -- PTR @0050f5fc; Ghidra missed the fn start): the watchdog
  MIRRORS the watched subsystem's electrical level (+0x278), brownout
  downgrade when gen output <= minVoltage% x rated.  @004b1804 relabeled
  ResetToInitialState (slot 10) -- the old "Simulation" tag was wrong.
- The factory watcher-CONNECT pass reconstructed (vtable slot +0x38,
  @004aee2c/@004b1a40 byte-identical, recovered from raw exe bytes):
  watchedLink.Add(roster[watchedSubsystem]) on the master node.  Was the
  SubProxy::Start() no-op -- every watchdog sat at 0 forever.
- MinVoltageScale = 0.01 (a 10-byte x87 literal @0x4b1924; was 1.0f =
  permanent brownout) and PowerWatcher's Derivation chains its REAL base
  HeatWatcher (the HeatableSubsystem stand-in broke IsDerivedFrom for the
  whole Torso/Searchlight/ThermalSight family).
- KB correction swept: derivation tag 0x50e604 = HEATWATCHER (not
  "HeatSink"); the btl4gaug heat-widget gate now tests it via the
  BTIsHeatWatcher bridge.

THE CROSSHAIR (task #58 forensics, 6-agent workflow + live probes):
- The VIEW is TORSO-MOUNTED: jointtorso -> jointeye -> siteeyepoint in
  every twist-capable .SKL; the camera + canopy ride the same hinge
  subtree through HingeRenderable's live matrix-stack compose -- ALREADY
  WORKING in the port.  The crosshair stays screen-centered (center IS
  the boresight); the twist reads on the tape carets/compass/radar.
- The real bug was the port's gBTAimX = tan(twist) slew (the falsified
  "body-mounted view" model): the camera already carried the twist, so
  the crosshair counter-slid to hull-forward and the fire ray with it.
  Deleted; the pick ray inherits the twist from the yawing eye basis.
- Two instrumentation traps documented (chase-eye-as-default-camera,
  BT_FORCE_TORSO clobbering real joints -> the hook now only fills
  unresolved ones); an over-correcting explicit eye compose was added on
  those false readings and retired the same day.

CONTROLS + REPLICATION:
- Q/E spring-center on release (the axis is a twist-RATE demand; the old
  hold-deflection model drifted forever); X also zeroes the axis and
  pulses the authentic torso Recenter (@004b6918).  M cycles control
  mode via the real CycleControlMode body.
- Torso update-record DIRECTION fixed: engine truth is Write=serialize /
  Read=apply; @004b6a78 is the READ (was mislabeled Write) and the
  missing WRITE @004b6a1c recovered from raw disasm (recordLength 0x1C,
  twist/vel/rate at +0x10/14/18) -- kills the replicant's 0xCDCDCDCD
  -140-degree ghost twist.
- Marching-ghost desync: 4 Standing-case guards zero stale reverse
  cycleSpeed (negative cadence passed the <= ZeroSpeed stop gate).
- Kill credit rerouted to the OBSERVED killer (lastInflictingID ->
  killer's player link) -- kills count, target K/D populates.

KB: subsystems.md (watcher chain), multiplayer.md (record direction),
combat-damage.md + gauges-hud.md + cockpit-view.md (torso-mounted view
re-correction), decomp-reference.md (new addresses + tag fix),
open-questions.md (dead capability-roster loops 2-4, snapshot CD read).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-13 13:27:49 -05:00
co-authored by Claude Fable 5
parent 065c114590
commit 02cdfd6576
29 changed files with 1112 additions and 124 deletions
+66 -3
View File
@@ -6,6 +6,11 @@
// (Authentic path = the gyro-driven eye-joint DCS chain -- deferred.)
float gBTEyeBobY = 0.0f;
float gBTEyeSwayX = 0.0f; // lateral weight-shift (the walk "swagger"), same source
// task #58: the player torso's live twist (rad), published by mech4.cpp's HUD
// tick. NOT consumed by the eye anymore -- the cockpit eye inherits the twist
// through the jointtorso HingeRenderable in the draw traversal (see the note in
// DPLEyeRenderable::Execute). Kept published for diagnostics/correlation.
float gBTEyeTwist = 0.0f;
#pragma hdrstop
#include "l4vidrnd.h"
@@ -1340,6 +1345,21 @@ void
//
if(oldHinge != *myHinge)
{
// task #58 probe (BT_HINGE_LOG): which hinge renderables actually see
// their joint move (standing + BT_FORCE_TORSO => only the torso hinge
// and any doors change). kids = child renderables that would inherit
// the rotation through the stack.
{
static const int s_hl = getenv("BT_HINGE_LOG") ? 1 : 0;
static int s_hln = 0;
if (s_hl && (s_hln++ % 60) == 0)
DEBUG_STREAM << "[hinge] this=" << (void*)this
<< " hinge=" << (void*)myHinge
<< " rot=" << (float)(Radian)(myHinge->rotationAmount)
<< " kids=" << (int)(End() - Enumerate())
<< " par=" << (void*)GetParentProbe()
<< "\n" << std::flush;
}
oldHinge = *myHinge;
hingeOffsetMatrix.BuildIdentity();
//#if SINGLE_AXIS_HINGE
@@ -5604,6 +5624,24 @@ void
// that assumed +Z=forward/+Y=up (which mis-framed the cockpit); the look/up axes now
// fall out of the eye's own basis. pos/at/up above are kept only for the aim-boresight.
D3DXMATRIX eyeW = mat4.ToD3DMatrix();
// task #58 NOTE (torso twist vs the camera): the COCKPIT eye needs
// NO explicit twist compose -- it executes inside the mech's draw
// traversal under jointtorso's HingeRenderable (Torso::PushTwist ->
// Joint::SetRotation -> hinge rotationAmount; HingeRenderable
// multiplies the live hinge into the matrix stack), so `prev` above
// already carries the twist and the view yaws authentically with
// the canopy. VERIFIED live: with the real jointtorso swept, the
// active cockpit [eyefwd] swings ~60 deg and the torso hinge sits
// in the eye's parent chain (kids=2). An explicit
// W' = W*E^-1*R_y(twist)*E compose was briefly added here and
// DOUBLE-ROTATED the cockpit view (2x view vs 1x canopy: "the HUD
// passes through the cockpit frame") and swung the CHASE camera
// (parented on the root, same class) with the twist ("torso fixed,
// legs twisting"). Both "eye is frozen" measurements that motivated
// it were instrumentation artifacts: (1) the chase eye was the
// active camera in the headless default view; (2) BT_FORCE_TORSO
// used to redirect the sweep into the SHADOW hinge (see the hook's
// trap note in torso.cpp).
D3DXMATRIX view;
D3DXMatrixInverse(&view, NULL, &eyeW);
@@ -5618,6 +5656,30 @@ void
D3DXMatrixInverse(&view, NULL, &eyeW);
}
}
// TWIST-FOLLOW PROBE (task #58, BT_EYE_LOG): does the ACTIVE eye's
// forward yaw with the torso twist? Correlate with [torso] twist.
// Also print the STACK TOP the eye composed with (prev) -- its
// translation tells whether the eye executed inside the full
// parent-chain traversal (t ~= eye rest position) or with a bare
// stack (t == entity origin / zero).
{
static const int s_eyeLog = getenv("BT_EYE_LOG") ? 1 : 0;
static int s_eyeLogN = 0;
if (s_eyeLog && myRenderer->mCamera == this && (s_eyeLogN++ % 120) == 0)
{
DEBUG_STREAM << "[eyefwd] this=" << (void*)this
<< " fwd=(" << eyeFwdW.x << "," << eyeFwdW.y
<< "," << eyeFwdW.z << ") stackT=(" << prev(3,0) << ","
<< prev(3,1) << "," << prev(3,2) << ") chain:";
HierarchicalDrawComponent *p = GetParentProbe();
for (int pc = 0; p != NULL && pc < 8; ++pc)
{
DEBUG_STREAM << " " << (void*)p;
p = p->GetParentProbe();
}
DEBUG_STREAM << "\n" << std::flush;
}
}
if (dbgEyeExec < 8)
{
DEBUG_STREAM << "[EYECHK] eyeW=(" << eyeW._41 << "," << eyeW._42 << "," << eyeW._43
@@ -5650,9 +5712,10 @@ void
// on a slope (body pitched ~8 deg to conform to the ground) aim
// its pick ray INTO the ground, short of a distant target
// (mechPicks=0). Level the boresight: drop the view direction's
// pitch and rebuild an upright basis (world +Y up). The reticle
// X still carries the torso twist (BTTwistToReticleX); the reticle
// Y carries any aim elevation. Falls back to the raw basis only
// pitch and rebuild an upright basis (world +Y up). The torso
// twist rides IN eyeFwdW now (task #58 eye compose above) --
// yaw survives the leveling, so the centered crosshair's pick
// ray follows the twist. Falls back to the raw basis only
// if the view is (degenerately) near-vertical.
// Derive the boresight from the AUTHORITATIVE view matrix
// (eyeFwdW = the world look direction from the inverse view),