From 751b1159b551a430bfaaabfb6f92dcca77c868cb Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Mon, 3 Aug 2026 17:53:33 -0500 Subject: [PATCH] #118: the eject slot graphic lands -- BitMapInverseWipeScalar reconstructed The operator's field report ("eject slot flashing but no graphic in it") was the tracked-NULL bring-up stub in BallisticWeaponCluster. Reconstructed the missing gauge class from the binary: - BitMapInverseWipeScalar @004c61c8 (vtable 0x518a14; base @004c5e84, Execute @004c5fb8, BecameActive @004c5fa4): a bitmap COLUMN SWEEP whose level tracks a live Scalar -- two spans with INVERTED colour pairs, [0..level] fg colorA / bg colorB, remainder the inverse. - Wired per the binary call site (part_014.c:2312): bteejtm.pcc at (0xF,0) on the weapon's eng port, colours 0/0xFF, watching the weapon's PercentOfEject (@0x3F8, the round-eject 0..1 progress) via a new complete-type bridge BTWeaponPercentOfEjectPtr (databinding rule). At idle (level 0) the EJECT graphic now renders in the slot; during a round-eject cycle it sweeps with the countdown. BTEEJTM.PCC ships in content/GAUGE (the loose gauge bitmap set). Smoke-tested: constructs clean. Co-Authored-By: Claude Fable 5 --- game/reconstructed/btl4gau2.cpp | 21 ++++++-- game/reconstructed/btl4gaug.cpp | 95 +++++++++++++++++++++++++++++++++ game/reconstructed/btl4gaug.hpp | 33 ++++++++++++ game/reconstructed/projweap.cpp | 11 ++++ game/reconstructed/projweap.hpp | 1 + 5 files changed, 156 insertions(+), 5 deletions(-) diff --git a/game/reconstructed/btl4gau2.cpp b/game/reconstructed/btl4gau2.cpp index d06d1ab..6b12551 100644 --- a/game/reconstructed/btl4gau2.cpp +++ b/game/reconstructed/btl4gau2.cpp @@ -2020,11 +2020,22 @@ BallisticWeaponCluster::BallisticWeaponCluster( destroyedLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, engPort, 0xc2, 0x85, "edestryd.pcc", 0, 0xff, (int *)&failedState, "TwoState"); // the destroyed X: bright when FAILED - // ejectWipe (BitMapInverseWipeScalar @004c61c8, eject-timer wipe reading - // subsys+0x3f8). BRING-UP: the BitMapInverseWipeScalar class is not yet - // declared/reconstructed in btl4gaug (only the non-Scalar BitMapInverseWipe); - // tracked NULL until it lands. - ejectWipe = NULL; // @0x44 + // ejectWipe (BitMapInverseWipeScalar @004c61c8): the round-EJECT progress + // sweep -- bteejtm.pcc revealed column-by-column as the weapon's + // PercentOfEject (@0x3F8) runs 0..1. Binary call (part_014.c:2312): + // FUN_004c61c8(., rate, eng_mode, renderer, ownerID, engPort, 0xF, 0, + // "bteejtm.pcc", 0, 0xFF, weapon+0x3f8, name). Reconstructed 2026-08-03 + // (#118 field report: the eject slot flashed EMPTY -- this was the + // tracked-NULL stub). + { + extern Scalar *BTWeaponPercentOfEjectPtr(void *weapon); + Scalar *ejectPct = BTWeaponPercentOfEjectPtr(subsystem_in); + ejectWipe = (ejectPct != NULL) + ? new BitMapInverseWipeScalar(ChildRate(), eng_mode, renderer_in, + owner_ID, engPort, 0xF, 0, "bteejtm.pcc", 0, 0xFF, + ejectPct, "BitMapInverseWipeScalar") + : NULL; // @0x44 + } } BallisticWeaponCluster::~BallisticWeaponCluster() diff --git a/game/reconstructed/btl4gaug.cpp b/game/reconstructed/btl4gaug.cpp index fb56b07..1f9fd72 100644 --- a/game/reconstructed/btl4gaug.cpp +++ b/game/reconstructed/btl4gaug.cpp @@ -2152,6 +2152,101 @@ void BitMapInverseWipe::Execute() } +//########################################################################### +// BitMapInverseWipeScalar @004c61c8 ctor / @004c5fb8 Execute +//########################################################################### +// +// The weapon-eng round-EJECT progress sweep (bteejtm.pcc <- PercentOfEject). +// Base ctor @004c5e84 interns the image and captures colours + the bitmap +// geometry; the @004c61c8 tail adds the Scalar value-watcher (FUN_00474855 +// into this+0xA8) -- our GaugeConnectionDirectOf is that watcher. +// +BitMapInverseWipeScalar::BitMapInverseWipeScalar( + GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in, + int owner_ID, int graphics_port_number, int x, int y, const char *image, + int color_a, int color_b, Scalar *value_pointer, + const char *identification_string +): + GraphicGauge(rate, mode_mask, renderer_in, owner_ID, graphics_port_number, + identification_string) +{ + localView.SetOrigin(x, y); + imageName = new char[strlen(image) + 1]; + strcpy(imageName, image); + colorA = color_a; + colorB = color_b; + value = 0.0f; + previousLevel = -1; + L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer; + BitMap *bmp = warehouse->bitMapBin.Get(imageName); + if (bmp == NULL) + { + width = 0; + height = 0; + } + else + { + width = bmp->Data.Size.x; + height = bmp->Data.Size.y; + } + AddConnection(new GaugeConnectionDirectOf(0, &value, value_pointer)); +} + +BitMapInverseWipeScalar::~BitMapInverseWipeScalar() +{ + L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer; + warehouse->bitMapBin.Release(imageName); + delete[] imageName; + imageName = NULL; +} + +Logical BitMapInverseWipeScalar::TestInstance() const { return GraphicGauge::TestInstance(); } + +void BitMapInverseWipeScalar::BecameActive() // @004c5fa4 +{ + previousLevel = -1; +} + +// +// @004c5fb8 -- Execute. level = round(value x width) clamped to [0, width] +// (the x87 form Ghidra drops -- fild width / fmul value, gotcha #19), and on +// a change draw the two column spans with INVERTED colour pairs: [0..level] +// fg colorA over bg colorB, [level+1..width] fg colorB over bg colorA. +// +void BitMapInverseWipeScalar::Execute() +{ + Scalar scaled = value * (Scalar)width; + int level = (int)(scaled + (scaled < 0.0f ? -0.5f : 0.5f)); // FUN_004dcd94 round + if (level < 0) level = 0; + if (level > width) level = width; + + if (level != previousLevel) + { + previousLevel = level; + L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer; + BitMap *bmp = warehouse->bitMapBin.Get(imageName); + if (bmp != NULL) + { + int split = level; + if (split != 0) + { + localView.MoveToAbsolute(0, 0); + localView.SetColor(colorA); + localView.DrawBitMap(colorB, bmp, 0, 0, split, height); + ++split; // the binary's iVar2++ + } + if (split < width) + { + localView.MoveToAbsolute(split, 0); + localView.SetColor(colorB); + localView.DrawBitMap(colorA, bmp, split, 0, width, height); + } + warehouse->bitMapBin.Release(imageName); + } + } +} + + //########################################################################### //########################################################################### // HeadingPointer @004c554c Make / @004c562c ctor diff --git a/game/reconstructed/btl4gaug.hpp b/game/reconstructed/btl4gaug.hpp index 46fc213..d958fc1 100644 --- a/game/reconstructed/btl4gaug.hpp +++ b/game/reconstructed/btl4gaug.hpp @@ -541,6 +541,39 @@ Scalar value; // @0xB4 this[0x2D] (connection) }; + //####################################################################### + // BitMapInverseWipeScalar @004c61c8 (vtable 0x518a14; base ctor @004c5e84, + // Execute @004c5fb8, BecameActive @004c5fa4). A bitmap COLUMN SWEEP whose + // level tracks a live Scalar 0..1 (the ctor's trailing Scalar* rides a + // value-watcher into this+0xA8): columns [0..level] draw fg colorA over + // bg colorB, the remainder draws the INVERSE -- the weapon-engineering + // panel's round-EJECT progress graphic (bteejtm.pcc, watching the + // weapon's PercentOfEject @0x3F8). Reconstructed 2026-08-03 (#118: the + // operator saw the eject slot flash with NO graphic -- this was the + // tracked-NULL bring-up stub in BallisticWeaponCluster). + //####################################################################### + class BitMapInverseWipeScalar : + public GraphicGauge + { + public: + BitMapInverseWipeScalar( // @004c61c8 + GaugeRate, ModeMask, L4GaugeRenderer *, int owner_ID, + int graphics_port_number, int x, int y, const char *image, + int color_a, int color_b, Scalar *value_pointer, const char *); + ~BitMapInverseWipeScalar(); + Logical TestInstance() const; + void BecameActive(); // @004c5fa4 (previousLevel = -1) + void Execute(); // @004c5fb8 (two-span inverse sweep) + protected: + char *imageName; // @0x90 + int colorA; // @0x94 + int colorB; // @0x98 + int width; // @0x9C bitmap width (columns) + int height; // @0xA0 bitmap height + int previousLevel; // @0xA4 + Scalar value; // @0xA8 (connection: the 0..1 progress) + }; + //####################################################################### // SegmentArc gauges -- needle / dial over a 270-degree arc. Derived from // the MUNGA L4 arc primitives (FUN_004745e0 / FUN_00473f44). diff --git a/game/reconstructed/projweap.cpp b/game/reconstructed/projweap.cpp index 81ff587..d3a98c7 100644 --- a/game/reconstructed/projweap.cpp +++ b/game/reconstructed/projweap.cpp @@ -268,6 +268,17 @@ void * ? (void *)((ProjectileWeapon *)weapon)->ammoBinLink.Resolve() : 0; } +// Eject-wipe gauge bridge (#118): the BallisticWeaponCluster's +// BitMapInverseWipeScalar watches the weapon's PercentOfEject (@0x3F8, the +// binary passes param_8+0x3f8 straight into the @004c61c8 ctor). Complete- +// type TU per the databinding rule -- never raw-offset a compiled object. +Scalar * + BTWeaponPercentOfEjectPtr(void *weapon) +{ + return (weapon != 0) + ? &((ProjectileWeapon *)weapon)->percentOfEject : 0; +} + //############################################################################# // Construction / Destruction diff --git a/game/reconstructed/projweap.hpp b/game/reconstructed/projweap.hpp index 83086f7..e303ff5 100644 --- a/game/reconstructed/projweap.hpp +++ b/game/reconstructed/projweap.hpp @@ -360,6 +360,7 @@ class NotationFile; AmmoBinConnection ammoBinLink; // @0x43C embedded 0xC connection to the AmmoBin subsystem friend void *BTWeaponAmmoBin(void *weapon); // panel ammo-bin bridge (AFC100-counter fix) + friend Scalar *BTWeaponPercentOfEjectPtr(void *weapon); // eject-wipe gauge bridge (#118) // (ctor @004bcbb0, dtor @004bcbcf, link vtable // @00512424; resolved via the resource AmmoBin // index at +0x1C0) -- LAST field, object ends 0x448