gauges: register + wire the map (radar) -- the tactical display renders

MapDisplay (the radar/threat-map gauge, btl4rdr.cpp) was fully reconstructed
(ctor + Execute + DrawViewWedge/DrawStatic/DrawMoving/DrawNames) but never
registered and its data attributes never published.  Wire it up end-to-end:

DATA (the attributes the map binds):
* Mech table extended to the full 0x15..0x38 (was the 0x21 prefix): add
  RadarRange@0x2f -> radarRange, RadarLinearPosition@0x30 -> radarLinearPosition
  (Point3D* -> &localOrigin.linearPosition), RadarAngularPosition@0x31 ->
  radarAngularPosition (Quaternion* -> &localOrigin.angularPosition), DuckState
  @0x37 -> duckState; the rest bind the shared attrPad (kept dense so Find can't
  strcmp a gap).  New members set in the Mech ctor; radarRange defaults to 500
  (SetTargetRange is still stubbed) so nearby contacts are on-scale.  The map's
  position/angle attributes resolve to Point3D**/Quaternion** (AttributePointer
  is int Simulation::*, so the ATTRIBUTE_ENTRY reinterpret binds pointer members
  fine -- verified live: posPtr/anglePtr resolve).
* Sensor (named "Avionics") publishes RadarPercent/SelfTest/BadVoltage: define
  Sensor::AttributePointers[] + build the previously-empty Sensor::AttributeIndex
  chained to PoweredSubsystem::GetAttributeIndex() (= HeatSink's dense index).

WIDGET (the registration glue -- NOT in the assert-anchored decomp, so
reconstructed from the config + ctor):
* MapDisplay::methodDescription (config "map") + MapDisplay::Make.  The
  offset_position keyword "center"/"bottom" is typed as a STRING and converted
  in Make, avoiding a ModeManager named-constant registration.  Registered in
  BTL4MethodDescription[] (btl4grnd.cpp now includes btl4rdr.hpp).

Three crash fixes exposed once the map ran (each a pre-existing stub the map is
the first consumer of):
* Sensor radarPercent went hugely negative (RadarBaseline - HeatMasterEnergy(),
  where the inherited heatEnergy is un-normalized in the not-byte-exact heat-leaf
  branch) -> the radar reported non-operational.  Guard: treat an out-of-[0,1]
  heat term as no penalty (marked bring-up; real overheat penalty needs the heat
  normalization).
* ResolveOperatorEntity was a stub returning NULL -> EntityPosition(NULL) crashed
  CalculateBounds.  Fixed to the renderer linked-entity + application viewpoint
  fallback (same as HeadingPointer).
* MapName::ExtractFromEntity didn't guard a NULL entity (the binary's Verify(False)
  iterator-mismatch path compiles out at DEBUG_LEVEL 0) -> EntityPosition(NULL).
  Guarded.

Verified live (BT_DEV_GAUGES): the radar now draws the view wedge (the mech's
180-deg FOV cone) and completes the full wedge/static/moving/names cycle with
operating=1, cap=1, scale=500, position/angle resolved, 0 crashes.  Combat un-
regressed (TARGET DESTROYED after 8 hits), heap-clean under BT_HEAPCHECK.
Permanent gated diagnostic: BT_MAP_LOG traces the phase/draw pipeline.  Contacts
show 0 (the spatial-query entity classification is a follow-up); DuckState now
resolves for the duck button too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-06 21:16:29 -05:00
co-authored by Claude Opus 4.8
parent eae631ba04
commit 48ed3a8b73
5 changed files with 181 additions and 7 deletions
+32 -1
View File
@@ -103,8 +103,29 @@ Derivation
Receiver::MessageHandlerSet
Sensor::MessageHandlers;
//
// Attribute Support -- the gauge bindings for the Sensor (named "Avionics" in the
// cockpit config): Avionics/RadarPercent (the map's capabilities ratio), SelfTest,
// BadVoltage. SENSOR.HPP declares the enum + AttributePointers[]; the empty
// default-ctor AttributeIndex published NOTHING (not even SimulationState). Build
// it from the table chained to PoweredSubsystem::GetAttributeIndex() (which
// resolves to HeatSink's dense index) so the merged index stays dense (ids run
// contiguously from PoweredSubsystem::NextAttributeID).
//
const Sensor::IndexEntry
Sensor::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Sensor, RadarPercent, radarPercent), // @0x31C (map capabilities ratio)
ATTRIBUTE_ENTRY(Sensor, SelfTest, selfTest),
ATTRIBUTE_ENTRY(Sensor, BadVoltage, badVoltage)
};
Sensor::AttributeIndexSet
Sensor::AttributeIndex;
Sensor::AttributeIndex(
ELEMENTS(Sensor::AttributePointers),
Sensor::AttributePointers,
PoweredSubsystem::GetAttributeIndex()
);
Sensor::SharedData
Sensor::DefaultData(
@@ -256,6 +277,16 @@ void
// this[0x38] is the resolved thermal master sink; +0x158 == heatEnergy.
radarPercent = RadarBaseline - HeatMasterEnergy();
// ⚠ BRING-UP GUARD (heat-leaf branch not byte-exact): the inherited heatEnergy
// reads un-normalized/garbage here (~3850) so radarPercent goes hugely negative
// and the radar reports non-operational. radarPercent is authentically a [0,1]
// capability ratio, so when the heat term is out of range treat it as no penalty
// (full radar). A real overheat penalty needs the heat-energy normalization
// (deferred with the heat-leaf re-base). Exposed once the map gauge began
// reading Avionics/RadarPercent; inert before.
if (HeatMasterEnergy() < 0.0f || HeatMasterEnergy() > RadarBaseline)
radarPercent = RadarBaseline;
if (HeatModelOff()) // this[0x10] == 1
{
radarPercent = 0.0f;