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>
8.0 KiB
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 ctorFUN_004c7bf0calls base ctorFUN_004440d4(part_014.c:1213), andFUN_004440d4is the ctor called byBackgroundBitmapand its siblings (part_009.c:1633/1695/1758/1838/1956) — the L4GAUGEGraphicGaugeBackgroundleaves. 5-arg base ctor(mode, renderer, owner, port, id)matchesGraphicGaugeBackground(ModeMask, GaugeRenderer*, unsigned, int, const char*)(GAUGE.h:117). The stub'spublic Gaugeis wrong → change toGraphicGaugeBackground. - 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:
- PrepEngr vtable (vtables.tsv row
0051a040, ptr0051a06c) =[4c7d14(dtor), 4173b8, 417938, 4c7e48, 444004…]— byte-identical shape to BackgroundBitmap (004fb918, ptr471d7c) =[471d7c(dtor), 4173b8, 417938, 471dec, 444004…]. BackgroundBitmapis explicitly aGraphicGaugeBackground(L4GAUGE.cpp:2443) whose only non-dtor override isBecameActive()(L4GAUGE.cpp:2511) → its single override slot (3) = BecameActive → PrepEngr's slot 3 (4c7e48) = BecameActive.- 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_004c7e48is a paint-on-activation (walk roster → draw labels intolocalView), semantically a BecameActive, not a per-frame Execute. No container/child-chain exists (ctor builds none; dtorFUN_004c7d14only 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 NumericDisplaywithNumericDisplay(L4Warehouse*, int x, int y, const char* font, int digits, NumericFormat, int bg, int fg)andvoid Draw(GraphicsView*, Scalar). The spec'sNumericDisplay num(warehouse,0xe9,0x176,font,2,unsignedFormat,0,0xff); num.Draw(&localView,(Scalar)screenNumber)maps 1:1 to the rawFUN_004700bc(…,0xe9,0x176,font,2,0,0,0xff)+FUN_00470430(…)+ dtorFUN_0047018c(part_014.c:1310-1313).Draw(notDrawAt) 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)
-
Make allocation — switch to plain
new PrepEngrScreen(...)(dropoperator new(0x90)+placement-new). The reconstructedGraphicsView localViewis not byte-exact to the 19950x24..0x6Bwindow, sosizeof(PrepEngrScreen)may exceed 0x90 — a fixedoperator new(0x90)would then heap-overflow.new PrepEngrScreen(...)allocates the true size and matchesBackgroundBitmap::Make(L4GAUGE.cpp:2379); no external code reads this object at raw offsets, so the binary alloc size is not load-bearing. Keepstatic_assert(sizeof(PrepEngrScreen) <= 0x90)only as a compile-time canary. Optionally addRegister_Object(g);after construction for parity with BackgroundBitmap (debug leak-tracking only). -
§6 #4 UPGRADED from "unknown" to CONFIRMED FAITHFUL.
BTGetSubsystemAuxScreen(powersub.cpp:1333) appliesBTIsPoweredSubsystem=FUN_0041a1a4(…,0x50f4bc)— the exact filter the raw uses (part_014.c:1308) — and returnsauxScreenNumbervia the realPoweredSubsystemtype (+0x104 = binarysub+0x1DC). So folding the filter +auxScreen==screenNumbergate into the bridge is byte-faithful; the qualifying-subsystem set is identical. -
Acknowledge the dropped erase in the label block. The raw does 3 unresolved
localViewvtbl calls (+0x18/+0x24/+0x48, args not recovered by Ghidra) before thesub+0x224label draw (part_014.c:1323-1325) — a cell-erase/setup. The spec drops them and substitutesSetColor(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+0x224drawn-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-exactHeatSink(0x1D0) → resolved link must be a larger subclass; guarded raw read, best-effort.DrawStatusCellsC-NULL branchvtbl+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.