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>
390 lines
24 KiB
Markdown
390 lines
24 KiB
Markdown
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.hpp` stub for `PrepEngrScreen` is **wrong in three ways** and there is **no `methodDescription`/`Make`**, so `prepEngr(...)` is parse-skipped (12 CFG calls never build). It is NOT an `Execute`-driven `Gauge` with a child chain — it is a **`GraphicGaugeBackground` painted on activation** (identical base/vtable shape to the engine's `BackgroundBitmap`). Corrections: (1) base = `GraphicGaugeBackground`, not `Gauge`; (2) the overridden slot is `BecameActive()`, not `Execute()`; (3) `screenNumber@0x6C` and `mech@0x70` are **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-320` stub has `mech@0x6C` / `screenNumber@0x70` **swapped** and base `Gauge`. 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:
|
||
|
||
```cpp
|
||
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`)
|
||
|
||
```cpp
|
||
//#######################################################################
|
||
// 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)
|
||
|
||
```cpp
|
||
//###########################################################################
|
||
// 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) **is** `FUN_004c2ec4`; `nameCopy`, `AttributePointerOf`, `ResolveLink`, `BTGetSubsystemAuxScreen`, `NumericDisplay`, `L4Warehouse` are already available in this TU. `DrawPortBackground` currently takes `(GraphicsView*, L4Warehouse*, int, int, const char*)` — matches the calls above.
|
||
|
||
### 4c. Registration (`btl4grnd.cpp`, in `BTL4MethodDescription[]`, before `&BTL4ChainToPrevious`)
|
||
|
||
```cpp
|
||
&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.** `PrepEngrScreen` is a `GraphicGaugeBackground`, which has **no `Execute` virtual** (`Execute` first appears on `Gauge`, GAUGE.h:366). The `Gauge::Execute → Fail→abort` hazard does not apply. The overridden slot is **`BecameActive()`** (confirmed vtable-identical to `BackgroundBitmap`). The override body is a real paint (does not chain the inactivating base) — exactly like `BackgroundBitmap::BecameActive` (L4GAUGE.cpp:2510), so the "default BecameActive inactivates" hazard is also handled.
|
||
- **Shadow fields?** None. Own fields (`screenNumber`/`mech`/`statusImage`) are new; the base `GraphicGaugeBackground` owns `renderer`/`localView`, which are **used** (not re-declared). Do NOT re-declare `localView` or `renderer`.
|
||
- **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`/`mech` offsets 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 `+0x1D4` read.
|
||
- `sub+0x1D4` and `sub+0x224` are **raw subsystem offsets** on not-byte-exact objects → **fail-soft** (wrong digit / `bitMapBin.Get` returns NULL → draws nothing), not a crash. §6 gives the safe substitutes (label bridge; a "HeatSinkNumber" attribute if one is published).
|
||
- `NumericDisplay`/`DrawStatusCells`/`DrawPortBackground` operate on the gauge's own `localView` + `warehouse` (both valid post-ctor) → safe. No `GuardedExecute` needed (background paints aren't SEH-wrapped, but nothing here dereferences unresolved game state without a guard).
|
||
- **Registration / lifetime:** `GaugeBase::GaugeBase` self-registers (`renderer->Add(this)`, GAUGE.cpp), so `Make` needs no explicit `Register_Object` (that macro is only debug memory-tracking, MEMREG.h:24). Unlike `BackgroundBitmap`, `prepEngr` is **never** `ModeAlwaysActive` (always `ModeMFD*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):**
|
||
1. **`sub+0x224` (subsystem label .pcc name), part_014.c:1322** — a raw embedded `char[]` used as a bitmap name. It is **not** `Subsystem::subsystemName` (`GetName()`, at a much lower offset) and **not** the aux-screen label (`sub+0x1E4`, which `BTGetSubsystemAuxScreen` already returns). Spec substitutes the bridge's `label64`; confirming/bridging the true `+0x224` field (or verifying it equals the aux label) needs a `disas2.py`/field-map pass on the subsystem ctor. Low value (one label bitmap).
|
||
2. **`resolvedHeatSink+0x1D4`, part_014.c:1317** — a 1-digit readout (the linked sink's bank/index). Byte-exact `HeatSink` is only 0x1D0, so the resolved object must be a larger subclass for this to be in-bounds; raw read is best-effort. If a `HeatSinkNumber`/`BankNumber` attribute exists, prefer `AttributePointerOf`; otherwise keep the guarded raw read.
|
||
3. **`DrawStatusCells` empty-cell C-placeholder** (part_014.c:1156-1159) uses GraphicsView **vtbl+0x4c(99,10)** — an unresolved GraphicsView slot (between `DrawFilledRectangleToAbsolute@0x48` and `DrawBitMap@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.
|
||
4. **The `FUN_0041a1a4(**(sub+0xc),0x50f4bc)` filter** (part_014.c:1308) is folded into `BTGetSubsystemAuxScreen` (same treatment as `VehicleSubSystems`); if that bridge's filter ever diverges from the exact `ClassDerivations` resolution it would change *which* subsystems qualify — but the ClassID `switch` is 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. |