diff --git a/context/decomp-reference.md b/context/decomp-reference.md index baa3000..256f868 100644 --- a/context/decomp-reference.md +++ b/context/decomp-reference.md @@ -32,7 +32,7 @@ NOT `SearchList(clip_id, 16)` (which treats the arg as a LIST → walks garbage |---|---|---| | SkeletonStream | — | `.SKL` skeletons | | DamageZoneStream | **0x14** | damage-zone records (Mech ctor reads count + array) | -| ModelList | **18** | model/mech names (`bhk1`, `madcat`, …) | +| GaugeImageStream | **18** (0x12) | radar PIP vector shapes (.GIM linelist; keyed by the entity model's resource id -- `bhk1`=1025, `madcat`=401, …; 110 ship in BTL4.RES). task #17 CORRECTION: the old "ModelList = model/mech names" label was wrong -- the names matched because pips are stored under their model's name/id. | | AnimationResourceType | **16 (0x10)** | animation clips (lowercase: `blhrrl`=904, `blhrrr`=905, `blhwwli`=910, `blhrrli`=916) | | damage-zone segment list | 0x14 | per-zone segment list | | critical-subsystem segment | 0x1e | crit subsystem segments | diff --git a/context/gauges-hud.md b/context/gauges-hud.md index 3331d44..5a63104 100644 --- a/context/gauges-hud.md +++ b/context/gauges-hud.md @@ -6,8 +6,8 @@ source_sections: "docs/GAUGE_COMPOSITE.md (full map); CLAUDE.md §8" related_topics: [reconstruction-gotchas, decomp-reference, subsystems, pod-hardware] key_terms: [gauge, methodDescription, attribute-pointer, GaugeRenderer, MFD, PCC] open_questions: - - "Condenser valve control gates on the owning BTPlayer game-mode flag (mech+0x190), likely off in basic missions" - "Status-message queue (StatusMessagePool) is a NULL stub -> MessageBoard is empty" + - "HUD binary attr table @005110c0 offsets conflict with the port HUD layout on 3 slots (0x1D8/0x1EC/0x1F8) -- re-dump before publishing" --- # Cockpit Gauges / MFD HUD diff --git a/context/reconstruction-gotchas.md b/context/reconstruction-gotchas.md index 57a910b..2576078 100644 --- a/context/reconstruction-gotchas.md +++ b/context/reconstruction-gotchas.md @@ -188,6 +188,16 @@ published attribute table MUST be a **dense prefix** from the parent's `NextAttr AVs. Fill gaps with a shared read-only pad member. Same for a class's `AttributeID` enum. [T2] +**PROVEN LIVE (task #16):** a gap does NOT necessarily crash immediately — the garbage slot's +`entryName` may happen to point at readable heap, so a gapped table can "work" for weeks (the +MechWeapon table's 0x0D..0x12 gap shipped in task #5 and survived on heap luck). ANY change +that reshuffles allocations (the task-#16 renumber did) can then fire the latent AV — observed +as `AttributeIndexSet::Find` crashing in `WeaponCluster::WeaponCluster("PercentDone")`. A +"passes the run" verification does NOT clear a gapped table; grep every pinned +`AttributeID = 0xNN` and check the parent chain actually reaches 0xNN-1, else pad +(mechweap.cpp now static_asserts its pad base against `PoweredSubsystem::NextAttributeID`). +[T2] + ## 12. Frame-pacing trap — the binary assumes a LOCKED 60 fps (task #11) The 1995 pod ran frame-locked; reconstructed per-frame logic can carry HIDDEN frame-rate diff --git a/docs/GAUGE_COMPOSITE.md b/docs/GAUGE_COMPOSITE.md index 8ff7271..0af93ac 100644 --- a/docs/GAUGE_COMPOSITE.md +++ b/docs/GAUGE_COMPOSITE.md @@ -1,9 +1,8 @@ # Gauge / MFD render pipeline — map + dev-composite plan -**Status:** pipeline MAPPED (workflow `gauge-pipeline-map`, 2026-07); the dev composite is **planned, not -yet built**. This is the execute-ready plan for making the pod's gauges/MFDs render + be VISUALLY testable -on a dev box — the "MFD compositing on dev" follow-up the platform profile gates (see §8 / the PLATFORM -PROFILE entry in CLAUDE.md). Companion: the platform-profile scaffold (`-platform pod|dev`). +**Status (updated 2026-07-12):** the dev composite is **BUILT AND LIVE** (Milestones A/B/C done — +BT_DEV_GAUGES / BT_DEV_GAUGES_DOCK; all 6 MFD surfaces composite; 50/50 attr bindings, 0 parse-skips). +The header below preserved the original PLAN framing; see the increments + Phase logs for what landed. ## The draw model — CONFIRMED (the good news) @@ -443,18 +442,17 @@ fill the gaps (bind not-yet-needed ids to a shared read-only pad member). Resource fetch = `renderer->warehousePointer->bitMapBin.Get/Release(name)` (FUN_00442aec/00442c12). ### Deferred / follow-ups -- `HeatSink/AmbientTemperature` maps to `ambientTemperature`@0x1D4 on the AGGREGATE HeatSink-bank class - (0xBBE, ctor @4ae8d0) which is `#if 0`'d — reviving it + its own table is a separate task (Heat `A` - readout stays 0 until then). -- Reservoir shadow: `Reservoir::coolantCapacity`@0x128 shadows the inherited `thermalCapacity` the gauge - reads → `Reservoir/CoolantCapacity` reads the base default; fix = delete the shadow + write thermalCapacity - in the Reservoir ctor (bundle with the Reservoir coolant-bar verification). +- ✅ `HeatSink/AmbientTemperature` — LANDED (AggregateHeatSink 0xBBE reconstructed, commit 16f5f54; + cleared the last NULL binding; the bank is also the ambient radiator since task #9). +- ✅ Reservoir shadow — LANDED (heatfamily_reslice.cpp now writes thermalCapacity from the resource + coolantCapacity; the shadow member is gone). - ✅ **oneOfSeveralPixInt** DONE (increment 5). The Mech table now reaches 0x37 so `DuckState` resolves; `Searchlight/LightOn` still needs a Searchlight `LightOn` table to make that button dynamic. -- ✅ **map** DONE (increment 6) — renders the view wedge. FOLLOW-UPS: the spatial-query contact - classification (0 contacts today) + the stubbed pip/name/video-object infrastructure (contacts + labels) - + `SetTargetRange` (radar zoom, currently a 500m default). -- Remaining widgets (task #13): `PlayerStatus` (comm/score, 8 uses), `vehicleSubSystems` (26 uses). +- ✅ **map** DONE (increment 6) — view wedge + live contact blips; ✅ `SetTargetRange` LANDED (radar + zoom). REMAINING: the stubbed pip/name/video-object symbology (contacts draw the cross-blip stand-in, + no labels) — under reconstruction (task #17, 2026-07-12). +- ✅ Remaining widgets — BOTH LANDED: `PlayerStatus` (increment 7, commit b691863) and + `vehicleSubSystems` (see docs/VEHICLE_SUBSYSTEMS.md Phase 2). ### Remaining widgets — scope (assessed 2026-07; NOT quick registration wins) - ✅ **PlayerStatus** DONE (increment 7; commit b691863) — reconstructed the whole family (mapped by the @@ -469,11 +467,9 @@ fill the gaps (bind not-yet-needed ids to a shared read-only pad member). Pixelmap8 returns NULL (the mech-outline recolor needs the DynamicMemoryStream read API + Pixmap pixel-copy mapped) → the score/name-box/status-box render but not the recoloured mech schematic. Player resolve uses `application->GetMissionPlayer()` (WinTesla-clean) instead of the binary's raw App+0x24 "Players"-node walk. -- **vehicleSubSystems** (26 uses -- the most-used unregistered widget) — no class declared; fully - from-scratch. Config: `vehicleSubSystems(qjak0.pcc..qjak7.pcc)` (8 subsystem-group bitmaps, no attribute - bindings -- reads the mech's subsystem roster internally). Find its Make/ctor/Execute via the .data - methodDescription table (the "vehicleSubSystems" string is a .data const, not inlined in the decomp). - A focused effort (~250-300 lines across 3-5 functions). +- ✅ **vehicleSubSystems** — LANDED (docs/VEHICLE_SUBSYSTEMS.md: 7 authentic engineering panels with + real aux-screen positions via the BTGetSubsystemAuxScreen bridge). The from-scratch scoping note above + is historical. --- @@ -482,8 +478,10 @@ fill the gaps (bind not-yet-needed ids to a shared read-only pad member). Goal (user directive: "all of this needs to work as the game originally played. no shortcuts"): make every cockpit gauge read + reflect LIVE game state exactly as the 1995 pod. The `gauge-databinding-map` workflow (5 surface maps + 2 adversarial verifies) mapped every binding → subsystem/member → published-vs-missing → -authentic value → fix. **KEY REFRAME (verified against the shipped .SUB data + the binary sim): the Heat MFD -is authentically NEAR-STATIC, not a live heat-accumulation display.** `currentTemperature = +authentic value → fix. **[SUPERSEDED 2026-07-11, tasks #9/#10: the reframe below was computed at the +DEGENERATE bring-up heat scale. At the authentic 1e7-unit economy the Heat MFD is FULLY DYNAMIC — +weapons 77→2000, condensers/generators 100-1400, bank ~600; see context/gauges-hud.md.]** The original +(superseded) reframe read: the Heat MFD is authentically NEAR-STATIC — `currentTemperature = heatEnergy/thermalMass`; ThermalMass=1.39e6, so heatEnergy_init = 1.07e8 whose float32 ULP is 8.0 — a 3.5-heat laser shot ROUNDS TO ZERO (reaching Degradation 5000 needs ~2 billion shots). Coolant is DAMAGE-gated (not fire), the valve moves only on player toggle, AmbientTemperature is a hardcoded 300. So the @@ -567,7 +565,8 @@ AggregateHeatSink cleared the last, `HeatSink/AmbientTemperature`), and every co built (`[gskip]`=0). Diagnostic `BT_GAUGE_SKIP_LOG` (GAUGREND.cpp, gated) logs each unregistered primitive the dev-parse skips — the tool that pinned the last 3 (⚠ the gauge renderer builds LAZILY, so verification runs must wait long enough for BuildConfigurationFile; a too-early kill shows [gparse]=0 and looks like "not built"). Remaining gauge -work is DATA FEEDS, not widgets: the valve/message CONTROL routes (0xBD3 SubsystemMessageManager, WAVE 8) and the +work is DATA FEEDS, not widgets — [UPDATE 2026-07-12: the 0xBD3 untangle landed (task #7) and the valve +CONTROL landed (task #13, live MoveValve via the rookie-role gate); only the message half remains]: the status-message queue (StatusMessagePool) — deferred systems, cleanly marked. `SeekVoltageGraph::Execute` (@004c6934) is a cluster-child (not config-called) — its 4 Seek* attributes are the only remaining publish, non-blocking. @@ -590,8 +589,9 @@ is a cluster-child (not config-called) — its 4 Seek* attributes are the only r 6 condensers each read flow=0.166667 (=1/6).** ValveSetting→coolantFlowScale now authentic. (3) **MoveValve** — the reconstructed @4afbe0 body was a MISLABEL (MechControlsMapper mode cycler); disassembled the real handler @4ae464 (valveState cycle 1→5→50→0 + RecomputeCondenserValves) and replaced - it. DORMANT: its guard + message route go through the 0xBD3 SubsystemMessageManager (WAVE 8) → the valve - gauge shows the static authentic 1/N until 0xBD3 lands (no V-key bring-up stand-in added). + it. [UPDATE 2026-07-12: LIVE (task #13) — the guard was never the 0xBD3 messmgr; FUN_004ac9c8 is the + owning player's ROOKIE-ROLE lockout (roleClassIndex@0x274==0, task #12 bridge). MoveValve is registered + (id 4, table @0x50E52C), verified valveState 1→5 / flow 1/6→5/10; 'C' key + BT_VALVE_SLOT.] - ✅ **Combat SCORING feed** (KILLS/SCORE) — DONE (commit 34aaa7d; scoring-feed-decode workflow). The scoreboard read 0 for 4 reasons (none was handler logic — the ScoreMessage/VehicleDead/ScoreInflicted handlers were already reconstructed): (1) no producer — added the posts (per-hit ScoreInflicted at the @@ -618,10 +618,14 @@ is a cluster-child (not config-called) — its 4 Seek* attributes are the only r ### Phase 4 — DEEP/DEFERRED Byte-exact re-base of the PoweredSubsystem:HeatSink LEAF (Sensor/Emitter/Myomers) → radarPercent heat penalty -(delete the sensor.cpp:287-288 guard) + the raw-offset cluster reads; radar pip/name symbology + staticEntities; -CycleDisplayMode→ModeManager mask (activates cmArmor/cmCrit/cmHeat schematic layers); numericSpeed units. +(delete the sensor.cpp:287-288 guard — ✅ LANDED: the guard is deleted and the leaf is byte-exact, +sensor.cpp:407-413) + the raw-offset cluster reads (partially locked since — MechWeapon byte-exact); +radar pip/name symbology + staticEntities (task #17, under reconstruction 2026-07-12); +CycleDisplayMode→ModeManager mask (machinery reconstructed, gauge-layer toggle unverified [T3]); +numericSpeed units (open). ### Authentically-static (do NOT "fix") -currentTemperature ~77 (huge thermalMass); Degradation/Failure temps (fixed threshold markers); AmbientTemperature +[currentTemperature "~77 static" REMOVED 2026-07-11 — dynamic at the authentic heat economy, tasks #9/#10.] +Degradation/Failure temps (fixed threshold markers); AmbientTemperature 300; MaxRunSpeed (per-mech constant); cmArmor/cmCrit all-green (undamaged solo player — BT is PvP-only); CoolantMassLeakRate 0 on a pristine mech (damage-gated); Player RANK 1 in solo. diff --git a/engine/MUNGA_L4/L4GREND.cpp b/engine/MUNGA_L4/L4GREND.cpp index 96136ce..b357511 100644 --- a/engine/MUNGA_L4/L4GREND.cpp +++ b/engine/MUNGA_L4/L4GREND.cpp @@ -437,18 +437,15 @@ void //---------------------------------------------- Check(warehousePointer); - // DEV-COMPOSITE GUARD: the reconstructed BTL4GaugeRenderer's warehouse / gaugeImageBin - // is not fully wired (its internal chain reads garbage -> WarehouseBin::Find AVs). - // Skip the per-entity gauge-IMAGE caching when the gauge renderer is woken on a dev - // box (BT_DEV_GAUGES); the radar/secondary composite doesn't need per-mech images. - // Inert on the pod (the real warehouse is valid). FAITHFUL FOLLOW-UP: fix the - // BTL4GaugeRenderer warehouse reconstruction (likely a shadowed warehousePointer). - if (getenv("BT_DEV_GAUGES") == NULL) - { - ((L4Warehouse*)warehousePointer)->gaugeImageBin.Get( - interesting_entity->GetResourceID() - ); - } + // (task #17: the old DEV-COMPOSITE GUARD here -- "the reconstructed + // warehouse chain reads garbage / shadowed warehousePointer" -- described + // a bug fixed by later shadow sweeps; the BT ctor allocates a real + // L4Warehouse before the cfg parse and L4GaugeImage::Make is NULL-safe on + // missing type-0x12 resources. Guard removed; the radar pip cache now + // fills on dev boxes too.) + ((L4Warehouse*)warehousePointer)->gaugeImageBin.Get( + interesting_entity->GetResourceID() + ); //---------------------------------------------- // Update gaugeRenderer //---------------------------------------------- diff --git a/engine/MUNGA_L4/L4VB16.cpp b/engine/MUNGA_L4/L4VB16.cpp index 9c7874e..9fa2294 100644 --- a/engine/MUNGA_L4/L4VB16.cpp +++ b/engine/MUNGA_L4/L4VB16.cpp @@ -207,9 +207,10 @@ void SVGA16::DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int pa // gauge buffer (masks/positions from content/GAUGE/L4GAUGE.CFG MechInit @4395): // sec = radar/secondary (palette) Heat = UL Mfd2 = UC Comm = UR // Mfd1 = LL Mfd3 = LR -// monoTint < 0 -> palette-expand; else the mono tint for that MFD plane. The MFD -// content today is mostly the authored cockpit frame art (btquad/btcomm/btheat) -- -// the live widgets are BT-specific gauge classes not yet reconstructed (parse-skipped). +// monoTint < 0 -> palette-expand; else the mono tint for that MFD plane. +// (2026-07-12: the "widgets not yet reconstructed / parse-skipped" note that +// lived here is long superseded -- every config widget is registered and +// built, 0 parse-skips; see docs/GAUGE_COMPOSITE.md.) // struct BTGaugeSurfaceDesc { diff --git a/game/reconstructed/btl4rdr.cpp b/game/reconstructed/btl4rdr.cpp index 2da36cc..99c68e1 100644 --- a/game/reconstructed/btl4rdr.cpp +++ b/game/reconstructed/btl4rdr.cpp @@ -63,6 +63,13 @@ // #include +// task #17 (radar symbology): the "pip" system is the ENGINE's L4GaugeImage +// (vector-stroke shapes, resource type 0x12) cached in L4Warehouse:: +// gaugeImageBin; name labels are the Mission's prebuilt egg bitmaps. +#include // L4GaugeImage -- the pip vector shapes (fwd shim) +#include // L4Warehouse::gaugeImageBin -- the pip table +#include // Mission::GetSmallNameBitmap -- the name labels +#include // Player::playerBitmapIndex #pragma hdrstop #if !defined(BTL4RDR_HPP) @@ -104,21 +111,20 @@ static int //########################################################################### // -// A tracked entity's "video object": carries the name-bitmap id used for the -// floating label and the entity's current weapons target. (BT engine concept; -// absent from the WinTesla Entity.) +// task #17 DECODED: the "video object" at *(entity+0x190) IS Entity:: +// owningPlayer (ENTITY.h:589; resourceID is the adjacent +0x1BC) -- the radar +// reads its playerBitmapIndex (+0x1E0, the egg 'bitmapindex' key) for the +// name label and BTPlayer::objectiveMech (+0x284) for the target box. The +// old invented VideoObject class is retired. // -class VideoObject -{ -public: - int nameID; - Entity *target; -}; - +// The "pip" is the ENGINE's L4GaugeImage (L4GAUIMA.h -- FUN_0046f0c0 IS +// L4GaugeImage::Draw line-for-line), and the "pip table" is L4Warehouse:: +// gaugeImageBin (WarehouseBinOf, warehouse+0x58) reached via +// GaugeRenderer::warehousePointer (+0x4C) -- keyed by Entity::resourceID +// (BTL4.RES ships 110 type-0x12 vector shapes: every mech/vehicle/building/ +// tree). Thin facades keep the recovered draw-loop shape: // -// One radar symbol ("pip") + the per-model lookup table that owns them. -// -class Pip +class Pip // == L4GaugeImage { public: void @@ -129,14 +135,25 @@ public: int color, AffineMatrix &transform, int box_color - ); + ) + { + ((L4GaugeImage *)this)->Draw(lod_index, meters_per_pixel, + view, color, transform, box_color); + } }; -class PipTable +class PipTable // == L4Warehouse { public: Pip * - LookUpPip(int model_class_ID, int variant); + LookUpPip(ResourceDescription::ResourceID entity_resource_ID) + { + // FUN_004688ce(warehouse+0x58, entity+0x1BC, 0) -- the refcounting + // Get, exactly as the binary's per-frame loop calls it (the + // mission-end Purge reclaims; one vector shape per model). + return (Pip *)((L4Warehouse *)this)->gaugeImageBin.Get( + entity_resource_ID, False); + } }; // @@ -165,11 +182,10 @@ static PipTable * GetPipTable(GaugeRenderer *renderer); // -// Per-entity video object, name-bitmap cache, and the "labelled static entity" -// test (nav beacons etc. carry a name bitmap). +// Name-label + target resolution (task #17): the label bitmap comes from the +// owning Player's bitmapindex through the Mission's SMALL prebuilt-raster +// chain; the target from the owning BTPlayer's objectiveMech (bridge). // -static VideoObject * - GetVideoObject(Entity *entity); static BitMap * LookUpNameBitmap(int name_ID); static int @@ -222,18 +238,17 @@ void return; } - VideoObject *video_object = GetVideoObject(entity); // *(entity+400) - if (video_object == NULL) + Player *owning_player = entity->GetOwningPlayer(); // *(entity+0x190) + if (owning_player == NULL) { - nameBitmap = NULL; + nameBitmap = NULL; // unowned (dummy/effect): no label } else { - // - // Resolve the entity's name id through the global name-bitmap cache. - // - int nameID = video_object->nameID; // *(vobj+0x1E0) - nameBitmap = LookUpNameBitmap(nameID); // cache vtbl+0x0C + // player+0x1E0 playerBitmapIndex -> the Mission's SMALL name-raster + // chain (prebuilt 64x16 egg bitmaps, loaded 1-based by + // Mission::LoadNameBitmaps). + nameBitmap = LookUpNameBitmap(owning_player->playerBitmapIndex); } // @@ -790,7 +805,7 @@ void } Pip *pip = (pips != NULL) - ? pips->LookUpPip(entity->GetClassID(), 0) // FUN_004688ce(pips+0x58,entity+0x1bc,0) + ? pips->LookUpPip(entity->GetResourceID()) // FUN_004688ce(pips+0x58,entity+0x1bc,0) : NULL; if (pip != NULL) { @@ -875,7 +890,7 @@ void } Pip *pip = (pips != NULL) - ? pips->LookUpPip(entity->GetClassID(), 0) + ? pips->LookUpPip(entity->GetResourceID()) : NULL; if (pip != NULL) { @@ -891,12 +906,9 @@ void } else if (entity->GetClassID() == 0xBB9) // MechClassID -- radar contacts are mechs { - // BRING-UP: the authentic pip raster set (pips table) is not present - // in this engine build, so draw a simple cross blip at the contact's - // projected radar position (the target flashes in boxColor). Filtered - // to mechs so the ~300 dynamic world entities (effects/props) the grid - // carries don't clutter the display -- the real pip table would key the - // symbol off the model class, which is the proper follow-up. + // FALLBACK (kept, task #17): a mech whose model has no type-0x12 + // pip resource (all shipped mechs HAVE one -- this covers dev + // content) still draws the cross blip at the projected position. // // Project delta = (entity - viewpoint) directly to radar pixels: the // worldToView camera matrix bakes a scale that is wrong for a point @@ -1000,28 +1012,9 @@ void //########################################################################### //########################################################################### -// -// FUN_0046f0c0 / FUN_004688ce -- radar pip symbol + per-model table. -// -void - Pip::Draw( - Scalar /*lod_index*/, - Scalar /*meters_per_pixel*/, - GraphicsView * /*view*/, - int /*color*/, - AffineMatrix & /*transform*/, - int /*box_color*/ - ) -{ - // BEST-EFFORT: the pip raster set lives in the BT renderer's pip table, - // which is not present in this engine build. -} - -Pip * - PipTable::LookUpPip(int /*model_class_ID*/, int /*variant*/) -{ - return NULL; -} +// (task #17: the Pip::Draw / PipTable::LookUpPip stub bodies that lived here +// are retired -- both are inline facades over the engine's L4GaugeImage / +// L4Warehouse::gaugeImageBin now, see the class declarations above.) // // FUN_00417ab4 -- the gauge renderer's "operator entity" (the mech whose @@ -1040,34 +1033,39 @@ static Entity * } // -// renderer+0x4C -- the BT pip table. Not present on the WinTesla renderer. +// renderer+0x4C -- GaugeRenderer::warehousePointer: the L4Warehouse whose +// gaugeImageBin (+0x58) is the pip table (task #17; the "not present on +// WinTesla" note was wrong -- the facility is engine T0, L4WRHOUS.h). // static PipTable * - GetPipTable(GaugeRenderer * /*renderer*/) + GetPipTable(GaugeRenderer *renderer) { - return NULL; + return (renderer != NULL) ? (PipTable *)renderer->warehousePointer : NULL; } // -// *(entity+400) -- the entity's video object (name id + current target). -// -static VideoObject * - GetVideoObject(Entity * /*entity*/) -{ - return NULL; -} - -// -// The global name-bitmap cache (DAT_004efc94 App singleton, +0xC8 graphics). +// The "global name-bitmap cache" DECODED (task #17): DAT_004efc94+0xC8 is +// Application::currentMission, and the cache is the Mission's SMALL +// name-bitmap VChain (prebuilt 64x16 egg rasters keyed 1-based by the egg +// 'bitmapindex'). All T0 (MISSION.h / APP.h). // static BitMap * - LookUpNameBitmap(int /*name_ID*/) + LookUpNameBitmap(int name_ID) { - return NULL; + Mission *mission = (application != NULL) + ? application->GetCurrentMission() : NULL; + if (mission == NULL || name_ID <= 0) + { + return NULL; + } + return mission->GetSmallNameBitmap(name_ID); } // -// entity+0x21C -- a labelled entity's name id. +// entity+0x21C -- Landmark::landmarkID (cultural.h; the static loop's label +// key into the LARGE chain). DORMANT in shipped content: no runtime writer +// exists (the id was baked by the 1995 map tool) and no [landmarks] section +// exists in any egg -- kept faithful-but-inert [T1]. // static int GetNameID(Entity * /*entity*/) @@ -1076,7 +1074,9 @@ static int } // -// FUN_0041a1a4 -- IsDerivedFrom(LabelledEntityClass). +// FUN_0041a1a4 vs 0x4e70c4 -- IsDerivedFrom("Landmark") (task #17 decode: +// the "LabelledEntityClass" IS the Landmark class, parent CulturalIcon). +// Dormant with GetNameID above (no landmark content ships). // static Logical IsLabelledEntity(Entity * /*entity*/) @@ -1090,8 +1090,12 @@ static Logical static Entity * GetTarget(Entity *owner) { - VideoObject *video_object = (owner != NULL) ? GetVideoObject(owner) : NULL; - return (video_object != NULL) ? video_object->target : NULL; + // *(*(owner+0x190)+0x284) -- the owning BTPlayer's objectiveMech (the + // designated target; complete-type bridge, btplayer.cpp). + extern Entity *BTPlayerObjectiveMechOf(void *player); + Player *owning_player = (owner != NULL) ? owner->GetOwningPlayer() : NULL; + return (owning_player != NULL) + ? BTPlayerObjectiveMechOf(owning_player) : NULL; } // diff --git a/game/reconstructed/btplayer.cpp b/game/reconstructed/btplayer.cpp index a817add..22bcea6 100644 --- a/game/reconstructed/btplayer.cpp +++ b/game/reconstructed/btplayer.cpp @@ -1492,3 +1492,19 @@ int BTPlayerRoleLocksAdvanced(void *owner_mech) return 0; // dev-permissive [T3] return (player->roleClassIndex == 0) ? 1 : 0; // player+0x274 == 0 } + + +//############################################################################# +// BTPlayerObjectiveMechOf -- complete-type bridge (task #17) +// +// The radar map's target resolve: *(*(owner+0x190)+0x284) = the owning +// BTPlayer's objectiveMech (the designated target; the same slot the +// power-routing valve gate reads past). btl4rdr.cpp can't include +// btplayer.hpp (TU type collisions), so it calls through this bridge. +// +Entity *BTPlayerObjectiveMechOf(void *player) +{ + if (player == 0) + return 0; + return (Entity *)((BTPlayer *)player)->GetObjectiveMech(); // @0x284 +} diff --git a/game/reconstructed/mechweap.cpp b/game/reconstructed/mechweap.cpp index 01ca3b2..bf713fe 100644 --- a/game/reconstructed/mechweap.cpp +++ b/game/reconstructed/mechweap.cpp @@ -108,15 +108,46 @@ Simulation::AttributeIndexSet MechWeapon::AttributeIndex; // legacy empty member // gauge data-binding wave: OutputVoltage/PercentDone -> rechargeLevel (1.0 == // ready; ComputeOutputVoltage/the charge state machine drive it, so both animate // as the weapon recharges). Chained to PoweredSubsystem's dense index. +// task #16: the ID GAP between our parent chain (ends 0x0D) and the pinned +// binary ids MUST be padded -- see the enum note. Locked so a future parent +// publish shows up here instead of silently double-assigning ids: +static_assert((int)PoweredSubsystem::NextAttributeID + == (int)MechWeapon::MechWeaponPadFirstAttributeID, + "MechWeapon pad base != PoweredSubsystem::NextAttributeID -- re-count the pads"); + const MechWeapon::IndexEntry MechWeapon::AttributePointers[]= { - // The authentic entry (binary table @0x511890): the streamed controls maps - // bind the pod fire buttons directly onto this Scalar -- fireImpulse IS the - // binary's "TriggerState" (the edge-detected fire channel destination). - ATTRIBUTE_ENTRY(MechWeapon, TriggerState, fireImpulse), // @0x31C id 0x13 - ATTRIBUTE_ENTRY(MechWeapon, OutputVoltage, rechargeLevel), // @0x320 (charge/ready level) - ATTRIBUTE_ENTRY(MechWeapon, PercentDone, rechargeLevel) // @0x320 (recharge dial fraction) + // task #16: the FULL binary table @0x511890 (eleven entries) + the + // port-extra OutputVoltage alias. Every member offset matches the + // binary's (layout static_assert-locked); TriggerState stays the streamed + // fire-button binding (id 0x13 PINNED). + // + // The five PADS fill our chain-vs-binary id gap (0x0D..0x11) with valid, + // named, never-bound entries (the binary's own ids here are the powered/ + // aux-screen family we don't publish); target = rechargeLevel, harmless. + { (int)MechWeapon::MechWeaponPadFirstAttributeID + 0, "MechWeaponPad0D", + (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, + { (int)MechWeapon::MechWeaponPadFirstAttributeID + 1, "MechWeaponPad0E", + (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, + { (int)MechWeapon::MechWeaponPadFirstAttributeID + 2, "MechWeaponPad0F", + (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, + { (int)MechWeapon::MechWeaponPadFirstAttributeID + 3, "MechWeaponPad10", + (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, + { (int)MechWeapon::MechWeaponPadFirstAttributeID + 4, "MechWeaponPad11", + (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, + ATTRIBUTE_ENTRY(MechWeapon, PercentDone, rechargeLevel), // 0x12 @0x320 + ATTRIBUTE_ENTRY(MechWeapon, TriggerState, fireImpulse), // 0x13 @0x31C + ATTRIBUTE_ENTRY(MechWeapon, DistanceToTarget, rangeToTarget), // 0x14 @0x324 + ATTRIBUTE_ENTRY(MechWeapon, TargetWithinRange, targetWithinRange), // 0x15 @0x34C + ATTRIBUTE_ENTRY(MechWeapon, WeaponRange, effectiveRange), // 0x16 @0x328 (heat-degraded) + ATTRIBUTE_ENTRY(MechWeapon, PipPosition, pipPosition), // 0x17 @0x338 + ATTRIBUTE_ENTRY(MechWeapon, PipColor, pipColor), // 0x18 @0x33C + ATTRIBUTE_ENTRY(MechWeapon, PipExtendedRange, pipExtendedRange), // 0x19 @0x348 + ATTRIBUTE_ENTRY(MechWeapon, EstimatedReadyTime, estimatedReadyTime),// 0x1A @0x330 + ATTRIBUTE_ENTRY(MechWeapon, RearFiring, usesExternalModel), // 0x1B @0x334 + ATTRIBUTE_ENTRY(MechWeapon, WeaponState, weaponAlarm), // 0x1C @0x350 + ATTRIBUTE_ENTRY(MechWeapon, OutputVoltage, rechargeLevel) // 0x1D (port alias) }; MechWeapon::AttributeIndexSet& @@ -303,7 +334,7 @@ MechWeapon::MechWeapon( previousFireImpulse = 0.0f; // 0x3A4 recoil = 0.0f; // 0x3E8 useConfiguredPip = (usesExternalModel == 0); // 0x3E0 - pipState = 0; // 0x330 + estimatedReadyTime = 0; // 0x330 (binary attr "EstimatedReadyTime") Check_Fpu(); } diff --git a/game/reconstructed/mechweap.hpp b/game/reconstructed/mechweap.hpp index a0fe84c..446fccd 100644 --- a/game/reconstructed/mechweap.hpp +++ b/game/reconstructed/mechweap.hpp @@ -168,9 +168,38 @@ class CockpitHud; // a few dense-index slots (Build sizes by max id) -- harmless. // OutputVoltage/PercentDone are port gauge additions // (name-resolved) renumbered after it. - TriggerStateAttributeID = 0x13, - OutputVoltageAttributeID, // 0x14 - PercentDoneAttributeID, // 0x15 + // task #16 (attribute parity): the FULL binary table @0x511890, + // ids renumbered to binary truth. PercentDone was 0x15 and the + // port-extra OutputVoltage alias squatted 0x14 -- both collided + // with the binary's DistanceToTarget/TargetWithinRange ids. + // (Numeric ids matter to the streamed type-6 maps -- which bind + // ONLY TriggerState 0x13, unchanged -- and to binary parity; + // gauge configs bind by NAME.) + // + // THE ID GAP MUST BE PADDED (task #16 crash): our reconstruction's + // parent chain ends at PoweredSubsystem::NextAttributeID == 0x0D + // (the binary's reaches 0x12 -- we publish fewer base attrs), and + // AttributeIndexSet::Build leaves the slots between UNINITIALIZED + // -- Find() strcmps EVERY slot, so a gap slot's garbage entryName + // is a latent AV (gotcha #11; the old pinned table survived on + // heap luck -- the renumber reshuffled allocations and it fired + // live in WeaponCluster's "PercentDone" resolve). Pads below fill + // ids 0x0D..0x11 (the mech.cpp attrPad idiom). + MechWeaponPadFirstAttributeID = 0x0D, // == PoweredSubsystem::NextAttributeID (locked below) + PercentDoneAttributeID = 0x12, // @0x320 rechargeLevel + TriggerStateAttributeID, // 0x13 @0x31C (PINNED -- the fire-button binding) + DistanceToTargetAttributeID, // 0x14 @0x324 rangeToTarget + TargetWithinRangeAttributeID, // 0x15 @0x34C + WeaponRangeAttributeID, // 0x16 @0x328 -- the HEAT-DEGRADED effectiveRange, + // not the configured max (binary truth) + PipPositionAttributeID, // 0x17 @0x338 + PipColorAttributeID, // 0x18 @0x33C + PipExtendedRangeAttributeID, // 0x19 @0x348 + EstimatedReadyTimeAttributeID, // 0x1A @0x330 + RearFiringAttributeID, // 0x1B @0x334 + WeaponStateAttributeID, // 0x1C @0x350 weaponAlarm + OutputVoltageAttributeID, // 0x1D PORT-EXTRA alias -> rechargeLevel (the + // engineering clusters bind it by name) NextAttributeID }; private: @@ -361,8 +390,11 @@ class CockpitHud; Scalar rangeToTarget; // @0x324 distance to current target (-1 if none) Scalar effectiveRange; // @0x328 (1 - heatLoad) * weaponRange (heat-degraded) Scalar weaponRange; // @0x32C resource WeaponRange (configured max) - int pipState; // @0x330 reset 0 (TODO: name -- pip/active state) - Logical usesExternalModel; // @0x334 resource model-name compare flag (best-effort) + int estimatedReadyTime; // @0x330 reset 0 (binary attr 0x1A "EstimatedReadyTime" + // names this slot -- resolves the old pipState TODO) + Logical usesExternalModel; // @0x334 the "EXT" model-name marker; the binary attr + // 0x1B names this slot "RearFiring" (an EXT/rear-mount + // weapon fires backward) int pipPosition; // @0x338 resource PipPosition RGBColor pipColor; // @0x33C resource PipColor (3 floats, init -1,-1,-1) int pipExtendedRange; // @0x348 resource PipExtendedRange diff --git a/game/reconstructed/myomers.cpp b/game/reconstructed/myomers.cpp index 18ac5c0..5eced4c 100644 --- a/game/reconstructed/myomers.cpp +++ b/game/reconstructed/myomers.cpp @@ -75,17 +75,47 @@ Derivation "Myomers" ); -Receiver::MessageHandlerSet - Myomers::MessageHandlers; +// task #14: the REAL shared data. The old empty static +// MessageHandlers/AttributeIndex meant (a) no inherited message handlers +// (the powered ids 4-8 among them), (b) an EMPTY UNCHAINED attribute index -- +// even CurrentTemperature/InputVoltage resolved NULL on Myomers instances, so +// the Myomer engineering panel's cluster children were silently blank. Both +// now chain PoweredSubsystem's sets, and the SEVEN binary attributes +// (IndexEntry table @00511588) are published. Function-local statics per the +// static-init-order rule (reconstruction-gotchas #9). +Receiver::MessageHandlerSet& + Myomers::GetMessageHandlers() +{ + static Receiver::MessageHandlerSet messageHandlers( + PoweredSubsystem::GetMessageHandlers()); // copy-inherit (ids 4-8) + return messageHandlers; +} -Myomers::AttributeIndexSet - Myomers::AttributeIndex; +Myomers::AttributeIndexSet& + Myomers::GetAttributeIndex() +{ + static const Myomers::IndexEntry entries[]= + { + ATTRIBUTE_ENTRY(Myomers, SpeedEffect, speedEffect), // 0x12 @0x31C + ATTRIBUTE_ENTRY(Myomers, CurrentSeekVoltageIndex, currentSeekVoltageIndex), // 0x13 @0x320 + ATTRIBUTE_ENTRY(Myomers, RecommendedSeekVoltageIndex, recommendedSeekVoltageIndex), // 0x14 @0x324 + ATTRIBUTE_ENTRY(Myomers, MinSeekVoltageIndex, minSeekVoltageIndex), // 0x15 @0x328 + ATTRIBUTE_ENTRY(Myomers, MaxSeekVoltageIndex, maxSeekVoltageIndex), // 0x16 @0x32C + ATTRIBUTE_ENTRY(Myomers, SeekVoltage, seekVoltage), // 0x17 @0x330 (array base) + ATTRIBUTE_ENTRY(Myomers, OutputVoltage, outputVoltage) // 0x18 @0x344 + }; + static Myomers::AttributeIndexSet attributeIndex( + ELEMENTS(entries), entries, + PoweredSubsystem::GetAttributeIndex() + ); + return attributeIndex; +} Myomers::SharedData Myomers::DefaultData( &Myomers::ClassDerivations, - Myomers::MessageHandlers, - Myomers::AttributeIndex, + Myomers::GetMessageHandlers(), + Myomers::GetAttributeIndex(), Myomers::StateCount ); diff --git a/game/reconstructed/myomers.hpp b/game/reconstructed/myomers.hpp index e797c89..9bf14ed 100644 --- a/game/reconstructed/myomers.hpp +++ b/game/reconstructed/myomers.hpp @@ -111,7 +111,11 @@ class Mech; public: static Derivation ClassDerivations; static SharedData DefaultData; // @0051154c - static Receiver::MessageHandlerSet MessageHandlers; + // task #14: accessor idiom (function-local statics) -- the old empty + // static MessageHandlers/AttributeIndex members starved the class of + // BOTH its own 7 binary attrs AND every inherited resolve (temps, + // InputVoltage) -> the Myomer engineering panel's children fed NULL. + static Receiver::MessageHandlerSet& GetMessageHandlers(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Streamed model resource. @@ -162,9 +166,8 @@ class Mech; }; private: - static const IndexEntry AttributePointers[]; // @00511588 protected: - static AttributeIndexSet AttributeIndex; + static AttributeIndexSet& GetAttributeIndex(); // @00511588 table, task #14 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support diff --git a/game/reconstructed/powersub.cpp b/game/reconstructed/powersub.cpp index c45f3bd..d134913 100644 --- a/game/reconstructed/powersub.cpp +++ b/game/reconstructed/powersub.cpp @@ -849,171 +849,19 @@ int //########################################################################### //########################################################################### -// Myomers +// (Myomers mislabel -- RETIRED, task #14) //########################################################################### //########################################################################### - -//############################################################################# -// Shared Data Support // -Myomers::SharedData - Myomers::DefaultData( - Myomers::GetClassDerivations(), - Myomers::GetMessageHandlers(), - Myomers::GetAttributeIndex(), - Myomers::StateCount - ); - -Derivation* - Myomers::GetClassDerivations() - { - static Derivation classDerivations(PoweredSubsystem::GetClassDerivations(), "Myomers"); - return &classDerivations; - } - +// The "Myomers" block that lived here (ctor @4b1d18, classID 0xBC3, +// outputVoltage/powered/voltageAvailable) was a MISLABEL of Sensor (real +// reconstruction: sensor.cpp). Its Myomers::DefaultData / ctor / dtor / +// TestInstance / MyomersSimulation / ResetToInitialState / HandleMessage / +// CreateStreamedSubsystem definitions ODR-DUPLICATED the REAL Myomers +// (myomers.cpp, classID 0xBC6) -- /FORCE picked the winner by link order, +// so which SharedData live Myomers instances got was link-order-dependent +// (the same archetype as the fixed Condenser stub dedup). Retired whole. // -// @004b1d18 -- master myomer bundle registers a separate Performance and a -// status-flag bit (0x8); copies skip the live simulation. -// -Myomers::Myomers( - Mech *owner, - int subsystem_ID, - SubsystemResource *subsystem_resource, - SharedData &shared_data -): - PoweredSubsystem(owner, subsystem_ID, subsystem_resource, /*shared_data=*/DefaultData) // FUN_004b0f74(..,&DAT_0050fa1c,0,0) -{ - outputVoltage = 1.0f; // this[0xc7] - powered = 0; // this[0xc8] - voltageAvailable = 0; // this[0xc9] - simulationFlags |= 0x8; // this[0xa] |= 8 - - if ((subsystem_resource->subsystemFlags & SegmentCopyMask) != 4) // (flags & 0xC) != 4 - { - SetPerformance((Performance)&Myomers::MyomersSimulation); // PTR @0050fa94 - } -} - -// -// @004b1d90 -// -Myomers::~Myomers() -{ - Check(this); - BT_ClearMyomers((Mech *)owner); // *(this[0x34]+0x374) = 0 - Check_Fpu(); -} - -Logical Myomers::TestClass(Mech &) { return True; } -Logical Myomers::TestInstance() const // @004b1e18 -{ - return IsDerivedFrom(*GetClassDerivations()); // FUN_0041a1a4(..,0x50fa2c) -} - -// -// @004b1c4c -- compute the actuator drive voltage available to the locomotion -// model from the powering generator, throttled by heat damage and gated by -// the electrical state. -// -void - Myomers::MyomersSimulation(Scalar time_slice) -{ - PoweredSubsystem::PoweredSubsystemSimulation(time_slice); // FUN_004b0bd0 - - Generator *source = (Generator *)voltageSource.Resolve(); // this[0x38] linked source - outputVoltage = RatedVoltageRef - source->MeasuredVoltage(); // 1.0 - *(source+0x158) - - if (simulationState == 1) // this[0x10] @0x40 - { - outputVoltage = 0.0f; - powered = 0; - } - else - { - powered = 1; - } - - if (electricalStateAlarm.GetLevel() == Ready) // this[0x9e] == 4 - { - voltageAvailable = 0; - } - else - { - voltageAvailable = 1; - outputVoltage = 0.0f; - } - - // - // Heat damage (HeatSink heat-state @0x184) throttles the drive. - // - switch (heatAlarm.GetLevel()) // this+0x184 (heat-state inside heatAlarm@0x170+0x14) - { - case HeatSink::NormalHeat: // 0 - powered = 1; - break; - case HeatSink::DegradationHeat: // 1 - outputVoltage *= DegradedDriveScale; // *= 0.5 - powered = 1; - break; - case HeatSink::FailureHeat: // 2 - outputVoltage = 0.0f; - powered = 0; - break; - } -} - -// -// @004b1c18 -- reset clears the myomer output state, then chains to the base. -// -void - Myomers::ResetToInitialState(Logical powered_flag) -{ - if (powered_flag) - { - outputVoltage = 1.0f; // this[0xc7] - powered = 0; // this[0xc8] - voltageAvailable = 0; // this[0xc9] - } - PoweredSubsystem::ResetToInitialState(powered_flag); // FUN_004b0e6c -} - -// -// @004b1bfc -- straight delegation to the base. -// -Logical - Myomers::HandleMessage(int message) -{ - return PoweredSubsystem::HandleMessage(message); // FUN_004b0efc -} - -// -// @004b1dcc -- no extra resource fields; just tags the record as Myomers. -// -int - Myomers::CreateStreamedSubsystem( - NotationFile *model_file, - const char *model_name, - const char *subsystem_name, - SubsystemResource *subsystem_resource, - NotationFile *subsystem_file, - const ResourceDirectories *directories, - int passes - ) -{ - if ( - !PoweredSubsystem::CreateStreamedSubsystem( // FUN_004b13ac - model_file, model_name, subsystem_name, - subsystem_resource, subsystem_file, directories, passes - ) - ) - { - return False; - } - subsystem_resource->subsystemModelSize = 0x190; - subsystem_resource->classID = RegisteredClass::MyomersClassID; // 0x0bc3, resource+0x20 - return True; -} - //########################################################################### //########################################################################### @@ -1509,8 +1357,11 @@ Subsystem *CreatePoweredSubsystem(Mech *owner, int id, void *seg) // Bridge for the vehicleSubSystems gauge factory (btl4gau2.cpp). The engineering- // screen assignment lives on PoweredSubsystem (auxScreenNumber / auxScreenPlacement // / auxScreenLabel, resource +0x104/+0x108/+0x10C). Reading it from btl4gau2.cpp -// via a raw offset (sub+0x1dc) is wrong because the reconstructed heat-leaf branch -// is not byte-exact -- so the gauge factory calls this bridge, which casts through +// via a raw offset (sub+0x1dc) is wrong because raw offsets on our compiled +// objects are the databinding trap (2026-07-12: the "heat-leaf not byte-exact" +// wording is stale -- the leaf IS byte-exact since the sensor re-base, +// sensor.cpp:407-413 -- but the bridge stays: named members are the rule) +// -- so the gauge factory calls this bridge, which casts through // the real PoweredSubsystem type and returns the NAMED fields. Returns False for // non-PoweredSubsystem-derived subsystems (== the FUN_0041a1a4 / 0x50f4bc type // filter the vehicleSubSystems Make applies before dispatch). diff --git a/game/reconstructed/powersub.hpp b/game/reconstructed/powersub.hpp index 8d36603..2c8009a 100644 --- a/game/reconstructed/powersub.hpp +++ b/game/reconstructed/powersub.hpp @@ -296,81 +296,13 @@ class Generator; static_assert(offsetof(PoweredSubsystem, thermalResistivityCoefficient)== 0x30C, "PoweredSubsystem::thermalResistivityCoefficient @0x30C"); static_assert(sizeof(PoweredSubsystem) == 0x31C, "sizeof(PoweredSubsystem) 0x31C"); }; - -//########################################################################### -//############################# Myomers ################################# -//########################################################################### -// -// The mech's artificial-muscle bundle: a PoweredSubsystem whose output -// voltage (and hence the actuator drive available to the locomotion model) -// tracks the powering generator and is throttled back by heat damage. -// -// vtable @0050fb0c, ctor @004b1d18, dtor @004b1d90, shared data @0050fa1c. -// RegisteredClass::MyomersClassID == 0x0bc3. -// - class Myomers: - public PoweredSubsystem - { - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Shared Data Support - // - public: - static Derivation *GetClassDerivations(); // @0050fa2c - static SharedData DefaultData; // @0050fa1c - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Test Class Support - // - public: - static Logical - TestClass(Mech&); - Logical - TestInstance() const; // @004b1e18 - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Construction and Destruction - // - public: - typedef PoweredSubsystem::SubsystemResource SubsystemResource; - - Myomers( - Mech *owner, - int subsystem_ID, - SubsystemResource *subsystem_resource, - SharedData &shared_data = DefaultData - ); // @004b1d18 - ~Myomers(); // @004b1d90 - - static int - CreateStreamedSubsystem( // @004b1dcc - NotationFile *model_file, - const char *model_name, - const char *subsystem_name, - SubsystemResource *subsystem_resource, - NotationFile *subsystem_file, - const ResourceDirectories *directories, - int passes = 1 - ); - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Simulation / overrides - // - public: - void - MyomersSimulation(Scalar time_slice); // @004b1c4c (Performance, PTR @0050fa94) - void - ResetToInitialState(Logical powered = True); // slot 10, @004b1c18 - Logical - HandleMessage(int message); // slot 9, @004b1bfc (-> PoweredSubsystem) - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Local data. - // - protected: - Scalar outputVoltage; // @0x31C init 1.0f -- drive available to the locomotion model - int powered; // @0x320 init 0 -- True while the generator is on-line - int voltageAvailable; // @0x324 init 0 -- True while electricalState == Ready - }; +// (task #14: the "Myomers" class that lived here -- vtable 0050fb0c / ctor +// @4b1d18 / classID 0xBC3, members outputVoltage/powered/voltageAvailable -- +// was a MISLABEL of **Sensor** (the real reconstruction is sensor.cpp/hpp; +// the real Myomers, classID 0xBC6, is myomers.cpp/hpp). Its definitions +// ODR-DUPLICATED ?DefaultData@Myomers@@ etc. against the real class, and +// /FORCE silently picked the winner by link order -- the real Myomers could +// receive this block's SharedData or vice versa. RETIRED whole.) //########################################################################### //############################# Generator ############################## diff --git a/game/reconstructed/thermalsight.cpp b/game/reconstructed/thermalsight.cpp index 52d91af..a54e7db 100644 --- a/game/reconstructed/thermalsight.cpp +++ b/game/reconstructed/thermalsight.cpp @@ -52,14 +52,28 @@ Derivation Receiver::MessageHandlerSet ThermalSight::MessageHandlers; -ThermalSight::AttributeIndexSet - ThermalSight::AttributeIndex; +// task #16: publish the binary attr table @00511270 -- one entry, +// {LightState (id 4) -> thermalActive @0x1D8} (offset encoded 0x1D9 = 0x1D8|1). +// Function-local statics per the static-init-order rule. +ThermalSight::AttributeIndexSet& + ThermalSight::GetAttributeIndex() +{ + static const ThermalSight::IndexEntry entries[]= + { + ATTRIBUTE_ENTRY(ThermalSight, LightState, thermalActive) // id 4 @0x1D8 + }; + static ThermalSight::AttributeIndexSet attributeIndex( + ELEMENTS(entries), entries, + PowerWatcher::GetAttributeIndex() + ); + return attributeIndex; +} ThermalSight::SharedData ThermalSight::DefaultData( // @00511228 &ThermalSight::ClassDerivations, ThermalSight::MessageHandlers, - ThermalSight::AttributeIndex, + ThermalSight::GetAttributeIndex(), ThermalSight::StateCount ); diff --git a/game/reconstructed/thermalsight.hpp b/game/reconstructed/thermalsight.hpp index 0b18811..52aecd6 100644 --- a/game/reconstructed/thermalsight.hpp +++ b/game/reconstructed/thermalsight.hpp @@ -109,7 +109,7 @@ private: static const IndexEntry AttributePointers[]; // @00511270 protected: - static AttributeIndexSet AttributeIndex; + static AttributeIndexSet& GetAttributeIndex(); // @00511270 table (task #16) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local data.