Radar view-wedge tracks the torso twist (Gitea issue #1)

The SECTOR radar's view cone read viewHorizontalRotation, wired by the
MapDisplay ctor from the mech's Torso -- but FindSubObject and
GetHorizontalRotation were NULL stubs, so the connection was never created
and the wedge sat at heading 0 regardless of twist.

Reconstructed from the binary:
- FindSubObject (FUN_0041f98c): a subsystem-ROSTER walk (count @+0x124,
  array @+0x128) matching the streamed subsystem name (sub+0xd4) with a
  tolower-strcmp (FUN_004d4b58) -- the 'Torso' sub-object IS the roster
  Torso subsystem.
- GetHorizontalRotation: torso+0x1D8 == Torso::currentTwist (layout-locked),
  via the existing task-#56 bridge BTGetTorsoTwistAddr (Radian is layout-
  identical to Scalar).

Verified live (MadCat, Standard mode Q/E): the wedge tracks the twist in
lockstep (rot 0 -> -2.21 rad), and the semantic test passes -- body turned
away, torso twisted back onto the enemy: reticle green + wedge pointing at
the enemy's blip on the body-fixed scope.  NB the Blackhawk's torso is FIXED
(+/-0.01 deg limits) -- its wedge authentically never moves; test with a
twisting mech.  Diag env: BT_RADAR_LOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-17 15:24:52 -05:00
co-authored by Claude Opus 4.8
parent 8616b62405
commit 1ed8b05160
2 changed files with 63 additions and 8 deletions
+16
View File
@@ -131,6 +131,22 @@ At the authentic 1e7-unit economy (tasks #9-#10) the Heat MFD is fully dynamic:
77→2000 (the authored failure threshold), condensers/generators run 100-1400 under sustained
fire, and the bank plateaus ~600 — temperatures, heatLoad and the heat alarms all animate. [T2]
## Radar view-wedge tracks the torso twist (Gitea issue #1 — FIXED 2026-07-17) [T2]
The SECTOR radar's view cone (`MapDisplay::DrawViewWedge` @004c2484, btl4rdr.cpp) reads
`viewHorizontalRotation@0x31C`, fed by a per-frame `GaugeConnectionDirectOf<Radian>` the ctor
wires from the mech's Torso. The two helpers were NULL stubs, so the connection was never
created and the wedge sat at heading 0 (the reported "cone does not track the twist").
Reconstructed from the binary: `FindSubObject` (FUN_0041f98c) = a SUBSYSTEM-ROSTER walk
(count @+0x124 / array @+0x128) matching the streamed name (sub+0xd4) case-insensitively
(FUN_004d4b58 = tolower-strcmp) — the "Torso" sub-object IS the roster Torso subsystem;
`GetHorizontalRotation` = torso+0x1D8 = `Torso::currentTwist` (layout-locked), reached via the
existing task-#56 bridge `BTGetTorsoTwistAddr` (Radian ≡ {Scalar angle}, exact reinterpret).
VERIFIED live (MadCat, Standard mode Q/E): wedge rot tracks twist 0→−2.21 rad in lockstep, and
the SEMANTIC test passes — body turned away, torso twisted back onto the enemy → reticle goes
green AND the wedge points at the enemy's blip while the scope stays body-fixed. ⚠ Test with a
TWISTING mech: the Blackhawk's torso is FIXED (limits ±0.01°) — its wedge authentically never
moves. Diag: `BT_RADAR_LOG` ([radar] ctor probe + 1 Hz [radar-wedge] rot trace). [T2]
## Remaining = DATA FEEDS, not widgets (deferred systems)
**✅ The condenser valve CONTROL is LIVE (task #13, 2026-07-11) [T2]:** `MoveValve` (id 4, the
Condenser handler table @0x50E52C — exactly one entry) registered + guarded by the REAL
+47 -8
View File
@@ -201,11 +201,13 @@ static Entity *
//
// Mech sub-object lookup ("Torso") + its horizontal rotation feed.
// (The "sub-object" IS a roster SUBSYSTEM -- FUN_0041f98c walks the entity's
// subsystem roster by NAME; see the bodies at the end of this file.)
//
static Entity *
static Subsystem *
FindSubObject(Entity *entity, const char *name);
static Radian *
GetHorizontalRotation(Entity *entity);
GetHorizontalRotation(Subsystem *torso);
//###########################################################################
@@ -471,7 +473,11 @@ MapDisplay::MapDisplay(
//
if (entity->GetClassID() == 0xBB9)
{
Entity *torso = FindSubObject(entity, "Torso"); // FUN_0041f98c
Subsystem *torso = FindSubObject(entity, "Torso"); // FUN_0041f98c (roster walk)
if (getenv("BT_RADAR_LOG"))
DEBUG_STREAM << "[radar] ctor: torso sub "
<< (torso ? "FOUND" : "NOT FOUND (roster names?)")
<< " on mech " << (void *)entity << "\n" << std::flush;
if (torso != NULL)
{
AddConnection(
@@ -791,6 +797,19 @@ void
Radian leftEdge = viewHorizontalRotation - viewHalfHorizontalWidth;
Radian rightEdge = viewHorizontalRotation + viewHalfHorizontalWidth;
// DIAG (issue #1): 1 Hz wedge-heading trace.
if (getenv("BT_RADAR_LOG"))
{
static unsigned long sLast = 0;
unsigned long now = GetTickCount();
if (now - sLast >= 1000)
{
sLast = now;
DEBUG_STREAM << "[radar-wedge] rot=" << (Scalar)viewHorizontalRotation
<< "\n" << std::flush;
}
}
localView.SetColor(staticColor); // (this+0x48 vtbl+0x18)
Radian edges[2];
@@ -1176,20 +1195,40 @@ static Entity *
//
// FUN_0041f98c -- Entity::FindSubObject(name) (locates "Torso").
// The binary walks the entity's SUBSYSTEM ROSTER (count @+0x124, array
// @+0x128) and returns the first subsystem whose streamed NAME (sub+0xd4 ==
// Subsystem::subsystemName) matches case-insensitively (FUN_004d4b58 is a
// tolower-strcmp). Gitea issue #1 fix (2026-07-17): this and the feed below
// were NULL stubs, so the torso-twist -> radar view-wedge connection was
// never created and the wedge sat at heading 0 forever.
//
static Entity *
FindSubObject(Entity * /*entity*/, const char * /*name*/)
static Subsystem *
FindSubObject(Entity *entity, const char *name)
{
if (entity == NULL || name == NULL)
return NULL;
for (int i = 0; i < entity->GetSubsystemCount(); ++i)
{
Subsystem *sub = entity->GetSubsystem(i);
if (sub != NULL && sub->GetName() != NULL
&& _stricmp(sub->GetName(), name) == 0)
return sub;
}
return NULL;
}
//
// torso+0x1D8 -- the sub-object's horizontal (yaw) rotation feed.
// torso+0x1D8 -- the sub-object's horizontal (yaw) rotation feed:
// Torso::currentTwist (layout-locked @0x1D8, torso.hpp). Reached via the
// torso.cpp complete-type bridge (task #56's BTGetTorsoTwistAddr -- the same
// pointer the gyro's external-pitch link uses). Radian is layout-identical
// to Scalar ({ Scalar angle }, ANGLE.h), so the reinterpret is exact.
//
static Radian *
GetHorizontalRotation(Entity * /*entity*/)
GetHorizontalRotation(Subsystem *torso)
{
return NULL;
extern Scalar *BTGetTorsoTwistAddr(Subsystem *torso);
return (Radian *)BTGetTorsoTwistAddr(torso);
}