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>
47 lines
8.0 KiB
Markdown
47 lines
8.0 KiB
Markdown
I have now verified every load-bearing claim against the raw decomp, the vtable data, the BackgroundBitmap twin, and the established reconstruction templates. Here is my adversarial-verification verdict.
|
||
|
||
---
|
||
|
||
# VERIFY VERDICT: PrepEngrScreen spec — **CONFIRMED CORRECT** (with 3 minor strengthenings)
|
||
|
||
The colleague's spec is **sound and ready-to-implement**. Its three headline corrections to the existing stub are all independently verified. I found no incorrect claim; the changes below are hardening, not fixes.
|
||
|
||
## Core claims — all VERIFIED against the raw
|
||
|
||
**(a) Base class / sizeof / ctor offsets — CORRECT**
|
||
- Base = `GraphicGaugeBackground`, **confirmed**: PrepEngr's ctor `FUN_004c7bf0` calls base ctor `FUN_004440d4` (part_014.c:1213), and `FUN_004440d4` is the ctor called by `BackgroundBitmap` and its siblings (part_009.c:1633/1695/1758/1838/1956) — the L4GAUGE `GraphicGaugeBackground` leaves. 5-arg base ctor `(mode, renderer, owner, port, id)` matches `GraphicGaugeBackground(ModeMask, GaugeRenderer*, unsigned, int, const char*)` (GAUGE.h:117). The stub's `public Gauge` is **wrong** → change to `GraphicGaugeBackground`.
|
||
- sizeof **0x90** = `FUN_00402298(0x90)` (part_014.c:1189). ✓
|
||
- ctor field writes: `param_1[0x1b]=param_7`(screen)@0x6C, `param_1[0x1c]=param_6`(mech)@0x70 (part_014.c:1215-1216). **Stub has these two swapped** (btl4gau2.hpp:318-319) → spec's fix is correct.
|
||
- ctor param order `(mode, renderer, owner, port, mech, screen, img0..6, id)` matches the Make→ctor call (part_014.c:1191). ✓
|
||
|
||
**(b) methodDescription rows — CORRECT.** CFG `prepEngr(...)` (L4GAUGE.CFG:4595) has exactly 9 args: `ModeMFD1Eng1, 1, helv42.pcc, escnd.pcc, edmg.pcc, bteslev.pcc, bteunjm.pcc, espeed.pcc, btesrnge.pcc` → `typeModeMask, typeInteger, 7×typeString`. Global-DAT spacing confirms the parameterList mapping: `sizeof(ParameterDescription)=0x44` (4 + `maxStringLength`=0x40, GAUGREND.h:242-262), and `DAT_0051936c`(screen)=`parameterList[1].data`, `DAT_005193b0`(img0)=`parameterList[2].data.string`, stride 0x44 — exactly the reconstruction's read `p[i].data.{integer,string,modeMask}`. ✓
|
||
|
||
**(c) Container-Execute rule — CORRECTLY N/A.** `Execute` first appears on `Gauge` (GAUGE.h:366, vtable slot ~14); a `GraphicGaugeBackground` vtable has only 13 slots and no `Execute`. **The overridden slot is BecameActive, not Execute** — proved three ways:
|
||
1. PrepEngr vtable (vtables.tsv row `0051a040`, ptr `0051a06c`) = `[4c7d14(dtor), 4173b8, 417938, 4c7e48, 444004…]` — **byte-identical shape** to BackgroundBitmap (`004fb918`, ptr `471d7c`) = `[471d7c(dtor), 4173b8, 417938, 471dec, 444004…]`.
|
||
2. `BackgroundBitmap` is *explicitly* a `GraphicGaugeBackground` (L4GAUGE.cpp:2443) whose **only non-dtor override is `BecameActive()`** (L4GAUGE.cpp:2511) → its single override slot (3) = BecameActive → PrepEngr's slot 3 (`4c7e48`) = BecameActive.
|
||
3. Slots 1/2 are Node virtuals: `FUN_004173b8` = `return 0` = `Node::ReceiveNodeCommand` (node.h:10 inline `{return 0;}`); `FUN_00417938` = `ReleaseLinkHandler` — so slot 3 is the *first GaugeBase virtual* = `BecameActive` (GAUGE.h:52).
|
||
`FUN_004c7e48` is a paint-on-activation (walk roster → draw labels into `localView`), semantically a BecameActive, not a per-frame Execute. No container/child-chain exists (ctor builds none; dtor `FUN_004c7d14` only frees 7 images + implicit localView/base dtor) → the abort-on-unoverridden-Execute hazard does not apply. ✓
|
||
|
||
**(d) shadow/alias/phantom / databinding — CORRECTLY handled.** No shadow/alias/phantom fields. Roster read uses the databinding-safe `Entity::GetSubsystemCount()/GetSubsystem(i)` (ENTITY.h:167-177, reads the reconstructed `subsystemArray/subsystemCount`) + `BTGetSubsystemAuxScreen` — the exact `VehicleSubSystems::Make` pattern (btl4gau2.cpp:1621-1636). NumericDisplay/DrawPortBackground operate on the object's own `localView`/`warehouse`. The two raw-offset reads (`hs+0x1D4`, `sub+0x224`) are correctly guarded fail-soft and flagged (§6).
|
||
|
||
**(e) every-mech / abort / NULL — no crash risk.** `mech` null-checked; `GetSubsystem(i)` bounded by `i<count` (its internal `Verify(index<subsystemCount)` can't trip); `ResolveLink("HeatSink")` null-guarded before the `+0x1D4` read; the `0x50f4bc` filter + auxScreen check gate every deref. Not an every-mech path (gauge is bound to the viewpoint mech). ✓
|
||
|
||
## Bonus confirmations (spec was even more right than it claimed)
|
||
- **NumericDisplay usage is EXACT, not just "structural."** L4GAUGE.h:22-63 defines `class NumericDisplay` with `NumericDisplay(L4Warehouse*, int x, int y, const char* font, int digits, NumericFormat, int bg, int fg)` and `void Draw(GraphicsView*, Scalar)`. The spec's `NumericDisplay num(warehouse,0xe9,0x176,font,2,unsignedFormat,0,0xff); num.Draw(&localView,(Scalar)screenNumber)` maps **1:1** to the raw `FUN_004700bc(…,0xe9,0x176,font,2,0,0,0xff)` + `FUN_00470430(…)` + dtor `FUN_0047018c` (part_014.c:1310-1313). `Draw` (not `DrawAt`) is the correct method name.
|
||
- **`DrawPortBackground(GraphicsView*, L4Warehouse*, int x, int y, const char*)`** = `FUN_004c2ec4` (btl4gau2.cpp:923) — signature matches the spec's calls; the raw's `(x,y,view,warehouse,name)` arg order is correctly reordered by the existing helper.
|
||
|
||
## Changes I made (3, all hardening)
|
||
|
||
1. **Make allocation — switch to plain `new PrepEngrScreen(...)`** (drop `operator new(0x90)`+placement-new). The reconstructed `GraphicsView localView` is not byte-exact to the 1995 `0x24..0x6B` window, so `sizeof(PrepEngrScreen)` may exceed 0x90 — a fixed `operator new(0x90)` would then heap-overflow. `new PrepEngrScreen(...)` allocates the true size and matches `BackgroundBitmap::Make` (L4GAUGE.cpp:2379); no external code reads this object at raw offsets, so the binary alloc size is not load-bearing. Keep `static_assert(sizeof(PrepEngrScreen) <= 0x90)` only as a compile-time canary. Optionally add `Register_Object(g);` after construction for parity with BackgroundBitmap (debug leak-tracking only).
|
||
|
||
2. **§6 #4 UPGRADED from "unknown" to CONFIRMED FAITHFUL.** `BTGetSubsystemAuxScreen` (powersub.cpp:1333) applies `BTIsPoweredSubsystem` = `FUN_0041a1a4(…,0x50f4bc)` — the *exact* filter the raw uses (part_014.c:1308) — and returns `auxScreenNumber` via the real `PoweredSubsystem` type (+0x104 = binary `sub+0x1DC`). So folding the filter + `auxScreen==screenNumber` gate into the bridge is byte-faithful; the qualifying-subsystem set is identical.
|
||
|
||
3. **Acknowledge the dropped erase in the label block.** The raw does 3 unresolved `localView` vtbl calls (`+0x18/+0x24/+0x48`, args not recovered by Ghidra) *before* the `sub+0x224` label draw (part_014.c:1323-1325) — a cell-erase/setup. The spec drops them and substitutes `SetColor(0xff)` + `DrawPortBackground(label64)`. Cosmetic only; add to the §6 best-effort list next to #1.
|
||
|
||
## Residual UNKNOWNS (unchanged from spec, all cosmetic, none block registration)
|
||
- `sub+0x224` drawn-label ≠ `auxScreenLabel` (`sub+0x1E4`, what the bridge returns) — the label bitmap name may differ; needs a subsystem-ctor field-map pass (`disas2.py`). Guarded/flagged.
|
||
- `resolvedHeatSink+0x1D4` (1-digit sink index) is past byte-exact `HeatSink` (0x1D0) → resolved link must be a larger subclass; guarded raw read, best-effort.
|
||
- `DrawStatusCells` C-NULL branch `vtbl+0x4c(99,10)` — unresolved GraphicsView slot; plain fill substituted.
|
||
|
||
## FINAL CONFIDENCE: **HIGH**
|
||
Identity, base class, the BecameActive slot, layout, ctor/dtor/Make/BecameActive bodies, the 9-param methodDescription, and the `BTL4MethodDescription[]` registration line are all verified against `part_014.c`, `vtables.tsv`, `L4GAUGE.cpp/h`, `GAUGE.h`, `ENTITY.h`, and `powersub.cpp`. Registering `&PrepEngrScreen::methodDescription` (btl4grnd.cpp, before `&BTL4ChainToPrevious`) + the corrected header (`public GraphicGaugeBackground`, `void BecameActive()`, `screenNumber@0x6C`/`mech@0x70`) + the bodies as written (with change #1) will build and stop the 12 `prepEngr(...)` lines from parse-skipping. The only behavioral caveats are the two flagged cosmetic label reads. |