gauge-complete P4f: PrepEngrScreen reconstructed + registered -> 12 engineering-screen label overlays build

The "prepEngr" cockpit primitive (the per-engineering-screen static label overlay,
12 CFG calls at L4GAUGE.CFG:4595-4759) was a MISLABELED stub (base Gauge, an Execute
override, mech/screenNumber SWAPPED) with no methodDescription -> parse-SKIPPED, none built.

Reconstructed byte-verified (ctor @4c7bf0, BecameActive @4c7e48, Make @4c7b30; vtable
0051a06c). THREE corrections vs the stub (decode workflow + vtables.tsv proof):
(1) base = GraphicGaugeBackground, not Gauge -- vtable-identical shape to the engine's
BackgroundBitmap (only dtor + BecameActive overridden); (2) the overridden slot is
BecameActive (a paint-on-activation), NOT Execute -- GraphicGaugeBackground has no Execute
virtual, so the Gauge::Execute->Fail->abort hazard doesn't apply; (3) screenNumber@0x6C /
mech@0x70 were swapped. sizeof 0x90.

BecameActive walks the mech roster (databinding-safe GetSubsystemCount/GetSubsystem +
BTGetSubsystemAuxScreen bridge, like VehicleSubSystems::Make), finds the subsystem whose
auxScreenNumber == this screen (1..12), and paints the screen-number numeric + subsystem
label + type-specific label cells dispatched on GetClassID (Sensor/Myomers/Emitter/PPC/
Projectile/Missile/GaussRifle). methodDescription = 9 params (mode, screen, 7 .pcc names).
Registered in BTL4MethodDescription[]. Two raw subsystem reads (heat-sink #, +0x224 label)
are guarded fail-soft best-efforts (marked; the aux-screen bridge label64 substitutes).

VERIFIED: the skip list drops from {prepEngr x12, messageBoard} to just {messageBoard} --
all 12 prepEngr gauges build; no screen-range/crash; boot+sim+combat run; gauge composite
renders identically (COOLANT 300/77, clusters, radar all un-regressed). The prepEngr labels
paint on the Eng (ModeMFD*Eng*) screens, not the default composite view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-07 21:32:12 -05:00
co-authored by Claude Opus 4.8
parent 4a4ec6855c
commit c0d2eb0aae
4 changed files with 241 additions and 41 deletions
+216 -21
View File
@@ -822,32 +822,227 @@ void
//###########################################################################
//###########################################################################
// forward: the tiled-blit helper is defined lower in this TU (FUN_004c2ec4).
static void DrawPortBackground(GraphicsView *view, L4Warehouse *warehouse,
int x, int y, const char *tile);
// ---- FUN_004c79c8 : draw the three status-label cells (+ optional top erase box).
// Each cell blits its label .pcc, or blanks the cell. Absolute cell rectangles are
// the binary's exact coords (part_014.c:1127-1166).
static void
DrawStatusCells(GraphicsView *view, L4Warehouse *warehouse,
int topBox, const char *A, const char *B, const char *C)
{
if (topBox) // :1131-1135
{
view->SetColor(0);
view->MoveToAbsolute(0x97, 0x80);
view->DrawFilledRectangleToAbsolute(0x17d, 0x13b);
}
if (A == NULL) { view->SetColor(0); view->MoveToAbsolute(0x15a, 0x145); // :1136-1140
view->DrawFilledRectangleToAbsolute(0x17f, 0x159); }
else { view->SetColor(0xff); DrawPortBackground(view, warehouse, 0x15a, 0x145, A); }
if (B == NULL) { view->SetColor(0); view->MoveToAbsolute(0x85, 0x58); // :1145-1149
view->DrawFilledRectangleToAbsolute(0x17f, 0x7c); }
else { view->SetColor(0xff); DrawPortBackground(view, warehouse, 0x85, 0x58, B); }
if (C == NULL) { view->SetColor(0); view->MoveToAbsolute(0x2a, 0x34); // :1154-1160
// raw uses an extra GraphicsView vtbl+0x4c(99,10) compound-blank; a plain
// fill at the same cell is the faithful-enough substitute (marked).
view->DrawFilledRectangleToAbsolute(0x9f, 0x2d); }
else { view->SetColor(0xff); DrawPortBackground(view, warehouse, 0x2a, 0x34, C); }
}
// CFG shape (L4GAUGE.CFG:4595): mode, int screen(1..12), then 7 .pcc names
// (font + 6 labels). 9 params.
MethodDescription
PrepEngrScreen::methodDescription =
{
"prepEngr",
PrepEngrScreen::Make,
{
{ ParameterDescription::typeModeMask, NULL }, // p[0] mode (ModeMFD*Eng*)
{ ParameterDescription::typeInteger, NULL }, // p[1] screen number 1..12
{ ParameterDescription::typeString, NULL }, // p[2] font helv42.pcc -> statusImage[0]
{ ParameterDescription::typeString, NULL }, // p[3] escnd.pcc -> statusImage[1]
{ ParameterDescription::typeString, NULL }, // p[4] edmg.pcc -> statusImage[2]
{ ParameterDescription::typeString, NULL }, // p[5] bteslev.pcc -> statusImage[3]
{ ParameterDescription::typeString, NULL }, // p[6] bteunjm.pcc -> statusImage[4]
{ ParameterDescription::typeString, NULL }, // p[7] espeed.pcc -> statusImage[5]
{ ParameterDescription::typeString, NULL }, // p[8] btesrnge.pcc -> statusImage[6]
PARAMETER_DESCRIPTION_END
}
};
//
// @004c7b30 -- Make. DAT_0051936c is the engineer-screen number; must be 1..12
// or "PrepEngr: screen number out of range <n>" is warned. Allocates (0x90)
// and constructs with seven status-image names (DAT_005193b0..00519548).
// @004c7b30 -- Make. Validate the screen number (p[1], 1..12) then construct.
// The GraphicGaugeBackground base ctor self-registers with the renderer, so the
// gauge's BecameActive fires whenever its Eng screen is selected. We `new` the
// object (real compiled size) rather than operator new(0x90) -- the 2007 base
// isn't byte-exact, and nothing reads this object at raw offsets.
//
// @004c7bf0 -- ctor (vtable PTR_FUN_0051a06c). Derives from the non-graphic
// Gauge group (FUN_004440d4) -- it owns no GraphicsView, only a child-gauge
// chain at +0x24. Interns and BitMapCache-AddRefs the seven status pixmaps
// (this[0x1D..0x23]); stores mech (this[0x1B]) and screen number (this[0x1C]).
Logical
PrepEngrScreen::Make(
int display_port_index,
Vector2DOf<int> /*position -- PrepEngr draws at absolute coords*/,
Entity *entity,
GaugeRenderer *gauge_renderer
)
{
ParameterDescription *p = methodDescription.parameterList;
int screen = p[1].data.integer;
if (screen < 1 || screen > 12) // :1182
{
DEBUG_STREAM << "PrepEngr: screen number out of range " << screen << "\n"; // :1183
return False;
}
new PrepEngrScreen( // FUN_00402298(0x90) in the binary
p[0].data.modeMask, // mode (ModeMFD*Eng*)
(L4GaugeRenderer *)gauge_renderer,
0, // owner_ID (raw hard-wires 0)
display_port_index, // graphics_port_number
entity, // mech
screen, // screen number
p[2].data.string, p[3].data.string, p[4].data.string,
p[5].data.string, p[6].data.string, p[7].data.string,
p[8].data.string,
"PrepEngr");
return True;
}
//
// @004c7d14 -- dtor: release + free the seven images, tear down the child chain
// (FUN_0044ae0c), Gauge-group dtor (FUN_00443f7c).
// @004c7bf0 -- ctor. GraphicGaugeBackground base (mode/renderer/0/port/"PrepEngr");
// store screen + mech; copy the 7 image names and pre-load (AddRef) each from the
// bitmap cache.
//
// @004c7e48 -- Execute. Walk the mech's critical-subsystem roster
// (mech+0x124 count, mech+0x128 array). For each subsystem that (a) is a
// Generator-derived weapon (IsDerivedFrom 0x50f4bc) and (b) belongs to the
// selected screen (subsys+0x1dc == screenNumber), draw its label and -- via
// NumericDisplay (FUN_004700bc/0047043 0) -- its temperature and a HeatSink
// read-out, then dispatch on ClassID to lay out the weapon's status bitmaps
// (FUN_004c79c8 = draw three labelled status cells):
// 0xBC3 Sensor -> one cell + ammo bitmap (this[0x23])
// 0xBC6 -> two cells (this[0x22],this[0x20])
// 0xBC8/0xBD4 Emitter/PPC -> three cells (this[0x1E..0x20])
// 0xBCD/0xBD0 Proj/Missile -> one cell (this[0x21])
// 0xBCE GaussRifle -> warn "Gauss rifle not yet supported", one cell
PrepEngrScreen::PrepEngrScreen(
ModeMask mode_mask, L4GaugeRenderer *renderer_in, unsigned int owner_ID,
int graphics_port_number, Entity *mech_in, int screen_number,
const char *img0, const char *img1, const char *img2, const char *img3,
const char *img4, const char *img5, const char *img6,
const char *identification_string
):
GraphicGaugeBackground(mode_mask, renderer_in, owner_ID, // FUN_004440d4
graphics_port_number, identification_string)
{
screenNumber = screen_number; // @0x6C (:1216)
mech = mech_in; // @0x70 (:1215)
const char *src[7] = { img0, img1, img2, img3, img4, img5, img6 };
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer; // renderer+0x4c
for (int i = 0; i < 7; i++) // :1217-1238
{
if (src[i] != NULL) {
statusImage[i] = new char[strlen(src[i]) + 1]; // nameCopy (FUN_004700ac)
strcpy(statusImage[i], src[i]);
warehouse->bitMapBin.Get(statusImage[i]); // FUN_00442aec pre-load/AddRef
} else {
statusImage[i] = NULL;
}
}
}
//
// @004c7d14 -- dtor. Release the 7 pre-loaded images + free their name copies;
// the localView + base dtor run implicitly (do NOT emit explicit base-dtor calls).
//
PrepEngrScreen::~PrepEngrScreen()
{
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
for (int i = 0; i < 7; i++) // :1254-1274
{
if (statusImage[i] != NULL) {
warehouse->bitMapBin.Release(statusImage[i]); // FUN_00442c12
delete[] statusImage[i];
statusImage[i] = NULL;
}
}
}
//
// @004c7e48 -- BecameActive. Find the subsystem assigned to this screen and paint
// its labels (roster + aux-screen read via the databinding-safe accessors + the
// PoweredSubsystem bridge, exactly like VehicleSubSystems::Make). NON-inactivating
// (a real paint; does not chain the base) -> the paint-on-activation contract.
//
void
PrepEngrScreen::BecameActive()
{
if (mech == NULL) return;
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
const char *font = statusImage[0]; // helv42.pcc
int count = mech->GetSubsystemCount(); // mech+0x124
for (int i = 1; i < count; i++) // skip slot 0 (controls mapper)
{
Subsystem *sub = mech->GetSubsystem(i); // mech+0x128[i]
if (sub == NULL) continue;
int auxScreen = 0, placement = -1; char label64[64]; label64[0] = '\0';
if (!BTGetSubsystemAuxScreen(sub, &auxScreen, &placement, label64)) // filter + sub+0x1dc
continue; // not a PoweredSubsystem-derived weapon
if (auxScreen != screenNumber) // :1309
continue;
// ---- numeric #1 : the screen number, 2 digits @(0xE9,0x176) (:1310-1313)
{
NumericDisplay num(warehouse, 0xe9, 0x176, font, 2,
NumericDisplay::unsignedFormat, 0, 0xff); // FUN_004700bc
num.Draw(&localView, (Scalar)screenNumber); // FUN_00470430
}
// ---- numeric #2 : linked heat-sink number, 1 digit @(0x15E,5) (:1315-1321)
// BEST-EFFORT (marked): the resolved sink's +0x1D4 index is a raw offset on
// a not-byte-exact object -> guarded (NULL link / read 0).
int hsNumber = 0;
void *hsLink = AttributePointerOf(sub, "HeatSink"); // FUN_0041bfc0
void *hs = (hsLink != NULL) ? ResolveLink(hsLink) : NULL; // FUN_00417ab4
if (hs != NULL)
hsNumber = *(int *)((char *)hs + 0x1d4);
{
NumericDisplay num(warehouse, 0x15e, 5, font, 1,
NumericDisplay::unsignedFormat, 0, 0xff);
num.Draw(&localView, (Scalar)hsNumber);
}
// ---- subsystem label bitmap @(0x125,0x172) (:1322-1329). The binary draws
// the .pcc name at sub+0x224; the aux-screen bridge's label64 is the
// databinding-safe equivalent (BEST-EFFORT).
if (label64[0] != '\0') {
localView.SetColor(0xff);
DrawPortBackground(&localView, warehouse, 0x125, 0x172, label64);
}
// ---- type-specific label cells (switch on ClassID, :1330-1357)
switch (sub->GetClassID()) // sub+4
{
case 0xBC3: // Sensor
DrawStatusCells(&localView, warehouse, 1, NULL, NULL, NULL);
localView.SetColor(0xff);
DrawPortBackground(&localView, warehouse, 0xb8, 0x8c, statusImage[6]); // btesrnge
return;
case 0xBC6: // Myomers
DrawStatusCells(&localView, warehouse, 0, NULL, statusImage[5], statusImage[3]);
return;
case 0xBC8: // Emitter
case 0xBD4: // PPC
DrawStatusCells(&localView, warehouse, 0, statusImage[1], statusImage[2], statusImage[3]);
return;
case 0xBCD: // ProjectileWeapon
case 0xBD0: // MissileLauncher
DrawStatusCells(&localView, warehouse, 1, NULL, NULL, statusImage[4]);
return;
case 0xBCE: // GaussRifle -- not yet supported
DEBUG_STREAM << "PrepEngr: Gauss rifle not yet supported\n"; // :1353
DrawStatusCells(&localView, warehouse, 1, NULL, NULL, statusImage[3]);
return;
default:
return;
}
}
}
//###########################################################################
+24 -20
View File
@@ -290,35 +290,39 @@
//#######################################################################
// PrepEngrScreen -- builds the per-weapon "engineering" status screen.
// @004c7b30 Make / @004c7bf0 ctor / @004c7d14 dtor. vtable PTR_FUN_0051a06c.
// Derives from the non-graphic Gauge group (FUN_004440d4) and interns seven
// status pixmaps (this[0x1D..0x23]). Make validates DAT_0051936c (engineer
// screen number, must be 1..12; warns "PrepEngr: screen number out of range").
// Execute @004c7e48 walks the mech's critical-subsystem roster (mech+0x124
// count / mech+0x128 array) and, for each weapon on the selected screen,
// renders its label & ammo via NumericDisplay, dispatching on subsystem
// ClassID (0xBC3 Sensor, 0xBC6, 0xBC8/0xBD4 Emitter/PPC, 0xBCD/0xBD0
// Projectile/Missile, 0xBCE GaussRifle -- "Gauss rifle not yet supported").
// PrepEngrScreen (config keyword "prepEngr") -- the per-engineering-screen
// STATIC LABEL overlay. @004c7b30 Make / @004c7bf0 ctor / @004c7d14 dtor /
// @004c7e48 BecameActive. vtable PTR_FUN_0051a06c.
//
// A GraphicGaugeBackground (paint-on-activation, NOT an Execute gauge -- the
// same base/vtable shape as the engine's BackgroundBitmap: only the dtor +
// BecameActive slots are overridden). Interns 7 status .pcc's (font + 6
// labels) and, when its MFD-Eng screen (1..12) activates, paints the labels
// for the ONE subsystem whose auxScreenNumber == this screen, dispatched by
// the subsystem's ClassID. Was a MISLABELED stub (base Gauge, Execute, and
// mech/screenNumber SWAPPED) with no methodDescription -> "prepEngr" was
// parse-skipped (12 CFG calls never built).
//#######################################################################
class PrepEngrScreen :
public Gauge // non-graphic Gauge group (FUN_004440d4)
public GraphicGaugeBackground
{
public:
static MethodDescription methodDescription; // "prepEngr"
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004c7b30
PrepEngrScreen( // @004c7bf0
GaugeRate, ModeMask, L4GaugeRenderer *, int screen_number,
Entity *mech, int graphics_port_number,
const char *img0, const char *img1, const char *img2,
ModeMask, L4GaugeRenderer *, unsigned int owner_ID,
int graphics_port_number, Entity *mech, int screen_number,
const char *img0/*font*/, const char *img1, const char *img2,
const char *img3, const char *img4, const char *img5,
const char *img6, const char *identification_string);
const char *img6, const char *identification_string = "PrepEngr");
~PrepEngrScreen(); // @004c7d14
void Execute(); // @004c7e48
void BecameActive(); // @004c7e48 (the paint slot -- NOT Execute)
protected:
Entity *mech; // @0x6C this[0x1B]
int screenNumber; // @0x70 this[0x1C]
char *statusImage[7]; // @0x74 this[0x1D..0x23]
// child gauges chained in the base Gauge group at +0x24
int screenNumber; // @0x6C this[0x1B] (FIX: was mech)
Entity *mech; // @0x70 this[0x1C] (FIX: was screenNumber)
char *statusImage[7]; // @0x74 this[0x1D..0x23] (interned .pcc names)
// sizeof == 0x90 (Make alloc); no offset locks (2007 GraphicGaugeBackground
// base not byte-identical; every read is via named members / safe accessors).
};
+1
View File
@@ -148,6 +148,7 @@ MethodDescription
&PilotList::methodDescription, // "pilotList" -- Comm KILLS/DEATHS pilot roster
&GeneratorCluster::methodDescription, // "GeneratorCluster" -- the 4 generator engineering panels (buttons 9-12)
&SectorDisplay::methodDescription, // "sectorDisplay" -- radar SECTOR X/Z read-out (Secondary overlay)
&PrepEngrScreen::methodDescription, // "prepEngr" -- per-Eng-screen static label overlay (12x)
&BTL4ChainToPrevious
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB