Three rounds of source inspection produced three wrong suspects; one
instrumented run settled it:
[pl] PilotList slot=0 at (180,225) <- the only drawer
(no [ps] lines at all)
So the magenta blocks over the top-left title are PilotList's slot-0 entry
(the local pilot's name art + mech icon) on the Comm port -- content that
SHOULD draw; what is wrong is the entry's coordinate convention inside that
port. The port geometry itself is proven right: the Comm background
(btcomm.pcx, the KILLS/DEATHS columns) lands pixel-identical in both exes.
Second, separate finding: PlayerStatus never draws at all. The cfg builds
8 of them (sec port, 4x2, players 1..8) but our roster resolve is 0-based
against 1-based cfg player numbers, so every panel stays unbound and its
Execute returns early. Fixing that will add a whole scoreboard page, so it
belongs in an A/B session against the shipped reference rather than a blind
edit -- both leads are written up in GAUGE-BLOCK.NOTES.md.
Also recorded: the capture rig's occlusion failure mode (CopyFromScreen
grabs whatever is in front -- one run scored 77%/47% purely because an
editor covered half the window) and the PrintWindow fix for unattended runs.
Verified build, zero faults; last valid A/B measurement stands at 94.7%
identical / 92% coverage.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77 KiB
The gauge block worksheet — re-hosting the 6 BT gauge TUs (prepped 2026-07-24)
Three-dossier workflow output, banked VERBATIM for the implementing session. Read RENDER-ROADMAP.NOTES.md first for the big picture. Donors: C:/VWE/BT411/game/reconstructed/ (btl4gaug/gau2/gau3/rdr/grnd/galm.*); targets: the authentic surviving BTL4RDR/GRND/GALM.HPP interfaces + fresh staged headers for gaug/gau2/gau3. Build order (authentic BTL4.MAK): btl4rdr btl4gaug btl4gau2 btl4gau3 btl4grnd btl4galm. Verification oracle: the SHIPPED exe's gauge output on the same GAUGE content (L4GAUGE=640x480x16, HEAPSIZE=15000000, GAUGE INI granularityInKB=64, AWE32 on).
=============================================================================
btl4gau2.cpp/.hpp + btl4gau3.cpp/.hpp — per-class decode survey
Files: C:\VWE\BT411\game\reconstructed\btl4gau2.hpp (585 ln) / btl4gau2.cpp (2265 ln) / btl4gau3.hpp (304 ln) / btl4gau3.cpp (1060 ln). TU extents: gau2 = @004c6798..@004c9b50 (+ VehicleSubSystems::Make @004cbaf0), gau3 = @004c9bd0..@004cbea0. Link order btl4gaug -> btl4gau2 -> btl4gau3 -> btl4grnd.
BTL4GAU2 — composite panel clusters
SeekVoltageGraph : GraphicGauge (vtbl 0051a1fc). ctor @004c6798 / dtor @004c68ac / BecameActive @004c6920 / Execute @004c6934 / ClearGraph @004c6be4 / DrawCursorBox @004c6c30 / DrawTicks @004c6c6c. Binds 4 attribute POINTERS via AttributePointerOf (the FUN_0041bfc0 shim over Simulation::GetAttributePointer, miss normalised NullAttribute->NULL): "CurrentSeekVoltageIndex"/"MinSeekVoltageIndex"/"MaxSeekVoltageIndex"/"SeekVoltage" — ALL PUBLISHED by our recon (emitter.hpp: seekVoltage[5]@0x1C4, Min@0x3F8/Max@0x3FC; myomers.hpp: seekVoltage@0x198 + the full Myomers attr list). Live cursor derefs liveVoltage (@0xA4) = OutputVoltage attr ptr (energy path) or MyomerCluster::seekValue cache. Response sampler = subsystem vtbl slot 15 via BTSeekVoltageSample bridge (Emitter @004bb42c sqrt(seekPower/2e8), Myomers @004b8f94 sqrt(speed*3.6/350)); destroyed test = BTSubsystemDestroyed (simulationState@0x40==1) bridge. Plot constants x87-recovered: 230/187 scale, 1200 step, 12000 limit, 1/12000 ld80 norm, 9999 redraw sentinel. Port shims: the two bridges + ctor-seeded previousVoltage/cursor/tick fields (binary leaves uninit) + BT_SEEK_LOG probes. Its Execute's full-box CLEAR is the shared Eng bit-plane's top-box eraser (Gitea #10 finding A).
ConfigMapGauge : GraphicGauge (vtbl 0051a1b8). @004c6d80 ctor / @004c6e54 dtor / @004c6ee0 LinkToEntity / @004c6ef4 BecameActive / @004c6f1c Execute. NOT attribute-bound: reads LBE4ControlsManager::buttonGroup[btn].GetMapState(dest, receiver, msgID, 0x10000) with the feed from BTSubsystemControlFeed (subsys+0xE8 &fireImpulse / +0xEC messageID). 4-slot table PE-recovered: {0x0d,0x45 Pinky}{0x25,0x46 ThumbLow}{0x3d,0x40 Trigger}{0x55,0x47 ThumbHigh}, blit x=0xc, state 0..3 -> cm_off/cm_other/cm_only/cm_both.pcc over btjoy.pcc. Key correction on record: @004c6ee0 is the GaugeBase::LinkToEntity vtbl-slot-9 OVERRIDE (armed by the viewpoint-bind broadcast, APP.cpp:1277 -> GAUGREND.cpp:3011), not an uncalled SetColor — the old "authentically dormant" claim is retired. Port fix in Execute: issue #42 doubled-joystick — MoveToAbsolute(0,0) restored before the base blit.
AnimatedSubsystemLamp / AnimatedSourceLamp (best-effort names) : OneOfSeveral. @004c70a4 / @004c7160 ctors, shared-shape dtors @004c7134/@004c71f0. Drive inherited selected@0xAC via file-private connections: CoolingLoopConnection (@004c3134 ctor/@004c31a0 sampler) -> BTCoolingLoopFrame (coolantAvailable@0x134 gate, linkedSinks "HeatSink" resolve, condenserNumber@0x1d4); PowerSourceConnection (@004c31ec/@004c3258) -> BTPowerSourceFrame (Generator::generatorNumber@0x1E0 via named voltageSource). Two applied port fixes documented: SHADOW-FIELD (redeclared selected shadowed the base -> lamps frozen at frame 0) and COLOR (ctor dropped bg=0xff/fg=0 -> black-on-black). Both connections are DATABINDING-RULE rewrites: the binary's ResolveLink(AttributePointerOf("InputVoltage"))/attr-id-3 walks died when BT_DEV_GAUGES audio attribute rows shifted the chained ids, so the shims pass the SUBSYSTEM and read named members in powersub.cpp/heat.cpp.
ScalarBarGauge : BarGraphBitMapScalar (WipeGaugeScalar : GraphicGauge; engine bar @00472ef0), vtbl 0051a0e4. Two ctors: @004c721c "slotOf" (GeneratorVoltageConnection @004c3288/@004c32f4 -> BTGeneratorVoltage = ResolveVoltageSource()->MeasuredVoltage(), Generator::outputVoltage@0x1DC) and @004c72ac plain-Scalar (GaugeConnectionDirectOf @00474855 from a direct Scalar*, the GeneratorCluster path). Shared dtor @004c733c. Deliberately declares NO value field — writes inherited currentValue@0xB4; a redeclaration would shadow/mis-align (stated in hpp:237-239). Overloads disambiguated Scalar* (exact) vs void* (std-conv) — passing a literal 0/NULL is ambiguous under both BC4.52 and modern resolution (BC4.52 hazard).
GeneratorCluster : GraphicGauge (vtbl 0051a0a0). Make @004c7368 / ctor @004c746c / dtor @004c7778. Registered config primitive "GeneratorCluster" (15 params; NO leading rate token — panel rate hard-wired 0xFFFF); resolves GeneratorA..D by FindSubsystem, warns "Subsystem not found". Binds by name: CurrentTemperature/DegradationTemperature/FailureTemperature (published, heat.hpp +0xE4/+0xE8/+0xEC family), CoolantMassLeakRate, OutputVoltage (Generator states published in powersub). Raw-offset residue: leak "third" = *(subsys+0x150), lamp source = subsys+0x1D4 (generatorOn) — both 1995-binary offsets on non-byte-exact compiled objects. 5 children: VertTwoPartBar, ScalarBarGauge(@004c72ac), BitMapInverseWipe, 2x TwoState. Port shims: BecameActive (paints background; the engine-base default self-inactivates) + no-op Execute (2007 engine base Gauge::Execute is Fail->abort, GAUGE.cpp:598 — the 1995 base was a no-op; "only the dtor overridden" decode corrected). Children NOT deleted in dtor (renderer owns; binary-faithful).
PrepEngrScreen : GraphicGaugeBackground (vtbl 0051a06c; paint-on-activation, no Execute). Make @004c7b30 / ctor @004c7bf0 / dtor @004c7d14 / BecameActive @004c7e48. Config "prepEngr" (mode, screen 1..12, font + 6 label .pcc's). Was a MISLABELED stub (mech/screenNumber SWAPPED, no methodDescription -> 12 CFG lines parse-skipped). Binds: mech roster walk (skip slot 0) + BTGetSubsystemAuxScreen bridge (auxScreenNumber/placement/label — replaces raw sub+0x1dc/0x1e0/0x1e4 which read garbage on our layout) + ClassID dispatch (0xBC3 Sensor / 0xBC6 Myomers / 0xBC8 Emitter / 0xBD4 PPC / 0xBCD Projectile / 0xBD0 Missile / 0xBCE Gauss unsupported). BEST-EFFORT residue: heat-sink number = resolved HeatSink link +0x1d4 (guarded), label bitmap via bridge label64 instead of sub+0x224. DrawStatusCells (FUN_004c79c8 shape) carries the binary's absolute cell rects; the C-cell compound blank is a marked plain-fill substitute.
SubsystemCluster (base of the family) : GraphicGauge (vtbl 0051a020). ctor @004c8140 / dtor @004c87dc / Execute @004c88e4 / GetDrawState @004c8990 / DrawPanelFrame @004c89c4 / ReleaseChildren @004c8820 / SetChildrenEnable @004c8a28. Base ctor hard-wires rate/mode 0xFFFF/0xFFFFFFFF; mfd_mode/eng_mode are the two bit-plane child masks. Binds by name: InputVoltage/HeatSink/CurrentTemperature/DegradationTemperature/FailureTemperature/CoolantMassLeakRate (temps published). 11 children incl. HorizTwoPartBar, 2x cooling lamps, 2x bus lamps (gated on InputVoltage presence), ScalarBarGauge evolt 0..12000, OneOfSeveralInt btecmode (RAW subsys+0x134, best-effort), OneOfSeveralStates btemode (RAW +0x2b8, generator-only via BTIsPoweredSubsystem shim for IsDerivedFrom 0x50f4bc), 2x VertTwoPartBar (second off the ResolveLink'd HeatSink), LeakGauge (RAW +0x150 third). POLARITY CORRECTION (PPC-dial fix take 3): *(subsys+0x40)==1 means FAILED; failedState now derived from BTSubsystemDamageLevelOf(subsystem) >= 1.0 (truth source DistributeCriticalHit, mechdmg.cpp). GetDrawState: 1 = dead-panel; the binary's secondary *(subsystem+0x278)!=4 condition is UNIDENTIFIED [T4] and dropped. BecameActive is a port addition (previousDrawState=-1 force repaint).
HeatSinkCluster : SubsystemCluster (vtbl 00519fd4). ctor @004c8a6c / dtor @004c8d18 / Execute @004c8db0. Adds 4 TwoStates + NumericDisplayScalar (helv18) reading heatLoadScaled = heatLoad * 100.0 (_DAT_004c8df0). Bindings: heatFailLamp <- own failedState; hsFailLamp <- failFlag = (failSubsystem("HUD")+0x40 == 1) RAW best-effort; powerFail RAW subsys+0x324; structFail RAW subsys+0x320; heatLoad connection RAW subsys+0x31c — all four raw offsets are 1995 layout on compiled objects (unbridged; hazard class identical to the fixed CoolingLoop/AFC100 bugs).
MyomerCluster : SubsystemCluster (vtbl 00519f88). ctor @004c8df4 / dtor @004c8f54, Execute = base. Adds SeekVoltageGraph ("EngrGraph", edestryd.pcc, draw_cursor=False) fed by a GeneratorVoltageConnection into seekValue@0xCC, plus seek-step OneOfSeveralInt bteseek — the raw +0x800 was OOB for a 0x358 Myomers; re-pointed at Myomers::currentSeekVoltageIndex@0x320 (INFERRED — @004c8df4 not in the assert-anchored export).
WeaponCluster : SubsystemCluster (vtbl 00519f38). ctor @004c8fc4 / dtor @004c91d4 / BecameActive @004c9258 / Execute @004c9290 / virtual DrawWarningLamp @004c932c. Binds "PercentDone" — PUBLISHED: mechweap.hpp PercentDoneAttributeID = 0x12 @0x320 rechargeLevel (TriggerState 0x13 @0x31C also published, feeding ConfigMapGauge's fireImpulse path). Children: SegmentArc270 recharge dial 0..360 + ConfigMapGauge; warning lamp toggles at percentDone > 0.99 (_DAT_004c92e0), gated to draw-state 0 (alive). Port shims: PPC-dial repaint fix (base black-fill wipes the arc's incremental draw memory -> re-BecameActive arc/configMap/lamp on draw-state flips), diagWeaponName diagnostic member (APPENDED field — offset-layout deviation, plain-new only), BT_PANEL_LOG 61-prime probe (60 aliases on 6-panel Avatars).
EnergyWeaponCluster : WeaponCluster (vtbl 00519ee8). ctor @004c93b0 / dtor @004c94dc. Adds SeekVoltageGraph (draw_cursor=True) on the "OutputVoltage" attr ptr — note the port-extra 0x1D "OutputVoltage" MechWeapon alias is RETIRED (mechweap.hpp:208-212); Emitter publishes its authentic 0x1D-0x25 chain (currentLevel@0x414 raw volts). Seek-step OneOfSeveralInt RAW subsys+0x3f0 (Emitter::seekVoltageIndex, best-effort).
BallisticWeaponCluster : WeaponCluster (vtbl 00519e98). ctor @004c9558 / dtor @004c9940 / BecameActive @004c99cc / Execute @004c9a38 / TestInstance @004c9adc / DrawWarningLamp @004c9b50 (override: base dot + ammo-numeric SetColors swap for the stencil look — the port originally had only the base). Bindings: ammo count via BTWeaponAmmoBin + BTAmmoBinCountPtr bridges (Streak/AFC100 fixes; old raw bin+0x180 was garbage), feed state via BTAmmoBinFeeding (was raw bin+0x18c). REMAINING RAW HAZARDS: Execute reads jammed = *(subsys+0x364)==5 "weaponAlarm Jammed", but our recon PUBLISHES WeaponState 0x1C @0x350 weaponAlarm — offset disagreement AND an unbridged raw read; TestInstance still walks raw ResolveLink(subsys+0x43c) + *(ammoBin+0x180) despite the ctor's bridge fixes. Bring-up NULLs: ammoErase (GraphicsViewRecord @0044ad78 engine-internal) and ejectWipe (BitMapInverseWipeScalar @004c61c8 not yet reconstructed in btl4gaug); reload elapsed-seconds left at base (frame clock DAT_0052140c unwired).
VehicleSubSystems (no instance) : static Make @004cbaf0, config primitive "vehicleSubSystems" (8 typeString strip bitmaps). Walks the roster (skip slot 0), filters/reads aux screen via BTGetSubsystemAuxScreen, dispatches on ClassID: 0xBC3 Sensor->HeatSinkCluster(+"HUD" subsystem), 0xBC6->MyomerCluster, 0xBC8/0xBD4->EnergyWeaponCluster (cluster image picked by RAW *(sub+0x334)==0 ? qcircle : qcircr), 0xBCD/0xBD0->BallisticWeaponCluster, 0xBCE Gauss warned-unsupported (authentic). kAuxGeom = the 12 PE-recovered geometry rows (@0x51bf34 in the binary). SubsystemDisplayFilter is a permissive True stub for FUN_0041a1a4.
BTL4GAU3 — multiplayer / tactical HUD
PlayerStatusMappingGroup : NOT a Gauge — plain 28-element group of ColorMapperArmor (engine ctor @004c3b98, 0x70 alloc each). build @004c9bd0 / destroy @004c9ca8 / Execute-all @004c9cf4 / SetColor-all @004c9d18. Zone table = the 28 dz_* names (PTR_s_dz_door_0051a240), resolved per-mech by GetDamageZoneIndex (FUN_0042076c, -1 => inert); palettes adpal.pcc/adpal2.pcc; base colour index increments per zone.
SectorDisplay : GraphicGauge (vtbl 0051beec, sizeof 0xC4 = Make alloc, static_assert overflow-lock only). Make @004c9d44 / ctor @004c9e10 / dtor @004c9f94 / BecameActive @004ca038 / LinkToEntity @004ca068 (slot 9) / Execute @004ca07c. Config "sectorDisplay" (rate, mode, image, color, okColor); was PROSE-ONLY/parse-skipped, now byte-verified. Binds the LinkToEntity'd subject's localOrigin.linearPosition: sectorA = Round(-Z0.01)+500, sectorB = Round(X0.01)+500 into two 3-digit NumericDisplays; one-shot grid-cell blit (cellWidth = imageW/14, cells 12..13). Port shims: durable gridImage name copy (config string is transient), viewpoint-entity fallback when the LinkToEntity broadcast is unwired (marked; binary gates purely on subject!=0), BT_SECTOR_LOG.
TeamStatusDisplay : GraphicGauge (vtbl 0051bea8) — STILL PROSE-ONLY in the .cpp (header decl + @ADDR comment block @004ca208 ctor / @004ca39c dtor / @004ca424 SetEnable / @004ca438 BecameActive / @004ca478 Execute; no bodies, no methodDescription). Documented bindings: "Players" node (FindByName @00403ad0), slot by entity+0x1cc player index, kills from entity+0x1c8 (our recon publishes killCount in btplayer), rank/team entity+0x204 via FUN_0044fc10, spectator bit +0x29&0x40, local player via App+0x6c mech ->+0x190 ->+0x1cc. 8 rows of NumericDisplay pairs.
PilotList : GraphicGauge (vtbl 0051be64, sizeof 0x178). Make @004ca90c / ctor @004ca958 / dtor @004cab10 / BecameActive @004caba4 / Execute @004cabd0 / DrawMechIcon @004cad70 (DrawAmmoCount @004cae20 documented in hpp). Config "pilotList" (rate, mode, font) — the Comm KILLS/DEATHS roster; layout from the PE-parsed DAT_0051af88 8x(x,y,mode) table (Make position UNUSED). One slot per frame round-robin. Bindings ALL via compiled-member bridges (the gauge scoring wave databinding fix): BTResolveRosterPilot (mechmppr.cpp, ControlsMapper roster), BTPilotKills = killCount, BTPilotDeaths = Player::deathCount (NOT the binary's dead pad_0x280), BTPilotIsSelected (replaces the raw local+0x284/tgt+0x190 walk that AV'd under the SEH guard = the silent empty scoreboard). Residue: DrawMechIcon still derefs RAW pilot+0x1e0 nameId (harmless — feeds the always-NULL LookupPlayerNameBitmap stub, so the tinted-box branch runs). Byte-faithful quirk kept: e.resolvedMech never latched on the occupied path -> icon redraws every occupied frame.
PlayerStatus : GraphicGauge (vtbl 0051be20, 200-byte alloc). Make @004cae90 / ctor @004cb1a8 / dtor @004cb28c / BecameActive @004cb310 / Execute @004cb358. Config "PlayerStatus" (rate, mode, playerNumber 1..8, font, color, okColor, aliveColor, deadColor); Make rewired to parameterList + port/x/y bug fixed (binary-side Make passed entity/renderer as x/y). Bindings: player resolve via GetMissionPlayer (playerIndex==0 ONLY — players 2..8 never resolve in the port; binary walked the App+0x24 "Players" dictionary), then RAW offsets on the player: +0x1FC vehicle, +0x1C8 score (read as Scalar), +0x1C4 alive/dead. Our recon publishes currentScore/killCount in btplayer — these raw reads are the same unbridged hazard class PilotList already fixed (mitigated only by the BT_DEV_GAUGES guarded Execute). CreateMutantPixelmap8 @004cafac is a marked BRING-UP STUB (recoloredMech stays NULL -> outline blit skipped; DynamicMemoryStream read API + pixel copy unmapped; ReadStreamString @004caf50 deferred with it); nameImage cache (App+0xC8) unwired -> filled-box branch; mappingGroup rebuilt per player change (dtor deletes it — fixes a binary leak); disabled-state refresh hardcoded 0 (Mech::IsDisabled needs a Mech* cast, mech.hpp collides in this TU).
MessageBoard : GraphicGauge (vtbl 0051bddc, sizeof 0xA4 = Make alloc; layout-check friend struct). Make @004cb678 / ctor @004cb704 / dtor @004cb788 / BecameActive @004cb7fc / SetSource @004cb818 (no recovered caller) / Execute @004cb82c. Config "messageBoard" (rate, mode, btsmsgs.pcx, color). Strip cell math: cellX=(id&3)<<7, cellY=(id>>2)<<5, 0x80x0x20 cells, sender name cell at x=128, colour 7 (FUN_004c2f88). Field-order FIXes recorded: trackedMech@0x90 (was "int enabled"), previousNameId@0x9C=-1 / previousMessageId@0xA0=-2 (were swapped). Port deviations: FEED LIVE lazy viewpoint bind (binary's SetSource never called -> board authentically empty), message resolve via BTResolveMessageBoard bridge — currently a NULL stub in btplayer.cpp:1557 (StatusMessagePool is a NULL stub, BTPlayer+0x1dc never populated), so the board stays empty; previousNameId tracks the resolved BITMAP pointer instead of the binary's entity pointer (same change granularity).
Published-attribute cross-map (gauge reads -> what the recon already publishes)
- PercentDone 0x12 @0x320 rechargeLevel, TriggerState 0x13 @0x31C fireImpulse, WeaponState 0x1C @0x350 weaponAlarm — mechweap.hpp:196-212; 0x1D is NextAttributeID (the old port-extra "OutputVoltage" 0x1D alias RETIRED; Emitter chains authentic 0x1D-0x25 incl. currentLevel@0x414). WeaponCluster's PercentDone and ConfigMapGauge's control feed land on published members; BallisticWeaponCluster's jam test does NOT (raw +0x364 vs published weaponAlarm@0x350).
- Seek family (CurrentSeekVoltageIndex/Min/Max/SeekVoltage/OutputVoltage) — published on Emitter (emitter.hpp) and Myomers (myomers.hpp); SeekVoltageGraph binds all of them by name.
- HeatSink temps (CurrentTemperature/DegradationTemperature@0xE8/FailureTemperature@0xEC, "HeatSink" link) — published in heat.hpp/heat.cpp; BTCoolingLoopFrame reads named linkedSinks/condenserNumber.
- Generator states (outputVoltage/generatorNumber via ResolveVoltageSource/MeasuredVoltage) — powersub.cpp bridges BTGeneratorVoltage/BTPowerSourceFrame/BTIsPoweredSubsystem/BTGetSubsystemAuxScreen.
- killCount/deathCount/currentScore — btplayer.cpp bridges BTPilotKills:1479/BTPilotDeaths:1493; PilotList uses them; PlayerStatus and (prose) TeamStatusDisplay still read raw +0x1C8.
BC4.52 hazards (things that will not survive the authentic Borland 4.52 build)
- static_assert (C++11) — btl4gau3.hpp:134 (SectorDisplay) and btl4gau3.cpp MessageBoardLayoutCheck: must become compile-time array tricks or be dropped (they are 2007-base-relative anyway).
boolkeyword — btl4gau2.cpp:152/167 extern decls (BTGetSubsystemAuxScreen, BTIsPoweredSubsystem): BC4.52 predates bool; the surrounding code correctly uses Logical/True/False.- std::flush / namespaced iostream in every env-gated probe (BT_SEEK_LOG/BT_TEMPBAR_LOG/BT_PANEL_LOG/BT_VSS_LOG/BT_SECTOR_LOG/BT_SCORE_LOG/BT_PS_LOG) — port-only diagnostics; strip for a 4.52 build.
- ScalarBarGauge ctor overload pair (void* vs Scalar* 15th arg): a literal 0/NULL argument is ambiguous; all current call sites pass typed pointers (safe).
- Placement new in all the Make()s + PlayerStatusMappingGroup — needs <new.h> under BC4.52 (supported).
- Old for-scope: no same-name redeclaration collisions present (checked every multi-loop function; variables differ or live in separate functions).
- WeaponCluster::diagWeaponName is an APPENDED member (port diagnostic) — changes sizeof vs the binary's 0xE8-family layouts; benign only because clusters are plain-new (no operator new(size) alloc locks), flagged in the header.
- (int)(size_t) pointer truncation in MessageBoard::Execute / PilotList — fine on 32-bit BC4.52 (size_t via stddef.h), flag for any 64-bit target.
Load-bearing quotes
btl4gau2.cpp:2048 — jammed = (*(int *)((char *)subsystem + 0x364) == 5); // BEST-EFFORT raw (weaponAlarm==Jammed) [conflicts with mechweap.hpp:207 'WeaponStateAttributeID, // 0x1C @0x350 weaponAlarm (binary table END)']
mechweap.hpp:196-197 — PercentDoneAttributeID = 0x12, // @0x320 rechargeLevel / TriggerStateAttributeID, // 0x13 @0x31C (PINNED -- the fire-button binding)
mechweap.hpp:208 — issue #11: the 0x1D \"OutputVoltage\" PORT-EXTRA alias is RETIRED --
btl4gau2.cpp:1580-1582 — the binary's secondary condition (*(subsystem+0x278) != 4 -> 1) remains unidentified [T4] -- a healthy subsystem reads ALIVE here until that field is decoded (open-questions).
btl4gau2.hpp:237-239 — Its value slot @0xB4 (this[0x2D]) IS the inherited WipeGaugeScalar::currentValue -- the connection writes it, so NO own field is declared (declaring one would shadow / mis-align).
btl4gau2.cpp:2093 — if (ammoBin == NULL || *(int *)((char *)ammoBin + 0x180) == 0) [TestInstance still raw despite the ctor's BTAmmoBinCountPtr bridge fix]
btl4gau3.cpp:801 — scoreDisplay->Draw(&localView, *(Scalar *)((char *)player + 0x1C8)); // FUN_00470430 [raw player offset; btplayer publishes currentScore/killCount]
btl4gau3.hpp:134 — static_assert(sizeof(SectorDisplay) <= 0xC4, \"SectorDisplay must fit its 0xC4 Make alloc\"); [C++11, not BC4.52]
btl4gau2.cpp:152 — extern bool BTGetSubsystemAuxScreen(Subsystem *sub, int *screen, int *placement, char *label64); [bool keyword = BC4.52 hazard]
btl4gau2.hpp:165-170 — @004c6ee0 -- the GaugeBase::LinkToEntity OVERRIDE (vtbl slot 9, +0x24), NOT a \"SetColor\" ... (The old \"SetColor, no callers -> authentically dormant\" claim was a mislabel.)
btl4gau2.hpp:304-306 — the engine base Gauge::Execute is Fail(\"not overridden\")->abort (GAUGE.cpp:598) ... (The decode's \"only the dtor is overridden\" was wrong: the 1995 base Gauge::Execute was a no-op, the 2007 engine's aborts.)
btl4gau3.cpp:614-618 — CreateMutantPixelmap8 BRING-UP STUB: 'With this stub recoloredMech stays NULL -> Execute skips the outline blit; the score, name box, and alive/dead status box still render.'
btl4gau3.cpp:998-999 — trackedMech stays NULL and Execute is a safe no-op == the DEFERRED empty board [BTResolveMessageBoard = NULL stub at btplayer.cpp:1557]
btl4gau2.cpp:1751-1755 — 0x800 was OOB for a 0x358 Myomers (garbage) -> the seek gear step is Myomers::currentSeekVoltageIndex@0x320 ... INFERRED (the MyomerCluster ctor @004c8df4 isn't in the assert-anchored export).
Open questions
- SubsystemCluster::GetDrawState [T4]: the binary's secondary condition *(subsystem+0x278) != 4 is unidentified and currently dropped (failedState alone decides) — decode the +0x278 field (btl4gau2.cpp:1574-1583).
- BallisticWeaponCluster jam test reads raw *(subsys+0x364)==5 while mechweap.hpp publishes weaponAlarm as WeaponState 0x1C @0x350 — reconcile the offset (ProjectileWeapon-specific alarm vs base?) and route through the published attribute or a bridge; same class of layout bug as the fixed AFC100/Streak counters.
- BallisticWeaponCluster::TestInstance and Execute still walk raw ResolveLink(subsys+0x43c) and *(ammoBin+0x180) despite the ctor's BTWeaponAmmoBin/BTAmmoBinCountPtr bridge fixes — unify on the bridges?
- HeatSinkCluster raw offsets unbridged: heatLoad subsys+0x31c, powerFail +0x324, structFail +0x320, failSubsystem(+HUD)+0x40 — all 1995-layout reads on compiled objects (BEST-EFFORT flagged); candidates for the next databinding wave.
- PlayerStatus raw player offsets (+0x1FC vehicle, +0x1C8 score-as-Scalar, +0x1C4 alive/dead) vs the published btplayer members (currentScore/killCount) — PilotList already migrated to bridges; PlayerStatus has not. Also players 2..8 never resolve (GetMissionPlayer only serves playerIndex 0) — is the multi-player name-tag wanted in the port?
- TeamStatusDisplay is still prose-only (no ctor bodies, no methodDescription, no config keyword registered) — build it or record it as intentionally deferred?
- MyomerCluster seek-step source Myomers::currentSeekVoltageIndex@0x320 is INFERRED, not assert-anchored (@004c8df4 missing from the export) — verify against a live myomer eng page.
- Bring-up NULLs still open: BallisticWeaponCluster ammoErase (GraphicsViewRecord @0044ad78) and ejectWipe (BitMapInverseWipeScalar @004c61c8 not yet in btl4gaug); reload elapsed-seconds frame clock (DAT_0052140c) unwired; CreateMutantPixelmap8 + App+0xC8 name-bitmap cache (PlayerStatus outline/name and PilotList name icons draw the fallback boxes); MessageBoard feed dead until StatusMessagePool (btstubs.cpp:62) is real.
- ConfigMapGauge GetMapState mode constant 0x10000 ('ModeNonMapping -- the persistent groups') — stated but not marked verified against the LBE4 controls manager; confirm on a live trigger-config check.
- SubsystemCluster modeLamp (subsys+0x134) and stateLamp (subsys+0x2b8) remain BEST-EFFORT raw offsets on non-byte-exact objects — same bridge treatment as CoolingLoop/PowerSource pending.
=============================================================================
1. Donor -> authentic-header method mapping
The donor headers in C:\VWE\BT411\game\reconstructed are already verbatim copies of the surviving TeslaRel410 headers, so re-hosting is near-1:1; the deltas are listed per TU.
btl4rdr (MapDisplay radar) — donor C:\VWE\BT411\game\reconstructed\btl4rdr.cpp vs authentic C:\VWE\TeslaRel410\CODE\BT\BT_L4\BTL4RDR.HPP
Every class method in the donor matches the authentic header signature EXACTLY (donor btl4rdr.hpp == authentic BTL4RDR.HPP byte-for-byte in declarations; member offsets confirmed against the shipped object, offsetPosition@0x2D0..shadowTable@0x3C4):
- MapName::ExtractFromEntity(Entity*, AffineMatrix&, Entity* exclude=NULL, Entity* hotbox=NULL) @004c19fc
- MapName::Draw(GraphicsView*, int hotbox_color) @004c1ab0
- MapDisplay::methodDescription ("map") + static Make(int, Vector2DOf, Entity*, GaugeRenderer*) — NOT in decomp (ctor had no exported caller); reconstructed from L4GAUGE.CFG:4923 invocation
- MapDisplay::MapDisplay(GaugeRate, ModeMask, L4GaugeRenderer*, unsigned int owner_ID, int graphics_port_number, int left,bottom,right,top, OffsetPosition, int view_width_in_degrees, int bg/static/hotbox color, Scalar maximum_range, Entity*, Scalar* scale, Point3D** position, Quaternion** angle, Scalar* capabilities, Logical allow_rock_and_roll=False) @004c1c24, vtable 0051422c
- ~MapDisplay @004c1ea8; TestInstance @004c1f30; ShowInstance @004c1f40 (prints "ThreatIndicator:"); BecameActive @004c1fc0; Execute @004c1fd0 (4-phase amortized); CalculateBounds @004c2178 (assert path "d:\tesla\bt\bt_l4\BTL4RDR.CPP" line 0x224 CONFIRMS the TU); EraseDisplay @004c2294; DrawDisplay @004c22c4; BuildRadarShadow @004c228c (EMPTY in shipped binary — shadowTable allocated but never populated); DrawViewWedge @004c2484; DrawStatic @004c25b4; DrawMoving @004c2788; DrawNames @004c28c4 (assert line 0x408); helper MapBlipColor @004c2f88 (returns literal 7).
Donor-only material with NO header impact (file-static adaptation layer): Pip/PipTable facades over L4GaugeImage / L4Warehouse::gaugeImageBin (pip Draw = FUN_0046f0c0, bin Get = FUN_004688ce, keyed by Entity::resourceID +0x1BC, resource type 0x12), EntityPosition/EntityTransform, ResolveOperatorEntity (FUN_00417ab4, renderer+0x40, falls back to application viewpoint), GetPipTable (renderer+0x4C warehousePointer), LookUpNameBitmap (Mission::GetSmallNameBitmap via playerBitmapIndex +0x1E0), GetNameID/IsLabelledEntity (Landmark landmarkID +0x21C — dormant, no shipped content), GetTarget (BTPlayer::objectiveMech +0x284 bridge), FindSubObject (FUN_0041f98c roster walk for "Torso"), GetHorizontalRotation (Torso::currentTwist @+0x1D8 via BTGetTorsoTwistAddr bridge).
Donor PORT additions to strip or #ifdef when re-hosting to the authentic header/1995 semantics: renderer->RebuildEntityGrid() call in Execute phase 0 (RebuildEntityGrid itself is a port-added GaugeRenderer method — NOT in authentic GAUGREND.HPP); the three "VISIBLE-RADIUS cull" blocks in DrawStatic/DrawMoving/DrawNames; the fallback mech cross-blip branch in DrawMoving (classID 0xBB9, hand-rolled yaw projection); the YawPitchRoll-instead-of-EulerAngles heading fix (YawPitchRoll DOES exist in the authentic engine — restoration\source410\MUNGA\ROTATION.HPP:162 with operator=(const Quaternion&) at :190 — so this fix survives re-hosting); all getenv("BT_MAP_LOG"/"BT_RADAR_LOG") + GetTickCount debug blocks; the two transcription fixes (rotation-only assignment then translation row LAST; worldToView.Invert(view) is a true affine inverse FUN_0040b244) are CORRECTNESS-load-bearing and must stay.
btl4grnd (BTL4GaugeRenderer) — donor btl4grnd.cpp/.hpp vs authentic C:\VWE\TeslaRel410\CODE\BT\BT_L4\BTL4GRND.HPP
Authentic header declares exactly 5 members: ctor, dtor, TestInstance() const, NotifyOfNewInterestingEntity(Entity*), NotifyOfBecomingUninterestingEntity(Entity*). No data members (all state inherited: warehousePointer this[0x13]/@0x4C, lampManager this[0x7033], movingEntities @0x94, staticEntities @0xB0). Donor matches all 5:
- ctor @004cbea0 (vtable 0051cebc): lamp manager (BT lamp-name vtable 0051ceb0), L4Warehouse (FUN_00468778), BuildConfigurationFile("gauge\l4gauge.cfg" @0051ce9e, BTL4MethodDescription @0051c910)
- dtor @004cbf40: Remove(0) all gauges BEFORE deleting warehouse; TestInstance @004cbf90; NotifyOfNewInterestingEntity @004cbfa0 (vtbl slot 9); NotifyOfBecomingUninterestingEntity @004cc028 (slot 10).
DELTA 1: donor adds TearDownForViewpointRestream() (port fix Gitea #12) — NOT in the authentic header; re-hosting onto the exact 1995 header requires dropping it or carrying it as an explicit annotated header delta.
DELTA 2: donor base-ctor call
L4GaugeRenderer(false, NULL, NULL, NULL)andnew L4LampManager(controls_manager)are modernized-engine signatures; the shipped binary called both no-arg (FUN_0046f97c(this) / FUN_00448c54(obj)). Authentic re-host = no-arg ctors.
btl4galm (BTL4GaugeAlarmManager) — donor btl4galm.cpp/.hpp vs authentic C:\VWE\TeslaRel410\CODE\BT\BT_L4\BTL4GALM.HPP
Signatures match exactly: CreateGaugeAlarmStreamItem(MemoryStream*, const char* alarm_name, const char* alarm_data) -> Logical; ReadGaugeAlarmStreamItem(GaugeAlarm*, Entity*, Subsystem*, Enumeration, MemoryStream*) -> void. CRITICAL provenance: NO distinct override body exists in BTL4OPT.EXE — the only GaugeAlarmManager code is the MUNGA base (trap virtuals CreateGaugeAlarmStreamItem @00448ab8 / ReadGaugeAlarmStreamItem @00448ad4, both assert "not overridden!"; drivers CreateGaugeAlarmStream @00448af0 / BuildAlarmsForModel @00448d00; GaugeAlarm ctor @004489e8, 0x28 bytes, vtable PTR_FUN_004eed7c). No subclass vtable in vtables.tsv, no constructor caller (base ctors @00448ebd/@00448edc/@00448efb externally uncalled), and the linker region reserved for btl4galm.obj (@004cc40c..@004cdac0) holds a PNAME1..8.bgf HUD-model builder instead. The donor bodies (alarm-type names "threshold"/"range"/"state"/"flash") are BEST-EFFORT, low confidence. gaugeAlarmManager is NULL'd in GaugeRenderer ctor (GAUGREND.CPP:2853) and nothing in the BT boot sets it — the alarm subsystem was DORMANT in shipped BT 4.10; StartEntityAlarmImplementation (GAUGREND.CPP:3214) would Check-fail if ever activated.
2. GaugeEntityList / GAUGMAP radar query API (C:\VWE\TeslaRel410\restoration\source410\MUNGA\GAUGMAP.CPP + C:\VWE\BT411\engine\MUNGA\GAUGMAP.h)
Two collections:
- GaugeEntityList (Node-derived, wraps ChainOf<Entity*> instanceList): GaugeEntityList(); ~(); TestInstance(); Add(Entity*); Remove(Entity*); Clear(); NumberOfItems(); CopyEntities(GaugeEntityList* dest, Derivation* = NULL); CopyEntitiesWithinBounds(dest, Scalar min_x,min_y,min_z,max_x,max_y,max_z, Derivation* = NULL); ChainOf<Entity*>* const GetInstanceList() — the accessor MapDisplay iterates with ChainIteratorOf<Entity*>.
- GaugeEntityArray (the spatial index): 64x64 grid of GaugeEntityList (gaugeMapArraySize=64) over FULL_EXTENT 32767 m; ArrayOffset(n) = int(64*(n/16383.5)+.5)+32, clamped; Add/Remove hash on entity->localOrigin.linearPosition (x,z); tracks min/max extents; CopyEntities / CopyEntitiesWithinBounds(dest, 6 bounds, Derivation*) / GetBounds / PrintStatistics. Cell search range pads by LARGEST_RADIUS=500.0 ("HACK - this is a guess").
AUTHENTIC QUIRKS the MapDisplay must (and does) compensate for:
(a) GaugeEntityList::CopyEntitiesWithinBounds's per-entity box test is COMMENTED OUT in the authentic source (GAUGMAP.CPP:323-337) — it accepts EVERY entity in the cell; min_y/max_y parameter names are commented out (/min_y/). Real filtering is grid-cell-granular only.
(b) Both grid scan loops are EXCLUSIVE (z<high_z, x<high_x) — the topmost row/column of candidate cells is never searched (authentic off-by-one, present in both CopyEntities and CopyEntitiesWithinBounds).
(c) The renderer facade (restoration\source410\MUNGA\GAUGREND.HPP:524-599, inline): GetMovingEntities/GetMovingEntitiesWithinBounds delegate to GaugeEntityList movingEntities (a FLAT list — so moving-entity bounds args are effectively decorative given (a)), GetStaticEntities/GetStaticEntitiesWithinBounds to GaugeEntityArray staticEntities (the grid), GetStaticEntityBounds to staticEntities.GetBounds. Members declared at GAUGREND.HPP:689-690.
Hence MapDisplay's own maximumDistanceSquared cull in DrawStatic/DrawMoving is the REAL range filter. File-scope helper approximateDistance (Graphics Gems p.432, 13% error) also lives in GAUGMAP.CPP.
3. BTL4GaugeRenderer "LoadMission" + interesting-entity handling
There is NO LoadMission override on BTL4GaugeRenderer (surviving header proves it). The chain is: framework Renderer::LoadMission -> L4GaugeRenderer::LoadMissionImplementation (C:\VWE\BT411\engine\MUNGA_L4\L4GREND.cpp:284 — just chains to base) -> GaugeRenderer::LoadMissionImplementation (restoration\source410\MUNGA\GAUGREND.CPP:3256 — EMPTY, a Tell() stub with commented-out list statistics). Mission load populates NOTHING in the radar lists.
The actual feed is interesting-entity notification:
- BTL4GaugeRenderer::NotifyOfNewInterestingEntity @004cbfa0: SearchList(entity->GetResourceID(), ResourceDescription::GaugeImageStreamResourceType 0x12) — if the entity has NO type-0x12 gauge-image (pip) resource, discard; else IsDerivedFrom(Mover 0x4e4518) -> movingEntities.Add (FUN_004434f4, +0x94), else IsDerivedFrom(Terrain 0x4e6ef8) -> staticEntities.Add (FUN_00443864, +0xB0); ALWAYS chains to L4GaugeRenderer base (FUN_0046fd6c), which reaches GaugeRenderer::NotifyOfNewInterestingEntity (GAUGREND.CPP:3086) — that base forwards to every gauge in newList/activeList/inactiveList (when not suspended) and documents that list maintenance "has been moved up to the game-specific level... all descendents MUST chain back".
- NotifyOfBecomingUninterestingEntity @004cc028 is the exact mirror (Remove FUN_00443510/FUN_00443930). PORT NOTE: the WinTesla port dropped ExecuteImplementation's InterestingEntity feed, so the port added GaugeRenderer::RebuildEntityGrid (GAUGREND.CPP:3623, decl GAUGREND.HPP:584) which repopulates both lists per frame from HostManager Dynamic Master + Replicant iterators, sorting by Mover derivation; MapDisplay::Execute phase 0 calls it. On a faithful re-host to the authentic notification feed, both the method and the call site go away.
4. Gauge boot sequence + interpreter opcode->class registry
Boot order (mixed authentic/port evidence):
- BTL4Application::MakeGaugeRenderer ->
new BTL4GaugeRenderer(C:\VWE\BT411\game\reconstructed\btl4app.cpp:385; alloc FUN_00402298(0x1c53c) -> ctor FUN_004cbea0). - GaugeRenderer base ctor (GAUGREND.CPP:2797): registers movingEntities/staticEntities, creates the GaugeInterpreter (:2848), gaugeAlarmManager = NULL (:2853), warehousePointer = NULL.
- BTL4GaugeRenderer ctor @004cbea0: new L4LampManager (BT lamp-name vtable 0051ceb0, this[0x7033]) -> new L4Warehouse (this[0x13]) -> BuildConfigurationFile("gauge\l4gauge.cfg", BTL4MethodDescription).
- GaugeRenderer::BuildConfigurationFile (GAUGREND.CPP:4288): interpreter->Initialize(file, method_list, warehousePointer), then Configure("Initialization", NULL) — the cfg's
Initialization { }procedure runs immediately (ports/global gauges), with entity==NULL. - GaugeInterpreter::Initialize (GAUGREND.CPP:1086): flattens the CHAINED MethodDescription lists (a name==NULL entry's parameterList[0].data.nextMethodList jumps to the next table — METHOD_DESCRIPTION_CHAIN macro, GAUGREND.HPP:352) into primitiveTable[primitiveCount]; parses the ENTIRE cfg to bytecode: each
<label> { body }-> symbolTable->Add(label, currentOffset); GetProcedureBody emits built-in opcodes or primitives. - Opcode registry: GaugeInterpreterCommand is a TYPEDEF'D UNSIGNED CHAR (GAUGREND.HPP:58). Built-ins (GAUGREND.HPP:209-220): endMarker=0, returnCommand, setPortIndexCommand, addOffsetCommand, callCommand, enableCommand, disableCommand, attributeVariableCommand, nextAvailableCommand(=8). ParsePrimitive (GAUGREND.CPP:1418) assigns each cfg keyword opcode = nextAvailableCommand + (index of the matching MethodDescription in the flattened chain, matched by stricmp on ->name), then Save()s each ParameterDescription after the opcode byte. Dispatch (InterpretFromOffset, GAUGREND.CPP:1696): default case computes command -= nextAvailableCommand and calls primitiveTable[command]->Execute(display_port_index, position, entity, renderer); MethodDescription::Execute (GAUGREND.CPP:2704) Restore()s every parameter then calls the registered static
execute= the gauge class's Make — which news the class instance bound toentity. ParameterDescription types (GAUGREND.HPP:259-271): typeEmpty, typeRate, typeModeMask, typeInteger, typeColor, typeScalar, typeVector, typeRectangle, typeAttribute, typeString; MaximumParameters=16. - Per-mech build: BTL4Application::MakeViewpointEntity (AUTHENTIC survivor C:\VWE\TeslaRel410\restoration\source410\BT_L4\BTL4APP.CPP:451) -> GetGaugeRenderer()->ConfigureForModel("Init", viewing_entity) -> L4GaugeRenderer::ConfigureForModel (L4GREND.cpp:759): full label = mission->GetGameModel() + "Init" (e.g. "MadcatInit") -> Configure(full_name, entity) -> Interpret. L4GAUGE.CFG confirms per-model labels (BlkjackInit:46, MadcatInit:2562...) that each
callthe shared MechInit procedure; the map( ) primitive at L4GAUGE.CFG:~4923 supplies MapDisplay's 13 parameters (A, ModeAlwaysActive, (366,367), center, 180, colors 0/3/1, 4000 m, RadarRange, RadarLinearPosition, RadarAngularPosition, Avionics/RadarPercent). - TU->class registry (BTL4MethodDescription, .data @0051c910; port table btl4grnd.cpp:125-155): from btl4gaug.cpp — ColorMapperHeat "cmHeat", HeadingPointer "headingPointer", ColorMapperArmor "cmArmor", ColorMapperMultiArmor, ColorMapperCritical "cmCrit", VertTwoPartBar "vertBar", SegmentArcRatio, OneOfSeveralPixInt; from btl4rdr.cpp — MapDisplay "map"; from btl4gau3.cpp — PlayerStatus; from btl4gau2.cpp — VehicleSubSystems, BitMapInverseWipe "LeakGauge", VertNormalSlider, PilotList, GeneratorCluster, SectorDisplay, PrepEngrScreen, MessageBoard; LAST entry MUST be BTL4ChainToPrevious = METHOD_DESCRIPTION_CHAIN(L4MethodDescription) routing unmatched keywords to the engine base table. The recovered shipped table had 16 raw pointers (0x517ea4, 0x51a2b0, 0x5160ac, 0x517614, 0x5142b4, 0x514b44, 0x514f8c, 0x5146fc, 0x5153d4, 0x51581c, 0x515c64, 0x5164f4, 0x51693c, 0x5171cc, 0x5138f4, 0x51a6f8 incl. the chain) vs the port's 18+chain — the port table is a reconstruction superset; count mismatch vs shipped .data still unresolved.
5. BC4.52 hazards for the re-host
falsekeyword: btl4grnd.cpp:182L4GaugeRenderer(false, NULL, NULL, NULL)— BC4.52 has no bool/true/false. Moot if re-hosted to the authentic no-arg ctors (which is also the shipped-binary truth).- Namespaced headers: btl4rdr.cpp includes , , and uses std::cout/std::flush/std::hex/std::dec (ShowInstance :518, debug blocks) — BC4.52 needs iostream.h/string.h/new.h, no std::.
- Placement new in MapDisplay::Make (btl4rdr.cpp:359-378, operator new +
new (gauge) MapDisplay(...)) — needs new.h under BC4.52; the authentic 1995 idiom is plainnew MapDisplay(...). _stricmp(btl4rdr.cpp:1214, FindSubObject) — Borland spells itstricmp(btl4galm.cpp already does).GetTickCount()(btl4rdr.cpp:804) + all getenv() debug scaffolding — Win32/port-only; strip.- Old for-scope: BC4.52 leaks the loop variable, so DrawNames' two
for (int i = 0; ...)loops in one scope (btl4rdr.cpp:1067 and :1092) are a "Multiple declaration for 'i'" error — hoistint i;(the authentic codebase's style declares indices at function top throughout GAUGREND/GAUGMAP). - Opcode-space budget: GaugeInterpreterCommand is unsigned char -> max 248 primitives across the ENTIRE chain (BT 18 + L4 base + anything the base chains to); also opcode identity = flattened table index, so keyword resolution requires BTL4ChainToPrevious stays LAST and every listed methodDescription's Make chain is real code (a /FORCE-stubbed entry AVs at Interpret time — CLAUDE.md §10 rule cited in the donor).
- btl4galm.cpp
mem_stream->WriteBytes/ReadBytes— invented names; the shipped calls are raw vtbl+0x20 (write) / +0x1c (read); verify against the authentic MUNGA MemoryStream header before compiling. - TearDownForViewpointRestream must be removed from BTL4GRND.HPP to match the surviving header exactly (or kept as an explicitly-marked port delta).
ios::nocreate(interpreter file open, GAUGREND.CPP:1161) exists in BC4.52's iostream — fine, but gone in modern libs; it is a hazard only in the opposite (modern) direction.- YawPitchRoll fix is SAFE to keep: authentic ROTATION.HPP declares YawPitchRoll (:162) with operator=(const Quaternion&) (:190) — no re-host substitute needed.
Load-bearing quotes
GAUGMAP.CPP:323-337 (authentic) — the per-entity bounds test is commented out: "//if (pos.z >= min_z) ... { Test_Tell(\"Accepted\"); destination->Add(entity); ++count; }" — GaugeEntityList::CopyEntitiesWithinBounds accepts EVERYTHING in the cell; MapDisplay's maximumDistanceSquared cull is the real range filter.
GAUGMAP.CPP:571/679 — "for( ; z<high_z; ++z) { for(x=low_x; x<high_x; ++x)" — EXCLUSIVE upper bounds: the top row/column of candidate grid cells is never searched (authentic off-by-one).
GAUGREND.HPP:58 — "typedef unsigned char GaugeInterpreterCommand;" and :209-220 — "endMarker=0, returnCommand, setPortIndexCommand, addOffsetCommand, callCommand, enableCommand, disableCommand, attributeVariableCommand, nextAvailableCommand" — primitives get opcode = nextAvailableCommand + flattened-table index (ParsePrimitive, GAUGREND.CPP:1439/1466).
GAUGREND.CPP:1847 — "primitiveTable[command]->Execute(display_port_index, position, entity, renderer);" — the opcode->class dispatch; MethodDescription::Execute (:2719-2743) Restore()s each parameter then "(*execute)(...)" = the gauge class's static Make.
GAUGREND.CPP:3093-3098 (authentic comment) — "This method used to update both the movingEntities and staticEntities records. That functionality has been moved up to the game-specific level (e.g., RPL4GaugeRenderer) to allow the game to determine what to show. Note that all descendents MUST chain back to this base method..."
L4GREND.cpp:787-795 — ConfigureForModel: "Str_Copy(full_name, model_name, sizeof(full_name)); Str_Cat(full_name, configuration_name, sizeof(full_name)); ... Configure(full_name, entity);" — the "Init" label is prefixed by mission->GetGameModel(), e.g. "MadcatInit" (L4GAUGE.CFG:2562).
BTL4APP.CPP:451 (authentic survivor) — "GetGaugeRenderer()->ConfigureForModel(\"Init\", viewing_entity);" under the comment "Configure the gauges for the entity".
btl4galm.cpp:23-39 (provenance) — "there is **no distinct, separately-emitted BTL4GaugeAlarmManager override body** ... no subclass vtable ... appears in vtables.tsv, and no caller constructs a subclass ... the compiler folded BTL4GaugeAlarmManager's tiny overrides away / it was never instantiated in this build configuration."
GAUGREND.HPP:352-353 — "#define METHOD_DESCRIPTION_CHAIN(pointer) {NULL, NULL, {{ ParameterDescription::typeEmpty, pointer }}}" — a name==NULL entry whose parameterList[0].data.nextMethodList chains method tables; BTL4ChainToPrevious must stay LAST in BTL4MethodDescription.
GAUGMAP.CPP:30 — "#define LARGEST_RADIUS 500.0 // HACK - this is a guess" — the grid-cell search pad.
btl4grnd.cpp:172-182 — donor base-ctor delta: "In the shipped BT binary the base ctor (FUN_0046f97c) was called with `this` only -- the original L4GaugeRenderer took no arguments" vs the port's "L4GaugeRenderer(false, NULL, NULL, NULL)" (BC4.52: no `false` keyword; authentic re-host = no-arg).
Open questions
- Shipped BTL4MethodDescription @0051c910 shows 16 recovered pointers (incl. the chain) but the port registers 18 + chain — which two of the port's entries (candidates: SectorDisplay, PrepEngrScreen, MessageBoard as later splits, or two entries sharing a methodDescription) are not in the shipped table, and what is the shipped order? Order determines nothing at runtime (opcodes are parse-time-consistent) but matters for byte-faithful .data reconstruction.
- BTL4GaugeAlarmManager alarm-type spellings ("threshold"/"range"/"state"/"flash" are invented): does any surviving egg/model resource contain a "gaugeAlarm" sub-resource whose entry names would pin the real set (grep future dumps for the @004eecc9 "gaugeAlarm" key)?
- MemoryStream read/write method names in the authentic MUNGA stream header (shipped calls are vtbl+0x1c/+0x20) — needed before btl4galm.cpp compiles against the 1995 headers; donor's WriteBytes/ReadBytes are placeholders.
- Was gaugeAlarmManager EVER non-NULL in any BT 4.10 configuration (e.g., a debug build that constructed BTL4GaugeAlarmManager), or was BTL4GALM.HPP written 02/22/96 for a feature that never shipped? The dormancy conclusion rests on this one image (BTL4OPT.EXE).
- MapDisplay::Make is reconstructed (ctor had no exported caller in the decomp) — the offset_position "center"/"bottom" string conversion and the exact 13-parameter typing should be validated against a live parse of L4GAUGE.CFG:4923 once the BT gauge boot runs.
- On a faithful re-host, does the authentic NotifyOf*InterestingEntity feed actually fire in the reconstructed runtime (the port dropped the Renderer interest plumbing and substituted RebuildEntityGrid) — i.e., can RebuildEntityGrid + the three visible-radius culls be retired without the radar going empty again?
- BuildRadarShadow: empty in the shipped optimised build, but shadowTable (halfViewWidthInUnits*8 bytes) is still allocated — did an earlier/debug build populate it (the ShadowRecord{int distance} struct survives in the header), and is any recovered object file available to reconstruct the intended ray-march?
=============================================================================
BTL4GAUG widget primitives — per-class engine binding audit
Sources: donor C:\VWE\BT411\game\reconstructed\btl4gaug.hpp + btl4gaug.cpp (2465 lines, TU extent @004c2f94..@004c6771); 1995 engine C:\VWE\TeslaRel410\restoration\source410\MUNGA\GAUGE.HPP/GAUGE.CPP (Gauge/GraphicGauge/GaugeConnection), MUNGA\GAUGREND.HPP/GAUGREND.CPP (GaugeInterpreter/MethodDescription/ParameterDescription/GaugeRenderer), MUNGA_L4\L4GAUGE.HPP/L4GAUGE.CPP (the L4 widget library: NumericDisplay, SegmentArc, WipeGaugeScalar family, ColorState, BackgroundReconfig).
The engine API every widget plugs into (1995 contract)
- GaugeInterpreter (GAUGREND.HPP:125) parses L4GAUGE.CFG against a MethodDescription** primitive table; each widget publishes
static MethodDescription methodDescription{keyword, &Make, ParameterDescription list} (native exemplar: NumericDisplayScalar, L4GAUGE.CPP:552). BT registers its widgets in BTL4MethodDescription[] (btl4grnd.cpp:125-155, .data @0051c910), chained to the engine base table via METHOD_DESCRIPTION_CHAIN — same mechanism GaugeRenderer::BuildConfigurationFile (GAUGREND.CPP:4288) drives. - typeAttribute params resolve through ParameterDescription::ParseAttribute (GAUGREND.CPP:2141): "Sub/Attr" → entity->FindSubsystem("Sub") then Simulation::GetAttributePointer("Attr"); bare "Attr" → entity-level GetAttributePointer. THAT is who "publishes" an attribute — the subsystem's attribute table.
- Per-frame contract: Gauge::Update (GAUGE.CPP:549) walks instanceList calling each GaugeConnection::Update() (virtual, GaugeConnection : Plug, GAUGE.HPP:151), then calls virtual Execute(). BecameActive() = repaint invalidation hook. AddConnection (GAUGE.CPP:480); Gauge::~Gauge → RemoveAllConnections deletes connections.
- GraphicGauge adds the embedded
GraphicsView localView(GAUGE.HPP:446; donor's this+0x48).
Per class
ColorMapper — base: engine Gauge (non-drawing; ctor FUN_00444308). @004c37dc ctor, @004c38dc dtor, @004c395c BecameActive, @004c3980 Execute (palette-slot push). Binds no attribute itself; currentColorIndex (@0x54) is the destination for derived-class connections. Resource path: renderer->warehousePointer->palette8Bin (native idiom, cf. BackgroundReconfig L4GAUGE.CPP:1739/1794) + GaugeRenderer::GetGraphicsPort(int) (GAUGREND.HPP:517, FUN_00447f84). Nearest 1995 analog: BackgroundReconfig (palette remap via L4GaugeRenderer::RemapGraphicsPort, L4GAUGE.CPP:1801) — but ColorMapper is a live per-frame single-slot SetColor push, a BT-only widget.
ColorMapperArmor — base ColorMapper. CFG "cmArmor"(rate,mode,slot,palA,palB,dzName), L4GAUGE.CFG:4789. Make @004c3aa4, ctor @004c3b98, SetColor @004c3c38 (@0x6C state slot). Feed: ArmorZoneConnection (@004c33a4 ctor/@004c3430 Update) → Entity::damageZones[idx] (@0x120) → DamageZone::damageLevel @0x158 ×100. Publisher: the mech damage model (DamageZone array). Zone name→index = Entity::GetDamageZoneIndex (FUN_0042076c). 1995 L4 analog: ColorState (L4GAUGE.CPP:4140) took a zone NUMBER + DamageZone*** attribute — the name lookup is BT-side game code, not engine.
ColorMapperMultiArmor — base ColorMapper. CFG "colorMapperMultiArmor"(...8 zone names). Make @004c3c48 (binary quirk: presence test index != 0, so it always builds), ctor @004c3d60. Feed: MultiArmorConnection (@004c346c/@004c34f4) worst-of-8 damageLevel. Same publisher as cmArmor.
ColorMapperCritical — base ColorMapper. CFG "cmCrit"(...,subsystemName), L4GAUGE.CFG:143. Make @004c3ddc, ctor @004c3e40. Feed: CriticalConnection (@004c3598/@004c3610): Subsystem simulationState @0x40 ==1 → 100; else damage-zone proxy @0xE0 → damageLevel @0x158. Publisher: the named MechSubsystem via entity->FindSubsystem (FUN_0041f98c — native call, same as ParseAttribute uses).
ColorMapperHeat — base ColorMapper. THE TU ANCHOR: ctor @004c3f6c, assert "d:\tesla\bt\bt_l4\BTL4GAUG.CPP" line 0x68a. Make @004c3f08. CFG "cmHeat"(...,heatSubsystemName), L4GAUGE.CFG:182. Feed: HeatConnection (@004c3664/@004c3720) → HeatableSubsystem::currentTemperature @0x114 (publisher: HeatableSubsystem, heat.hpp:324; HEAT.TCP seeds 300.0 — temperature→0..100 scaling still flagged for human reconciliation). Ctor verifies IsDerivedFrom (FUN_0041a1a4) against 0x50e3ec HeatableSubsystem or 0x50e604 HEATWATCHER; the donor's BTIsHeatWatcher() extern (heatfamily_reslice.cpp) is a PORT bridge — 1995 native is a second IsDerivedFrom against the watcher family's Derivation table (GetClassDerivations pattern, heat.hpp:258/345/592).
HorizTwoPartBar — base: engine GraphicGauge (ctor FUN_00444818). Make @004c407c, ctor @004c4170, BecameActive @004c4324, Execute @004c4340 (3-zone repaint; zone colours fixed 2026-07-21 vs binary @004c443a/@004c44da). Feeds: 3× engine-native GaugeConnectionDirectOf (GAUGE.HPP:172, FUN_00474855) for value/low/high. Not in BTL4MethodDescription (built by btl4gau2 clusters, not CFG keyword). Shared tiled-blit helper DrawTiledBitmap @004c2ff8. 1995 analog family: WipeGaugeScalar/BarGraphBitMapScalar (L4GAUGE.CPP:2859/3535).
VertTwoPartBar — base GraphicGauge. CFG "vertBar", L4GAUGE.CFG:4521. Make @004c462c, ctor @004c4724, dtor @004c486c, BecameActive @004c48e0, Execute @004c48fc. Attributes: HeatSink/CoolantMass (→ coolantLevel @0x12C), HeatSink/CoolantCapacity ×2 — publisher HeatableSubsystem's attribute table (heat.hpp:350-372, resolved by ParseAttribute).
VertNormalSlider — base GraphicGauge. CFG "vertNormalSlider", L4GAUGE.CFG:4839. Make @004c4b08, ctor @004c4b84 (latches XOR op — native GraphicsView::SetOperation(GraphicsDisplay::Operation), GRAPH2D.CPP:2175), dtor @004c4c68, TestInstance @004c4c94, BecameActive @004c4cac, Execute @004c4cc0, Draw @004c4d48. Attribute: Condenser/ValveSetting (@0x15C coolantFlowScale) — publisher Condenser (heat.hpp:372).
OneOfSeveral (base) — base GraphicGauge. ctor @004c4d88, dtor @004c4e7c, BecameActive @004c4f14, Execute @004c4f28. Picks bitMapBin vs pixelMap8Bin by fromImageStrip — both native warehouse bins (L4GAUGE.CPP uses identical Get/Release idiom). Drawing: DrawPixelMap8/DrawBitMapOpaque — native GraphicsView ops (GRAPH2D.CPP recorder command list confirms bitmapOpaque/pixelmap8 commands).
OneOfSeveralInt — Make @004c5068, ctor @004c5148, dtor @004c51d8; GaugeConnectionDirectOf (FUN_004749de). Cluster-built, no CFG keyword.
OneOfSeveralPixInt — CFG "oneOfSeveralPixInt", L4GAUGE.CFG:5001. Make @004c5204, ctor @004c52d8, dtor @004c5364. Attributes: DuckState (entity-level), Searchlight/LightOn, ControlsMapper/DisplayMode — int state selectors resolved by ParseAttribute against those subsystems.
OneOfSeveralStates — Make @004c5390, ctor @004c5470, dtor @004c5500, BecameActive @004c552c. Feed: StateConnection (@004c3324/@004c3390) copying Subsystem state word @0x14 from the Entity* passed at construction (cluster-built).
HeadingPointer — base GraphicGauge; owns engine-native NumericDisplay (L4GAUGE.HPP:31, ctor FUN_004700bc, ForceUpdate/Draw native) + engine-native GraphicsViewRecord erase tracking (GRAPH2D.CPP:1425, Draw/Clear + GraphicsView::AttachRecorder @GRAPH2D.CPP:2676). CFG "headingPointer", L4GAUGE.CFG:4941. Make @004c554c, ctor @004c562c, dtor @004c573c, ShowInstance @004c57d0, BecameActive @004c58e8, Execute @004c5914. No CFG attribute: reads the linked entity's orientation quaternion (entity+0x10c) via renderer->GetLinkedEntity() (SharedData::Resolve FUN_00417ab4). PORT deviations: YawPitchRoll decomposition instead of the binary's EulerAngles (FUN_0040954c) — both are native MUNGA ROTATION.HPP types, deviation is deliberate (double-cover discontinuity) but not byte-faithful; application->GetViewpointEntity() fallback is PORT bring-up only (1995 = Renderer entity socket / GaugeBase::LinkToEntity).
BitMapInverseWipe — base GraphicGauge. CFG "LeakGauge", L4GAUGE.CFG:4858 (P2 registration; ctor lists third BEFORE frames — Make maps p[6]/p[5] by name). ctor @004c5b7c, dtor @004c5c80, BecameActive @004c5cf4, Execute @004c5d08 (threshold _DAT_0050e3d8=0.0025). Attribute: Condenser/CoolantMassLeakRate (@0x130 coolantDraw; published heat.cpp:345). 1995 analog: WipeGaugeScalar family. NOTE: the second wipe base @004c5e84 (dtor @004c5f30, BecameActive @004c5fa4, Execute @004c5fb8) and its Scalar variant @004c61c8 (dtor thunk @004c66d9) are notes-only, not reconstructed.
SegmentArc270 — base: engine SegmentArc (L4GAUGE.CPP:4837, L4GAUGE.HPP:1086 — native, protected currentValue/previousSegment/numberOfSegments confirmed). ctor @004c6244, dtor thunk @004c66b3. One GaugeConnectionDirectOf onto SegmentArc::currentValue (weapon PercentDone recharge; cluster-built). 1995 near-twin: SegmentArcNormalized (L4GAUGE.CPP:5120) — identical shape minus BT's segmentSpan (|n|/(n-1)*0.75, integer division). PORT-only: the Execute() diag wrapper + const char *diagName member (frozen-dial hunt) — NOT in the binary; native = no override, inherit SegmentArc::Execute.
SegmentArcRatio — base engine SegmentArc (FUN_00473f44; SegmentArc::Execute @0x474300). CFG "segmentArcRatio", L4GAUGE.CFG:4964. Make @004c62fc, ctor @004c6394, Execute @004c6488 (ratio→currentValue→delegate), dtor thunk @004c668d. Attributes: LinearSpeed, MaxRunSpeed — entity(mech)-level attributes via Simulation::GetAttributePointer.
File-private connections (HeatConnection, ArmorZoneConnection, MultiArmorConnection, CriticalConnection, StateConnection) — all : GaugeConnection (: Plug), overriding virtual Update() — exactly the engine contract (GaugeConnection::Update Fails if not overridden, GAUGE.CPP:212-215; Gauge::Update calls it per frame). Donor decomp notes call it "Transfer"; the engine name is Update, and the donor code already uses Update. Deleting-dtor thunks: @004c66ff/@004c6725/@004c674b/@004c6771 (ColorMapper family), @004c66b3/@004c668d (arcs).
PORT-only shims with native 1995 equivalents
DebugStream(mechrecon.hpp:564 ReconStream artifact; heat.hpp variant gated by BT_DEBUGSTREAM_DEFINED) → nativeDEBUG_STREAM <<macro (used throughout L4GAUGE.CPP:1435+, GAUGREND.CPP) plus Tell/Test_Tell; drops the #define dance at btl4gaug.cpp:78.HeatRound()static inline (btl4gaug.cpp:151) → engineRound(Scalar)(binary FUN_004dcd94; declaration lives in the engine scalar header, absent from the partial restoration tree).new char[strlen+1]+strcpyinterning (VertTwoPartBar/HorizTwoPartBar/OneOfSeveral/HeadingPointer/BitMapInverseWipe ctors) → nativenameCopy()(L4GAUGE.CPP:80, strdup+Register_Pointer; dtor pairs with Unregister_Pointer + delete[]).WarehouseBinOf<Palette8>& = renderer->warehousePointer->palette8Bin+.GetIfAlreadyExists()(ColorMapper ctor/dtor/Execute) → native idiom isL4Warehouse *w = (L4Warehouse *)renderer->warehousePointer;thenw->palette8Bin.Get(name)/.Release(name)— no typed bin reference, no peek API anywhere in L4GAUGE.CPP; also the base Warehouse* must be cast to L4Warehouse* (the donor skips the cast in ColorMapper only).operator new(0xNN)+ placement-new Make pattern → native Make uses plainnew ClassName(...)+ Register_Object (NumericDisplayScalar::Make L4GAUGE.CPP:613, SegmentArcNormalized::Make :5091). Placement new is legal in BC++4.52 (needs <new.h>) but non-native.BTIsHeatWatcher()extern bridge → second IsDerivedFrom against the HEATWATCHER Derivation table.application->GetViewpointEntity()fallback (HeadingPointer::Execute) → delete; native relies on the renderer's linked-entity socket.- SegmentArc270::Execute diag wrapper + diagName → delete (inherit SegmentArc::Execute).
- Gauge::GuardedExecute + the getenv-gated SEH path (GAUGE.HPP:391/GAUGE.CPP:578-604) and the getenv NULL-source binding inside GaugeConnectionDirectOf's ctor (GAUGE.HPP:217-235) — port bring-up in the SHARED engine files; 1995 = plain Execute() and Check_Pointer(data_source) hard-verify.
- GaugeInterpreter::interpreterTableSize grown 46864→262144 (GAUGREND.HPP:231) — port capacity fix, keep but flag as deviation.
BC++4.52 compile blockers in the donor (with 1995 replacement)
std::flush+std::flush-style namespace usage in SegmentArc270::Execute (btl4gaug.cpp:2459) — BC++4.52 iostream.h has no std namespace. Replacement: bareflush(native pattern<< "\n" << flush, L4GAUGE.CPP:4862) — or delete the whole PORT wrapper.- heat.hpp's optional
std::ostream&DebugStream variant — must never be the active definition under BC++4.52; replace the whole DebugStream/ReconStream machinery with DEBUG_STREAM. static T s_gaugeNullSource = T();in GaugeConnectionDirectOf's ctor (GAUGE.HPP:231) —T()value-init copy-init of a static local for built-in T is unreliable in BC++4.52; the block is port-only, delete it.- GuardedExecute's intended SEH (__try/__except) — no Win32 SEH in the DOS4GW/BC++4.52 target; delete (the checked-in body is already a plain call, so only the design note is affected).
- Everything else is clean for BC++4.52: only simple one-parameter class templates (GaugeConnectionDirectOf, Vector2DOf — both 1995-native), no lambdas, no STL containers, aggregate MethodDescription initializers identical to the native idiom. Watch item: BC++4.5x uses OLD for-scope rules — no same-function duplicate
for (int i...)declarations exist in btl4gaug.cpp today, but any merge must keep it that way.
Load-bearing quotes
btl4gaug.hpp:33-34: 'The sole address with a surviving embedded assert path -- the anchor -- is ColorMapperHeat's constructor: @004c3f6c "d:\tesla\bt\bt_l4\BTL4GAUG.CPP", line 0x68a'
GAUGE.CPP:569-603 (Gauge::Update): 'while ((gauge_connection=i.ReadAndNext()) != NULL) { ... gauge_connection->Update(); } ... else { Execute(); }' -- the per-frame contract every widget plugs into
GAUGE.HPP:172: 'template <class T> class GaugeConnectionDirectOf: public GaugeConnection' -- the engine-native template the donor bars/arcs use unchanged
L4GAUGE.CPP:80-89: 'char *nameCopy(const char *old_name) { ... strdup(old_name); Register_Pointer(copy); }' -- native replacement for the donor's new char[]+strcpy interning
L4GAUGE.CPP:613-631 (NumericDisplayScalar::Make): 'new NumericDisplayScalar( parameterList[0].data.rate, ... (Scalar *) parameterList[8].data.attributePointer // HACK!!!! The (Scalar *) is VERY dangerous!' -- the native Make pattern (plain new, no placement)
GAUGREND.CPP:2176-2189 (ParseAttribute): 'simulation = (Simulation *) entity->FindSubsystem(source); ... void *attribute_pointer = simulation->GetAttributePointer(source);' -- how Subsystem/Attr CFG bindings resolve
L4GAUGE.CPP:5163-5164 (SegmentArcNormalized ctor): 'connection = new GaugeConnectionDirectOf<Scalar>(0, ¤tValue, value_pointer);' -- the 1995 twin of SegmentArc270
heat.hpp:350-372: 'Simulation::GetAttributePointer ...: HeatSink/CoolantMass -> coolantLevel (@0x12C, live) ... CoolantMassLeakRateAttributeID // @0x130 coolantDraw ... ValveSettingAttributeID // @0x15C coolantFlowScale (condenser valve slider @2)'
btl4gaug.cpp:2456-2459 (SegmentArc270::Execute PORT wrapper): 'DEBUG_STREAM << "[arc] " << diagName ... << "\n" << std::flush;' -- std::flush will NOT compile under BC++4.52; native is bare `flush`
GAUGE.HPP:224-234 (GaugeConnectionDirectOf ctor, PORT block): 'static T s_gaugeNullSource = T(); dataSource = &s_gaugeNullSource;' -- getenv-gated port shim, delete for 1995 build
GAUGREND.HPP:228-231: 'The original (RP) value 46864 is too small for BattleTech's larger gauge config ... interpreterTableSize = 262144' -- port capacity deviation in the shared engine header
btl4grnd.cpp:136-154: '&ColorMapperHeat::methodDescription, // "cmHeat" ... &BTL4ChainToPrevious' -- the BT registration table chained to the engine L4MethodDescription
Open questions
- The 1995 spelling of the warehouse bin type and its peek API: WRHOUS.HPP is absent from restoration/source410, so
WarehouseBinOf<Palette8>and.GetIfAlreadyExists()(ColorMapper::Execute) cannot be verified against 1995 — every native L4GAUGE.CPP call site uses only((L4Warehouse*)renderer->warehousePointer)->xxxBin.Get()/Release(); the peek call may need rewriting as a Get/Release pair. - GraphicsPort::SetColor(PaletteTriplet*, int slot) — the single-slot hardware palette write ColorMapper::Execute performs: the L4 graphics-port header is missing from the restoration tree; the only palette API visible in 1995 source is L4GaugeRenderer::RemapGraphicsPort (whole-palette remap, L4GAUGE.CPP:1801). Confirm the slot-level call exists in the full L4 tree (the binary clearly does it via the port vtable).
- Where the engine declares
Round(Scalar)(FUN_004dcd94) — the scalar/math header is not in the partial restoration tree; HeatRound is the donor stand-in. - HeatConnection's HEATWATCHER alternate branch: for a watcher, offset 0x114 is watchedLink, not currentTemperature — the flag@0x14 read-path is unverified [T4, donor's own note].
- Heat temperature -> 0..100 colour-index scaling: HEAT.TCP seeds currentTemperature at 300.0; the donor rounds it raw into the palette index — reconcile against the original heat palette ramp (donor flags this for a human).
- The second BitMapInverseWipe base @004c5e84 and its Scalar variant @004c61c8 are notes-only (not reconstructed); if btl4gau2's LeakGauge subclassing needs them they are missing code.
- HeadingPointer uses YawPitchRoll where the binary used EulerAngles (FUN_0040954c) — a deliberate anti-glitch deviation; decide whether byte-fidelity or the fix wins for the 4.10 literal reconstruction.
- ColorMapperMultiArmor::Make keeps the binary's
index != 0presence-test quirk (a valid zone 0 and a -1 'unused' both count as present) — faithful, but flag if zone 0 semantics ever matter.
=============================================================================
5.3.26 LANDING REPORT (the first rendered milestone -- gauges LIVE)
All six TUs re-hosted (4-agent workflow + integration) and the block BOOTS: L4GAUGE=640x480x16 + HEAPSIZE=15000000 (gauge.conf / gaugefight.conf). Verified: the interpreter parses the FULL cfg through the 19-entry BT registry (only warn = the absent 'Initialization' label -- the BT cfg has none, graceful skip); Bhk1Init RUNS (case-insensitive label match on "bhk1"+"Init"); ColorMapperArmor Makes resolve zone NAMES against the live mech (8 cross-chassis warnings = the authentic -1 inert path); the full mutual missile fight runs 110s with the cockpit stack live (16/16 rounds, 138 zone hits, zero faults); gauges-off regressions untouched.
ENGINE DEVIATION (bring-up, load-bearing): ParameterDescription:: ParseAttribute normalizes unpublished attributes to a shared 64-byte zero cell (gaugeUnboundCell) instead of NULL -- GaugeConnectionDirectOf's 1995 ctor hard-derefs its source (the first boot crashed exactly there, @GaugeConnectionDirectOf::ctor). Widgets on unpublished names draw static zeros and LIGHT UP automatically when the attribute waves publish (the agents baked the 1995 CFG spellings into every binding: HeatLoad / CoolantAvailable / CurrentTemperature / Degradation~ / Failure~ / CoolantMassLeakRate / CurrentSeekVoltageIndex / Min~ / Max~ / SeekVoltage / RadarRange / RadarLinearPosition / RadarAngularPosition / RadarPercent).
WHAT'S INERT UNTIL ITS FEED WAVE (full lists in the agent reports above): seek-response curves (vtbl-slot-15 sampler), cooling-loop lamp frames, aux-screen numbers/labels (resource freed post-ctor -- roster-order stand-in), ballistic ammo numerics (ammoBinLink protected), heat-family named attribute rows, radar family, MessageBoard feed (StatusMessagePool), name bitmaps, TeamStatusDisplay (prose-only donor, deferred).
NEXT gauge steps: publish the heat/seek/radar attribute rows (small HPP tables -- lights half the panels), add BTPlayer::GetKillCount() and the ammoBinLink accessor, then the VISUAL pass on the rig (emulator VPXLOG=1 + VDB head windows / pod-launch --layout explode) diffing against the shipped exe on the same GAUGE content.
5.3.27 -- THE ATTRIBUTE WAVE (48/50 cockpit bindings LIVE)
The heat/power/sensor/mech attribute rows are published, so the gauges read
the real simulation instead of the zero cell. THE NUMBERING IS PINNED:
the chain publishes IDs 2..0x0E so PoweredSubsystem::NextAttributeID
lands on the AUTHENTIC 0x0F -- the surviving SENSOR.HPP numbers
RadarPercent off it, and MechWeapon's binary-pinned table starts its real
IDs at 0x12 (its pads therefore shrank 16 -> 3, bridging 0x0F..0x11 exactly
as the header comment predicted). Do not renumber without re-pinning both.
HeatableSubsystem 2..5 CurrentTemperature / HeatLoad / DegradationTemperature / FailureTemperature HeatSink 6..9 CoolantMass / CoolantCapacity / CoolantMassLeakRate / CoolantAvailable Condenser 0x0A ValveSetting (sibling branch) PoweredSubsystem 0x0A-0E InputVoltage / OutputVoltage / RatedVoltage / VoltageState / ConnectMode -> Next = 0x0F Generator 0x0A-0C OutputVoltage / RatedVoltage / GeneratorNumber (sibling branch off HeatSink) Sensor 0x0F-11 RadarPercent / SelfTest / BadVoltage (the AUTHENTIC surviving enum, now defined) Mech (entity) Radar{Range,LinearPosition,AngularPosition} / LinearSpeed / MaxRunSpeed / DuckState
Also rechained so the whole family is visible where the cfg expects it: MechWeapon -> PoweredSubsystem::AttributeIndex (weapons expose temps), Sensor -> PoweredSubsystem, Reservoir + AggregateHeatSink -> HeatSink.
VERIFIED (BT_GAUGE_ATTR_LOG=1 on gaugeattr.conf): 48 OK / 2 NULL, up from 33/17 and 0/50 at the block's birth. The two remaining have NO member to bind: HeatSink/AmbientTemperature (the radiator's ambient is a sim constant, not state) and Searchlight/LightOn (Searchlight is a memberless PowerWatcher subclass) -- both fall to the zero cell and draw static, which is correct until those bricks exist. Gauge fight + smoke + novice all clean.
5.3.28 -- THE VISUAL A/B PASS (first side-by-side vs the shipped exe)
RIG (repeatable): both exes live in the same mount reading the SAME GAUGE content -- run/image/BTL4SHP.EXE (the shipped ALPHA_1 binary) and BTL4REC.EXE (ours); confs run/vis_shp.conf + run/vis_rec.conf (gauges on: L4GAUGE=640x480x16, HEAPSIZE=15000000, machine=svga_s3, output=opengl). Capture: scratchpad/grab.ps1 (Win32 window grab) at ~70s, then a PIL diff. With L4VIDEO off the DOSBox window IS the 640x480x16 gauge framebuffer -- the packed buffer the VDB splits into the six heads -- so no VPX needed.
RESULT: our reconstructed exe draws the cockpit. Measured against the shipped framebuffer (chrome cropped):
first run 90.1% pixels identical, 82% of shipped's lit pixels lit
after fix 93.2% pixels identical, 85% coverage
Every difference in the first run traced to ONE root cause: the authored aux-screen block (number / placement / label) lives in the subsystem resource, which is FREED with the Mech ctor's stream buffer (the 5.3.24 use-after-free landmine). Both this tree AND the BT411 port had stubbed it ("PoweredSubsystem keeps NO live copy") -- which is why BT411's displays show the same artifacting. FIX: PoweredSubsystem now CACHES the block at ctor time (auxScreenNumber / auxScreenPlacement / auxScreenLabel / engScreenLabel + accessors), and btl4gau2 uses the authored screen instead of a roster-order stand-in and builds the placement strip art. Panels immediately moved onto their authored screens (the weapon recharge dials land on the same panels as the shipped exe; mech icons appear on the sensor/myomer panels; the generator letters correct to their authored screens).
STILL DIFFERING (all donor-inherited stubs, unchanged by this pass): the panel TITLE art ("SENSOR CLUSTER" / "MYOMERS" / "ERMED LASER RANGE 500M" / "TYPE II 65 TONS"), the pilot name ("Aeolus"), and some lamp frames ("OFF x" vs the shipped state digits). The label blit is now wired through GetAuxScreenLabel() but draws nothing on bhk1 -- so those titles come from the q-strip statusImage path (the 8 qblh*.pcc typeStrings VehicleSubSystems passes to DrawStatusCells), NOT from auxScreenLabel. That is the next thread. Name bitmaps stay inert (CreateMutantPixelmap8 / LookupPlayerNameBitmap bring-up stubs).
5.3.29 -- THE PANEL TITLES (94.8% identical / 91% coverage)
The 5.3.28 pass proved the titles do NOT come from auxScreenLabel's ENG-page
blit. Reading the art settled it: qsensors.pcc / qmyomers.pcc /
qermlas.pcc / qppc.pcc / qstrk6.pcc are 299x26 TITLE BANNERS, while
the qblh0..7.pcc strings the cfg hands vehicleSubSystems are 77x120 MECH
ICONS (which the 5.3.28 placement fix already lit). So the label IS the
title -- it just belongs on the MFD panel, not only the ENG page.
SubsystemCluster now builds a titleBanner child from the cached GetAuxScreenLabel(). Placement was MEASURED against the shipped framebuffer with the A/B rig (BackgroundBitmap places by left/BOTTOM): x + 0x09, y + 0xb3 from the panel origin -- found by a bring-up BT_TITLE_DY knob, then a green-pixel correlation over the title band, then baked. The authored data behind it (dumped live): Avionics screen=5 placement=2 label=qsensors.pcc; Myomers 6/0/qmyomers; PPC_1 1/4/qppc; PPC_2 10/6/qppc; ERMLaser_1 4/1/qermlas; ERMLaser_2 8/6; ERMLaser_3 7/4; SRM6_1 2/1/qstrk6; SRM6_2 9/3.
SCORE PROGRESSION vs the shipped cockpit (same GAUGE content, same mount): 5.3.26 birth 90.1% identical / 82% coverage 5.3.28 authored screens 93.2% / 85% 5.3.29 titles (rough) 92.6% / 88% 5.3.29 titles (tuned) 94.8% / 91% <-- current "SENSOR CLUSTER", "MYOMERS", "TYPE II 65 TONS" and both "ERMED LASER RANGE 500M" banners now render in their authored positions.
CAPTURE GOTCHA (cost two wasted runs): the grab script copies SCREEN pixels, so any window in front of DOSBox is what gets captured (one grab returned a white frame, another returned an unrelated 3-D app). grab.ps1 now SetForegroundWindow()s the target and waits 1.2 s first.
STILL OPEN: the pilot name bitmap ("Aeolus" -- CreateMutantPixelmap8 / LookupPlayerNameBitmap bring-up stubs), some lamp frames ("OFF x" vs the shipped state digits -- the mode accessor stand-in), and a stray magenta "MID/ADV" block that lands over the top-left title (a widget drawing at a wrong port offset; next thread).
5.3.30 -- THE PILOT NAME (and what the magenta boxes really are)
The name art was never missing: the egg carries it as notation pages of hex rows ([BitMap::Small::Aeolus]), the engine BitMap has a ctor that reads exactly that format (GRAPH2D.CPP), and the AUTHENTIC Mission ctor already loads them into smallNameBitmapChain (CODE/RP/MUNGA/MISSION.CPP:525 -- implemented, called, working). The only break was OUR side: btl4gau3's LookupPlayerNameBitmap was a bring-up stub returning NULL. It now goes through the authentic API -- application->GetCurrentMission()-> GetSmallNameBitmap(player->playerBitmapIndex) -- and RESOLVES live (traced: index=1 -> a real BitMap; the fallback-box branch never fires). Coverage 91% -> 92%.
DIAGNOSIS CORRECTED: the two magenta blocks over the top-left title are NOT the missing-name placeholder (that branch is provably not running). They are the REAL name bitmap drawn at the wrong slot coordinates -- PilotList positions its 8 roster slots from the PE-recovered DAT_0051af88 (x,y,mode) table, and ours puts them at the top-left instead of the shipped exe's centre row. Recovering that layout table is the next cockpit thread (it should also land "Aeolus" where the shipped binary draws it).
NOTE for whoever picks this up: a wrong-looking blit here is a LAYOUT bug, not an art or lookup bug -- the pipeline from egg hex rows to a live BitMap pointer is verified end to end.
5.3.31 -- WHO DRAWS THE STRAY BLOCKS (traced, not guessed)
Instrumented both suspects and ran. Result, unambiguous:
[pl] PilotList slot=0 at (180,225) <- the ONLY drawer
(no [ps] lines at all)
So the two magenta blocks over the top-left title are PilotList's slot-0 entry on the Comm port, drawing the pilot's name art + mech icon. PlayerStatus never draws: the cfg builds 8 of them (sec port, 4x2 grid, players 1..8) but our ResolveRosterPlayer(playerIndex) is 0-based while the cfg's player numbers are 1-BASED, so panel 1 looks for roster index 1 and a solo mission has only index 0 -> every panel stays unbound and its Execute returns early. TWO SEPARATE FINDINGS, both previously mis-attributed:
- PilotList slot 0 IS supposed to draw (the local pilot's roster row). Its layout entry {180,225,mode 0} is the PE-recovered table value, and the Comm port's background (btcomm.pcx, KILLS/DEATHS columns) lands pixel-identical in both exes -- so the PORT geometry is right and what remains is the entry's coordinate CONVENTION inside that port (bottom-relative vs top-relative, and the name/mech sub-offsets at mode 0: name_x = x+0x95, name_y = y-0x0d). Compare against where the shipped exe puts "Aeolus" (framebuffer ~x 200-290, y 183).
- PlayerStatus is off by one and therefore INERT. Feeding it playerIndex-1 (or making the resolver 1-based) would bind the local pilot to panel 1 and light the 4x2 scoreboard -- do this WITH the shipped exe open in the A/B rig, since it will add a whole page of new pixels that must be checked against the reference rather than assumed.
Method note: three rounds of source inspection produced three wrong suspects; ONE instrumented run settled it. For anything cockpit-visual, trace first -- the A/B rig plus a one-shot DEBUG_STREAM is cheaper than reading call graphs.
RIG CAVEAT (hit twice): grab.ps1 uses CopyFromScreen, so a window in front of DOSBox lands in the capture and the diff scores collapse (one run read 77%/47% purely because an editor covered the right half). SetForegroundWindow helps but Windows refuses to raise a window when another app holds the foreground lock. If the rig is to run unattended while the desktop is in use, switch the grab to PrintWindow(hwnd, hdc, PW_RENDERFULLCONTENT), which asks the window to render itself and works while occluded (verify it against the OpenGL output mode first -- if PrintWindow comes back blank on GL, set [sdl] output=surface in the vis_*.conf for capture runs).