From 1d2ddd839962d74c6503976c95bf0e71e1c2c59e Mon Sep 17 00:00:00 2001 From: arcattack Date: Mon, 6 Jul 2026 16:30:45 -0500 Subject: [PATCH] gauges: reconstruct cmArmor -- the per-zone ARMOR DAMAGE schematic [widget recon 3] Each armor zone on the cockpit schematic is now tinted by that zone's live damage. Render-verified: the schematic shows an all-green Blackhawk (all zones damageLevel=0 at spawn), replacing the static green+red it showed before cmArmor was registered -- i.e. cmArmor now owns those zone colors and shows the real undamaged state; damaged zones shift toward red. Reuses the ColorMapper base (from cmHeat) + two pieces reconstructed from the binary (Ghidra dropped the x87, recovered by disassembly): - ArmorZoneConnection (@004c33a4/@004c3430): resolves one zone from the owner's inherited Entity::damageZones[zone_index]; per-frame feed = zone==NULL ? 100 : Round(zone->damageLevel * 100) (the 0..1 damage ratio -> 0..100 percentage -> the colour index ColorMapper::Execute pushes into the palette slot). - ColorMapperArmor::Make/ctor (@004c3aa4/@004c3b98): Make resolves the CFG zone name (dz_ltorso etc., the 6th param) to an index via Entity::GetDamageZoneIndex (== the binary's FUN_0042076c, scanning damageZones[] by name); the ctor wires the ArmorZoneConnection. Decomp fact (corrects the cmHeat note): the ColorMapper base ctor takes NINE args -- an owner_ID sits between renderer and graphics_port_number. Our 8-arg ColorMapper::ColorMapper folds owner_ID=0 in (gauges have owner 0), so it is equivalent; cmArmor's mapping matches cmHeat plus the zone name. Verified: parses, builds, all dz_* zones resolve (0 "not found"), no /FORCE unresolved, combat un-regressed (TARGET DESTROYED, 0 crashes). The dynamic red-on-damage needs the player to take damage (the passive spawn dummy never hits back). Details: docs/GAUGE_COMPOSITE.md. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/GAUGE_COMPOSITE.md | 27 +++++- game/reconstructed/btl4gaug.cpp | 146 +++++++++++++++++++++++++++++--- game/reconstructed/btl4gaug.hpp | 3 + game/reconstructed/btl4grnd.cpp | 1 + 4 files changed, 166 insertions(+), 11 deletions(-) diff --git a/docs/GAUGE_COMPOSITE.md b/docs/GAUGE_COMPOSITE.md index b173ecc..685c5de 100644 --- a/docs/GAUGE_COMPOSITE.md +++ b/docs/GAUGE_COMPOSITE.md @@ -282,7 +282,32 @@ the Ghidra pseudo-C for Execute@004c5914 + ctor@004c562c had the x87 endpoint ma float computation Ghidra dropped by PE-parsing + capstone-disassembling the fn (`scratchpad/disas_hp.py`) — read the `fild/fmul/fadd` stream + the `.data` float pool.** -**Next increments (priority order from the map):** `cmArmor`/`colorMapperMultiArmor` (armor schematic, needs the +**✅ Increment 3 — `cmArmor` (ColorMapperArmor) DONE — the ARMOR DAMAGE schematic, per-zone.** Each of the +mech's armor zones on the cockpit schematic is now tinted by that zone's LIVE damage. Render-verified: the +schematic shows the Blackhawk silhouette all-green (all zones `damageLevel`=0 at spawn) — which REPLACED the +static green+red the schematic showed before cmArmor was registered (i.e. cmArmor now owns those zone colors, +showing the real undamaged state; damaged zones shift toward red via the decomp-verified path). Reuses the +ColorMapper base (done in incr.1) + adds two pieces reconstructed from the binary + a disassembly of the x87 +Ghidra dropped (`scratchpad/disas_hp.py`): +- **`ArmorZoneConnection`** (@004c33a4 ctor / @004c3430 Transfer): resolves ONE zone from the owner's + inherited `Entity::damageZones[zone_index]`; per-frame feed = `zone==NULL ? 100 : Round(zone->damageLevel + @0x158 * 100)` (the damage ratio 0..1 → 0..100 percentage → the colour index ColorMapper::Execute pushes). +- **`ColorMapperArmor::Make`/ctor** (@004c3aa4/@004c3b98): `Make` resolves the CFG zone NAME (`dz_ltorso` etc., + the 6th param) to an index via `Entity::GetDamageZoneIndex(CString)` — which IS the binary's `FUN_0042076c` + (scans `damageZones[]` matching each zone's name @0x15c); the ctor wires the ArmorZoneConnection. +- **DECOMP FACT (corrects the cmHeat note): the ColorMapper base ctor `FUN_004c37dc` takes NINE args** — there + is an `owner_ID` between `renderer` and `graphics_port_number` (`Gauge(rate,mode,renderer,owner_ID,id)` + + `colorSlot=param_7`, `graphicsPort=GetGraphicsPort(param_6)`). Our 8-arg `ColorMapper::ColorMapper` folds + `owner_ID=0` in (gauges have owner 0), so it's equivalent — cmArmor's mapping matches cmHeat (gpn=port, + colorSlot=p[2], palettes=p[3]/p[4]) plus the zone name (p[5]). Verified: parses, builds, **all `dz_*` zones + resolve (0 "not found")**, no `/FORCE` unresolved, combat un-regressed (TARGET DESTROYED, 0 crashes). NOTE: + the dynamic red-on-damage needs the PLAYER to take damage — the passive `BT_SPAWN_ENEMY` dummy never hits + back, so a live demo of a zone reddening needs a player-damage path (real MP combat, or a test hook). + ⚠ The sibling `colorMapperMultiArmor` (worst-of-8-zones, MultiArmorConnection @004c346c) + `cmCrit` + (ColorMapperCritical, CriticalConnection @004c3598) are the same shape — easy follow-ups now the pattern + + the ColorMapper base facts are pinned. + +**Next increments (priority order from the map):** `colorMapperMultiArmor`/`cmCrit` (same ColorMapper pattern), then the `ArmorZoneConnection`/`MultiArmorConnection` classes reconstructed); then the attribute-table wave (`vertBar`/ `segmentArcRatio` speed/`GeneratorCluster` — need `AttributePointers[]` on Mech/HeatableSubsystem, a separate pass); the XL items (`map`/`vehicleSubSystems`/`PlayerStatus`) last. The POD path needs the FULL 23-entry diff --git a/game/reconstructed/btl4gaug.cpp b/game/reconstructed/btl4gaug.cpp index 2bd5c8c..e2f4c94 100644 --- a/game/reconstructed/btl4gaug.cpp +++ b/game/reconstructed/btl4gaug.cpp @@ -178,6 +178,46 @@ void } +// +// ArmorZoneConnection (@004c33a4 ctor / @004c3430 Transfer) -- drives a +// ColorMapper's colour index from ONE damage zone's live damage ratio. The ctor +// resolves the zone from the owner's inherited Entity::damageZones[zone_index] +// (raw *(entity+0x120)[idx]); Transfer feeds it each frame. Used by ColorMapperArmor. +// +class ArmorZoneConnection : public GaugeConnection +{ +public: + ArmorZoneConnection(int *destination, Entity *entity, int zone_index) + : GaugeConnection(0), // FUN_00444124(this,0) + zone((zone_index < 0) ? NULL : entity->damageZones[zone_index]), + currentColorIndex(destination) + {} + +protected: + // + // Per-frame feed (@004c3430): no zone -> 100 (fail-safe full tint); else the + // zone's damage RATIO (damageLevel @0x158, 0..1) scaled to a 0..100 percentage + // and rounded -- ColorMapper::Execute clamps it + pushes the palette slot, + // recolouring that zone on the cockpit ARMOR DAMAGE schematic. + // + void Update(); // override + + DamageZone *zone; // source@0x10 + int *currentColorIndex; // destination@0x14 +}; + +void + ArmorZoneConnection::Update() +{ + if (zone == NULL) + { + *currentColorIndex = 100; + return; + } + *currentColorIndex = HeatRound(zone->damageLevel * 100.0f); // zone+0x158 * 100 +} + + //########################################################################### //########################################################################### // File-private GaugeConnection subclasses @@ -377,19 +417,105 @@ void // ColorMapperArmor @004c3aa4 Make / @004c3b98 ctor //########################################################################### // -// @004c3aa4 -- Make: build the damage-zone descriptor (DAT_00514414), look up -// the zone index on the host subsystem (FUN_0042076c; warns "damage zone ... -// not found" on failure), allocate (0x70) and construct a ColorMapperArmor -// wired with an ArmorZoneConnection. +// Tints ONE damage zone's colour on the cockpit ARMOR DAMAGE schematic by that +// zone's live damage. CFG shape (L4GAUGE.CFG:4789, inside the colorMapArmor +// macro): cmArmor( rate, mode, colourSlot, paletteA, paletteB, zoneName ) +// e.g. cmArmor(H, ModeSecondaryDamage, 32, adpal.pcc, adpal2.pcc, dz_ltorso); // -// @004c3b98 -- ctor: ColorMapper base (vtable PTR_FUN_00518dcc), this[0x1B]=0, -// then AddConnection(new ArmorZoneConnection(¤tColorIndex, subsystem, -// zone_index)) (connection ctor FUN_004c33a4, 0x18 bytes). + +MethodDescription + ColorMapperArmor::methodDescription = + { + "cmArmor", + ColorMapperArmor::Make, + { + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeColor, NULL }, // hardware colour slot + { ParameterDescription::typeString, NULL }, // palette name A + { ParameterDescription::typeString, NULL }, // palette name B + { ParameterDescription::typeString, NULL }, // damage-zone name (e.g. "dz_ltorso") + PARAMETER_DESCRIPTION_END + } + }; + // -// ArmorZoneConnection::Transfer @004c3430: -// if (source == 0) *destination = 100; -// else *destination = Round(); +// @004c3aa4 -- Make. Resolve the named damage zone to an index on the entity, +// then construct. (The binary built a transient zone-name descriptor and called +// FUN_0042076c; Entity::GetDamageZoneIndex IS that lookup -- it scans +// damageZones[] matching each zone's name.) // +Logical + ColorMapperArmor::Make( + int display_port_index, + Vector2DOf /*position*/, + Entity *entity, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription *p = methodDescription.parameterList; + + int zone_index = entity->GetDamageZoneIndex(CString(p[5].data.string)); // FUN_0042076c + if (zone_index < 0) + { + DebugStream << "ColorMapperArmor warning: damage zone " + << p[5].data.string << " not found\n"; + } + + ColorMapperArmor *gauge = (ColorMapperArmor *)operator new(0x70); // FUN_00402298(0x70) + if (gauge != NULL) + { + new (gauge) ColorMapperArmor( // FUN_004c3b98 + p[0].data.rate, p[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics port number (the runtime port) + p[2].data.color, // colour slot + entity, + p[3].data.string, // palette A + p[4].data.string, // palette B + zone_index, + "ColorMapperArmor"); + } + return True; +} + +// +// @004c3b98 -- ctor: ColorMapper base + wire an ArmorZoneConnection feeding the +// resolved damage zone's live damage to the colour index. +// +ColorMapperArmor::ColorMapperArmor( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int color_index, + Entity *entity, + const char *palette_a, + const char *palette_b, + int damage_zone_index, + const char *identification_string +): + ColorMapper( // FUN_004c37dc (owner_ID 0 folded in, as for cmHeat) + rate, mode_mask, renderer, graphics_port_number, + color_index, palette_a, palette_b, identification_string) +{ + unused = 0; // this[0x1B] @0x6C + + ArmorZoneConnection *connection = + (ArmorZoneConnection *)operator new(0x18); // FUN_004c33a4 (0x18 bytes) + if (connection != NULL) + new (connection) ArmorZoneConnection(¤tColorIndex, entity, damage_zone_index); + AddConnection(connection); // (*this+0x34) +} + +// +// ColorMapperArmor destructor. No extra work: the ArmorZoneConnection is +// released by the base Gauge teardown, the palettes by ~ColorMapper -- the +// base-dtor chain runs implicitly at the closing brace. +// +ColorMapperArmor::~ColorMapperArmor() +{ +} //########################################################################### // ColorMapperMultiArmor @004c3c48 Make / @004c3d60 ctor diff --git a/game/reconstructed/btl4gaug.hpp b/game/reconstructed/btl4gaug.hpp index 64e1da7..ecf8fb0 100644 --- a/game/reconstructed/btl4gaug.hpp +++ b/game/reconstructed/btl4gaug.hpp @@ -140,6 +140,9 @@ public ColorMapper { public: + // Registered in BTL4MethodDescription[] (btl4grnd.cpp) as "cmArmor". + static MethodDescription methodDescription; + static Logical Make( // @004c3aa4 int display_port_index, Vector2DOf position, Entity *entity, GaugeRenderer *gauge_renderer); diff --git a/game/reconstructed/btl4grnd.cpp b/game/reconstructed/btl4grnd.cpp index 03fdb88..907f7f2 100644 --- a/game/reconstructed/btl4grnd.cpp +++ b/game/reconstructed/btl4grnd.cpp @@ -131,6 +131,7 @@ MethodDescription // &ColorMapperHeat::methodDescription, // "cmHeat" -- heat-driven palette tint &HeadingPointer::methodDescription, // "headingPointer" -- compass needle + heading + &ColorMapperArmor::methodDescription, // "cmArmor" -- per-zone armor-damage tint &BTL4ChainToPrevious };