The "sectorDisplay" cockpit primitive (Secondary overlay, the radar SECTOR X/Z coordinate read-out) was PROSE-ONLY in btl4gau3 (placeholder Make, no ctor/ methodDescription/registration) -> the config line was parse-SKIPPED and never built. Reconstructed byte-verified from the disassembly (ctor @4c9e10, Execute @4ca07c, methodDescription PE-parse): SectorDisplay : GraphicGauge, sizeof 0xC4. Its Execute reads the linked mech's world position and shows two 100-unit sector numerics: numericA = Round(-localOrigin.z * 0.01) + 500 numericB = Round( localOrigin.x * 0.01) + 500 (rounding = round-to-nearest == FUN_004dcd94, corrected from the reviewer's wrong "truncate" claim; 0.01 const PE-verified; -Z/+X axis + fchs confirmed from asm). Overridden slots: LinkToEntity(9) caches the subject, BecameActive(3, non-inactivating), Execute(16) -> satisfies the container-Execute rule. Layout overflow-locked (static_assert sizeof<=0xC4). Make/ctor/dtor mirror the registered PilotList sibling; the config image name is copied (nameCopy) since Execute reads it per-frame. Registered in BTL4MethodDescription[]. VERIFIED LIVE (BT_SECTOR_LOG): Make port=1 pos=(125,579) image=helv15.pcc gridCached=1; Execute -Z=960.4 X=361.6 -> sectorA=510 sectorB=504 (Round(9.6)+500=510, Round(3.6)+500=504 -- authentic 100-unit sectors from live mech position). Gauge composite renders full, no crash. The skip list is now exactly the two remaining widgets (prepEngr x12, messageBoard). Also: a permanent BT_GAUGE_SKIP_LOG diagnostic (GAUGREND.cpp, gated) that logs each unregistered gauge primitive the dev-parse skips -- the tool that pinned this down (earlier "not built" runs were killed before the lazy gauge-renderer init). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
24 KiB
I now have complete, definitive coverage. The vtables.tsv row 0051a040 shows PrepEngr's slots …4c7d14 4173b8 417938 4c7e48… — byte-identical in shape to BackgroundBitmap's …471d7c 4173b8 417938 471dec… (dtor, 2 inherited, BecameActive). This confirms FUN_004c7e48 is the BecameActive slot and PrepEngrScreen is a GraphicGaugeBackground. Everything is traced. Here is the full spec.
PrepEngrScreen — decode & reconstruction spec (config keyword prepEngr)
Bottom line up front: the existing
btl4gau2.hppstub forPrepEngrScreenis wrong in three ways and there is nomethodDescription/Make, soprepEngr(...)is parse-skipped (12 CFG calls never build). It is NOT anExecute-drivenGaugewith a child chain — it is aGraphicGaugeBackgroundpainted on activation (identical base/vtable shape to the engine'sBackgroundBitmap). Corrections: (1) base =GraphicGaugeBackground, notGauge; (2) the overridden slot isBecameActive(), notExecute(); (3)screenNumber@0x6Candmech@0x70are swapped in the stub.
1. Identity
| Field | Value | Citation |
|---|---|---|
| Class name | PrepEngrScreen |
id-string s_PrepEngr_00519a9d = "PrepEngr", part_014.c:1193 |
| Config keyword | prepEngr |
L4GAUGE.CFG:4595…4759 (12×); it is the methodDescription's first string (distinct from the "PrepEngr" id-string) |
| Base class | GraphicGaugeBackground (GaugeBase→…→localView, no rate/Execute) |
ctor calls base FUN_004440d4 (part_014.c:1213); FUN_004440d4 = GraphicGaugeBackground ctor (mode,renderer,ownerID,port,idstr) → localView(GraphicsViewRecord) at +0x24 / index 9 (part_006.c @004440d4), identical to BackgroundBitmap (part_009.c @00471d00) |
| ctor | @004c7bf0 (FUN_004c7bf0) |
part_014.c:1204 |
| dtor | @004c7d14 (FUN_004c7d14) |
part_014.c:1246 |
| BecameActive (the paint) | @004c7e48 (FUN_004c7e48) |
part_014.c:1288 |
| Make (config factory) | @004c7b30 (FUN_004c7b30) |
part_014.c:1172 |
| methodDescription | in .rdata (not exported by the fn-only decompiler) — reconstruct from the CFG call shape |
— |
| vtable | PTR_FUN_0051a06c |
part_014.c:1214,1252 |
| sizeof | 0x90 (= FUN_00402298(0x90) in Make) |
part_014.c:1189 |
| GUID / classID | none (GraphicGaugeBackground carries no distinct classID; it's not roster-registered) | — |
Vtable-slot proof (reference/decomp/vtables.tsv, row 0051a040): … 4c7d14 4173b8 417938 4c7e48 … — the exact same shape as BackgroundBitmap (row 004fb918): … 471d7c 4173b8 417938 471dec …. i.e. [dtor][inherited][inherited][BecameActive]. Only dtor + BecameActive are overridden; everything else is inherited (444xxx base slots). TestInstance is non-virtual (GAUGE.h:46) → not a vtable override.
2. What it displays & data binding
prepEngr = "PREPARE the engineering screen": a static per-screen label overlay for one of the 12 MFD engineering screens (ModeMFD{1,2,3}Eng{1..4} → screen numbers 1..12; CFG:4595-4769). It is not the live bars/dials — those come from vehicleSubSystems/SubsystemCluster. PrepEngrScreen paints, once each time its screen becomes active, the labels appropriate to the one subsystem assigned to that screen.
BecameActive (@004c7e48) walks the mech's subsystem roster, finds the first subsystem whose auxScreenNumber == this->screenNumber, and paints:
| Element | Source | Where drawn | Live now? |
|---|---|---|---|
| screen-number numeric (2 digits) | this->screenNumber (own member) |
(0xE9,0x176) | LIVE (own field) |
| linked heat-sink numeric (1 digit) | ResolveLink(AttributePointerOf(sub,"HeatSink")) + 0x1D4 |
(0x15E,5) | DEFERRED — raw offset on the resolved sink (databinding risk; guard NULL) |
| subsystem label bitmap | sub + 0x224 (embedded char[] = a .pcc name) |
(0x125,0x172) | DEFERRED — raw subsystem offset, not a byte-exact field; use the BTGetSubsystemAuxScreen label bridge instead |
| type-specific label cells | this->statusImage[1..6] dispatched by sub->GetClassID() |
fixed cells via DrawStatusCells |
LIVE (own members + classID accessor) |
ClassID → label dispatch (switch(sub+4), part_014.c:1330-1357; images are this[0x1D..0x23], byte 0x74..0x8C):
| ClassID | Subsystem | DrawStatusCells(topBox, A, B, C) |
+extra |
|---|---|---|---|
| 0xBC3 | Sensor | (1, –, –, –) |
draw statusImage[6]=btesrnge.pcc @(0xB8,0x8C) — part_014.c:1332-1334 |
| 0xBC6 | Myomers | (0, –, img5=espeed, img3=bteslev) |
part_014.c:1339 |
| 0xBC8 / 0xBD4 | Emitter / PPC | (0, img1=escnd, img2=edmg, img3=bteslev) |
part_014.c:1344 |
| 0xBCD / 0xBD0 | ProjectileWeapon / MissileLauncher | (1, –, –, img4=bteunjm) |
part_014.c:1349 |
| 0xBCE | GaussRifle | warn "…Gauss rifle not yet supported" + (1, –, –, img3=bteslev) |
part_014.c:1353-1354 |
| default | — | nothing (return) | part_014.c:1336 |
Databinding-safe roster access (the trap: reconstructed Mech is 0x638 vs binary 0x854, so raw mech+0x124/+0x128 read garbage). Use the same accessors the sibling VehicleSubSystems::Make uses (btl4gau2.cpp:1621-1636): entity->GetSubsystemCount(), entity->GetSubsystem(i), and BTGetSubsystemAuxScreen(sub,&auxScreen,&placement,label64) (declared btl4gau2.cpp:151) — the bridge both applies the FUN_0041a1a4(**(sub+0xc),0x50f4bc) PoweredSubsystem type filter (part_014.c:1308) and returns auxScreenNumber (sub+0x1DC, part_014.c:1309), replacing the two raw reads.
Content (from all 12 CFG calls, identical param set, CFG:4595-4605): font helv42.pcc; labels escnd.pcc(sensor) edmg.pcc(damage) bteslev.pcc(seek/energy level) bteunjm.pcc(unjam) espeed.pcc(speed) btesrnge.pcc(range).
3. Class layout (byte-exact) + locks
Binary layout (from ctor FUN_004c7bf0 int-index writes = byte N×4, and the FUN_00402298(0x90) alloc):
GraphicGaugeBackground base .......... 0x00 .. 0x23 (GaugeBase fields incl. renderer@0x1C)
localView (GraphicsView) ........... 0x24 .. 0x6B (GraphicsViewRecord, init'd by FUN_0044ad78)
screenNumber int ................. 0x6C this[0x1B] (param_7; part_014.c:1216) ← NOT mech
mech Entity* ............. 0x70 this[0x1C] (param_6; part_014.c:1215) ← NOT screenNumber
statusImage[0] char* (font helv42) . 0x74 this[0x1D] (part_014.c:1218)
statusImage[1] char* (escnd) ....... 0x78 this[0x1E] (:1220)
statusImage[2] char* (edmg) ........ 0x7C this[0x1F] (:1222)
statusImage[3] char* (bteslev) ..... 0x80 this[0x20] (:1224)
statusImage[4] char* (bteunjm) ..... 0x84 this[0x21] (:1226)
statusImage[5] char* (espeed) ...... 0x88 this[0x22] (:1228)
statusImage[6] char* (btesrnge) .... 0x8C this[0x23] (:1230)
sizeof = 0x90
⚠ The current
btl4gau2.hpp:318-320stub hasmech@0x6C/screenNumber@0x70swapped and baseGauge. Both must change.
Because the reconstruction's GraphicGaugeBackground/GraphicsView are not byte-exact to the 1995 layout, do not emit offsetof locks against the binary offsets for the own fields (they'd fail like Sensor/heat-leaf). The only load-bearing size fact is the Make alloc; assert only that the compiled object fits the binary alloc:
static_assert(sizeof(PrepEngrScreen) <= 0x90, "PrepEngrScreen exceeds binary alloc 0x90");
(If you new PrepEngrScreen normally rather than operator new(0x90), even this is optional — no external code reads PrepEngrScreen at raw offsets.)
4. Full bodies to write
4a. Header (replace the stub at btl4gau2.hpp:304-322)
//#######################################################################
// PrepEngrScreen -- "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 --
// same base/shape as the engine's BackgroundBitmap, FUN_00471d00). Interns
// 7 status .pcc's (font + 6 labels) and, when its MFD-Eng screen activates,
// paints the labels for the ONE subsystem whose auxScreenNumber == this
// screen (1..12), dispatched by the subsystem's ClassID.
//#######################################################################
class PrepEngrScreen :
public GraphicGaugeBackground
{
public:
static MethodDescription methodDescription;
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004c7b30
PrepEngrScreen( // @004c7bf0
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 = "PrepEngr");
~PrepEngrScreen(); // @004c7d14
void BecameActive(); // @004c7e48 (the paint slot -- NOT Execute)
protected:
int screenNumber; // @0x6C this[0x1B]
Entity *mech; // @0x70 this[0x1C]
char *statusImage[7]; // @0x74 this[0x1D..0x23] (interned .pcc names)
};
4b. Implementation (add to btl4gau2.cpp, replacing the prose block at 819-851)
//###########################################################################
// PrepEngrScreen -- "prepEngr" @004c7b30 Make / @004c7bf0 ctor /
// @004c7d14 dtor / @004c7e48 BecameActive. A GraphicGaugeBackground.
//###########################################################################
// ---- FUN_004c79c8 : draw the 3 status-label cells (+ optional top erase box).
// Each cell either blits its label .pcc or blanks the cell. Coords are the
// binary's exact absolute cell rectangles (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) // part_014.c: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); } // FUN_004c2ec4
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
// NB: raw uses GraphicsView vtbl+0x4c(99,10) then MoveToAbsolute(10,0)
// then DrawFilledRectangleToAbsolute(0x9f,0x2d) -- a compound blank.
// vtbl+0x4c is an unresolved GraphicsView slot; a plain fill is close:
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. Validate the screen number (p[1], must be 1..12) then
// construct. The GraphicGaugeBackground base ctor self-registers with the
// renderer (GAUGE.cpp GaugeBase::GaugeBase -> renderer->Add(this)), so the
// gauge's BecameActive fires whenever its Eng screen is selected.
//
Logical
PrepEngrScreen::Make(
int display_port_index,
Vector2DOf<int> /*position -- ignored; 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) // part_014.c:1182
{
DebugStream << "PrepEngr: screen number out of range " << screen << "\n"; // :1183-1185
return False; // raw returns 0
}
// operator new(0x90) + placement-new to mirror FUN_00402298(0x90) (:1189).
PrepEngrScreen *g = (PrepEngrScreen *)operator new(0x90);
if (g != NULL)
new (g) PrepEngrScreen(
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 (Make param_4)
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; // raw returns 1
}
//
// @004c7bf0 -- ctor. GraphicGaugeBackground base (mode/renderer/0/port/"PrepEngr");
// store screen + mech; nameCopy the 7 images and pre-load (AddRef) each from the
// bitmap cache. (No SetOrigin -- localView keeps its default origin.)
//
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 (param_7, :1216)
mech = mech_in; // @0x70 (param_6, :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++)
{
statusImage[i] = nameCopy(src[i]); // FUN_004700ac (:1217-1230)
warehouse->bitMapBin.Get(statusImage[i]); // FUN_00442aec pre-load/AddRef (:1232-1238)
}
}
//
// @004c7d14 -- dtor. Release the 7 pre-loaded images + free their name copies.
// (The localView + base dtor run implicitly via the C++ epilogue -- do NOT emit
// explicit FUN_0044ae0c/FUN_00443f7c calls; they are compiler glue, per the
// reconstruction destructor rule.)
//
PrepEngrScreen::~PrepEngrScreen()
{
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
for (int i = 0; i < 7; i++)
{
warehouse->bitMapBin.Release(statusImage[i]); // FUN_00442c12 (:1254-1260)
delete[] statusImage[i]; // FUN_004022e8 (:1261-1274)
statusImage[i] = NULL;
}
}
//
// @004c7e48 -- BecameActive. Find the subsystem on this screen and paint its
// labels. (Roster/aux-screen read via the databinding-safe accessors + the
// PoweredSubsystem bridge, exactly like VehicleSubSystems::Make.)
//
void
PrepEngrScreen::BecameActive()
{
if (mech == NULL) return;
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer; // this[0x1C]->+0x4c
const char *font = statusImage[0]; // helv42.pcc
int count = mech->GetSubsystemCount(); // mech+0x124
for (int i = 1; i < count; i++) // skip slot 0 (the controls mapper)
{
Subsystem *sub = mech->GetSubsystem(i); // mech+0x128[i]
if (sub == NULL) continue;
int auxScreen = 0, placement = -1; char label64[64];
if (!BTGetSubsystemAuxScreen(sub, &auxScreen, &placement, label64)) // FUN_0041a1a4 filter + sub+0x1dc
continue; // not a PoweredSubsystem-derived weapon
if (auxScreen != screenNumber) // part_014.c: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
} // ~NumericDisplay = FUN_0047018c
// ---- numeric #2 : linked heat-sink number, 1 digit @(0x15E,5) (:1315-1321)
int hsNumber = 0;
void *hsLink = AttributePointerOf(sub, "HeatSink"); // FUN_0041bfc0
Subsystem *hs = (Subsystem *)ResolveLink(hsLink); // FUN_00417ab4
if (hs != NULL)
hsNumber = *(int *)((char *)hs + 0x1d4); // BEST-EFFORT raw (see §6)
{
NumericDisplay num(warehouse, 0x15e, 5, font, 1,
NumericDisplay::unsignedFormat, 0, 0xff);
num.Draw(&localView, (Scalar)hsNumber);
}
// ---- subsystem label bitmap @(0x125,0x172) (:1322-1329)
// Raw draws the .pcc name at sub+0x224; use the aux-screen label bridge
// (label64) for a databinding-safe equivalent. (BEST-EFFORT; see §6.)
localView.SetColor(0xff);
if (label64[0] != '\0')
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
DebugStream << "PrepEngr: Gauss rifle not yet supported\n"; // :1353
DrawStatusCells(&localView, warehouse, 1, NULL, NULL, statusImage[3]);
return;
default:
return;
}
}
}
DrawPortBackground(view, warehouse, x, y, tile)(btl4gau2.cpp:923) isFUN_004c2ec4;nameCopy,AttributePointerOf,ResolveLink,BTGetSubsystemAuxScreen,NumericDisplay,L4Warehouseare already available in this TU.DrawPortBackgroundcurrently takes(GraphicsView*, L4Warehouse*, int, int, const char*)— matches the calls above.
4c. Registration (btl4grnd.cpp, in BTL4MethodDescription[], before &BTL4ChainToPrevious)
&GeneratorCluster::methodDescription, // (existing, line 149)
&PrepEngrScreen::methodDescription, // "prepEngr" -- per-Eng-screen static label overlay
&BTL4ChainToPrevious
(#include <btl4gau2.hpp> is already present, btl4grnd.cpp:69.)
5. Systemic-check verdicts
- Container-Execute override needed? NO.
PrepEngrScreenis aGraphicGaugeBackground, which has noExecutevirtual (Executefirst appears onGauge, GAUGE.h:366). TheGauge::Execute → Fail→aborthazard does not apply. The overridden slot isBecameActive()(confirmed vtable-identical toBackgroundBitmap). The override body is a real paint (does not chain the inactivating base) — exactly likeBackgroundBitmap::BecameActive(L4GAUGE.cpp:2510), so the "default BecameActive inactivates" hazard is also handled. - Shadow fields? None. Own fields (
screenNumber/mech/statusImage) are new; the baseGraphicGaugeBackgroundownsrenderer/localView, which are used (not re-declared). Do NOT re-declarelocalVieworrenderer. - Alias fields? None.
- Phantom fields? None — all own fields are inside the 0x90 object; nothing past
sizeof. - The stub's two bugs to fix: base
Gauge→GraphicGaugeBackground;screenNumber/mechoffsets swapped;Execute()→BecameActive(). - Databinding NULL / every-mech crash risk:
mech==NULL→ guarded (early return).- roster via
GetSubsystemCount/GetSubsystem(safe accessors) + slot-0 skip → safe. ResolveLink("HeatSink")may be NULL → guarded before the+0x1D4read.sub+0x1D4andsub+0x224are raw subsystem offsets on not-byte-exact objects → fail-soft (wrong digit /bitMapBin.Getreturns NULL → draws nothing), not a crash. §6 gives the safe substitutes (label bridge; a "HeatSinkNumber" attribute if one is published).NumericDisplay/DrawStatusCells/DrawPortBackgroundoperate on the gauge's ownlocalView+warehouse(both valid post-ctor) → safe. NoGuardedExecuteneeded (background paints aren't SEH-wrapped, but nothing here dereferences unresolved game state without a guard).
- Registration / lifetime:
GaugeBase::GaugeBaseself-registers (renderer->Add(this), GAUGE.cpp), soMakeneeds no explicitRegister_Object(that macro is only debug memory-tracking, MEMREG.h:24). UnlikeBackgroundBitmap,prepEngris neverModeAlwaysActive(alwaysModeMFD*Eng*), so there is no run-once-then-delete branch — the gauge persists and repaints on each activation. Correct.
6. Confidence & unknowns
Confidence: HIGH for identity, base class, the BecameActive slot, the Make/ctor/dtor bodies, the methodDescription shape, the layout, and the ClassID→label dispatch. The base class, vtable slot, and lifetime are triple-confirmed (base-ctor FUN_004440d4 signature match + BackgroundBitmap structural twin + vtables.tsv row 0051a040). The CFG call shape (9 params) and image→slot mapping are pinned to the exact ctor stores and the switch.
Unknowns / follow-ups (all cosmetic, none block registration):
sub+0x224(subsystem label .pcc name), part_014.c:1322 — a raw embeddedchar[]used as a bitmap name. It is notSubsystem::subsystemName(GetName(), at a much lower offset) and not the aux-screen label (sub+0x1E4, whichBTGetSubsystemAuxScreenalready returns). Spec substitutes the bridge'slabel64; confirming/bridging the true+0x224field (or verifying it equals the aux label) needs adisas2.py/field-map pass on the subsystem ctor. Low value (one label bitmap).resolvedHeatSink+0x1D4, part_014.c:1317 — a 1-digit readout (the linked sink's bank/index). Byte-exactHeatSinkis only 0x1D0, so the resolved object must be a larger subclass for this to be in-bounds; raw read is best-effort. If aHeatSinkNumber/BankNumberattribute exists, preferAttributePointerOf; otherwise keep the guarded raw read.DrawStatusCellsempty-cell C-placeholder (part_014.c:1156-1159) uses GraphicsView vtbl+0x4c(99,10) — an unresolved GraphicsView slot (betweenDrawFilledRectangleToAbsolute@0x48andDrawBitMap@0x54; likely a relative-size rectangle). The spec draws a plain fill at the same coords; resolving the exact call needs a GraphicsView vtable decode. Only affects the blanked-cell appearance when a label is NULL.- The
FUN_0041a1a4(**(sub+0xc),0x50f4bc)filter (part_014.c:1308) is folded intoBTGetSubsystemAuxScreen(same treatment asVehicleSubSystems); if that bridge's filter ever diverges from the exactClassDerivationsresolution it would change which subsystems qualify — but the ClassIDswitchis the effective filter anyway, so this is faithful.
Net: registering &PrepEngrScreen::methodDescription + the bodies above stops the 12 prepEngr(...) lines from parse-skipping and paints the engineering-screen labels; the two raw subsystem reads (#1,#2) are guarded fail-soft best-efforts flagged for a later attribute/bridge pass.