Gauges: the CLOSEOUT wave -- radar pips, Myomers dedup, attribute parity, ledger sweep (tasks #14-#17)
TASK #17 -- AUTHENTIC RADAR SYMBOLOGY (the cross-blip stand-in retired). The recon proved the 'missing pip raster set' never existed as BT game code: pips are the ENGINE's L4GaugeImage vector-stroke system (T0 source in tree, L4GAUIMA.cpp == FUN_0046f0c0 line-for-line), the 'pip table' is L4Warehouse:: gaugeImageBin keyed by Entity::resourceID, and BTL4.RES ships 110 type-0x12 shapes (every mech/vehicle/building/tree). The six btl4rdr stubs are wired to the real facilities: contacts draw their authentic model silhouettes with LOD selection, the target gets the binary's 4px-inflated box highlight, and player-name labels resolve via Mission::GetSmallNameBitmap (the prebuilt 64x16 egg rasters keyed by the player's bitmapindex). DECODED: the invented 'VideoObject' was Entity::owningPlayer all along (+0x190; nameID = playerBitmapIndex@0x1E0, target = BTPlayer::objectiveMech@0x284 -- new bridge BTPlayerObjectiveMechOf); the 'LabelledEntity' class is Landmark (cultural.h; label path dormant -- no landmark content ships, no runtime landmarkID writer). The L4GREND BT_DEV_GAUGES warehouse guard is removed (its AV had a different culprit, below); resource type 18 corrected to GaugeImageStream in decomp-reference (was mislabeled 'ModelList'). TASK #14 -- the Myomers ODR duplicate ELIMINATED: the powersub.cpp/hpp 'Myomers' (classID 0xBC3 -- actually Sensor) is retired whole; it duplicated ?DefaultData@Myomers@@ against the real class (dumpbin-verified) and /FORCE picked the winner by link order. The real Myomers (0xBC6) now chains PoweredSubsystem's handler set (ids 4-8) and publishes its SEVEN binary attributes (@00511588: SpeedEffect/Current/Recommended/Min/MaxSeekVoltage- Index/SeekVoltage/OutputVoltage) -- the old empty unchained index starved the Myomer engineering panel of every resolve. TASK #16 -- attribute parity: MechWeapon publishes the FULL binary table @0x511890 (11 entries; ids renumbered to binary truth -- the port aliases had squatted the binary's DistanceToTarget/TargetWithinRange ids; the streamed TriggerState 0x13 binding unchanged). Binary names resolved two TODO members: pipState -> estimatedReadyTime (attr 0x1A), and the EXT-model flag is the binary's RearFiring (0x1B). ThermalSight LightState published. HUD (offset conflicts) + missile-side tables (id encoding suspect) documented for a re-dump instead of publishing blind. THE CRASH THIS EXPOSED [T2, cdb-verified]: gotcha #11's dense-table gap is a LATENT AV, not a guaranteed one -- the old table's 0x0D..0x12 gap (task #5) survived on heap luck; the renumber reshuffled allocations and Find() AV'd on a garbage entryName in WeaponCluster's PercentDone resolve. Fixed with five named PAD entries (the mech.cpp attrPad idiom) + a static_assert locking the pad base to PoweredSubsystem::NextAttributeID. Gotcha #11 amended with the proof. TASK #15 -- stale-ledger sweep: GAUGE_COMPOSITE ('composite not yet built', Reservoir shadow, PlayerStatus/vehicleSubSystems 'remaining', valve-dormant- until-0xBD3, sensor guard, the superseded 'Heat MFD near-static' reframe -- all banner-corrected), gauges-hud frontmatter, L4VB16 + powersub comments. Verified live: 50/50 config attribute bindings resolve, 0 NULLs, 0 parse skips, mech spawns and simulates 31/31 subsystems, no cross-blip fallbacks, the pip cache fills through entity registration without the old guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
80cadc98c7
commit
dd27238ceb
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 `<Name>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
|
||||
`<Name>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
|
||||
|
||||
+31
-27
@@ -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.
|
||||
|
||||
@@ -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
|
||||
//----------------------------------------------
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -63,6 +63,13 @@
|
||||
//
|
||||
|
||||
#include <bt.hpp>
|
||||
// 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 <L4GAUIMA.hpp> // L4GaugeImage -- the pip vector shapes (fwd shim)
|
||||
#include <L4WRHOUS.hpp> // L4Warehouse::gaugeImageBin -- the pip table
|
||||
#include <MISSION.hpp> // Mission::GetSmallNameBitmap -- the name labels
|
||||
#include <PLAYER.hpp> // 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<L4GaugeImage>, 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;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+14
-163
@@ -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).
|
||||
|
||||
@@ -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 ##############################
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
private:
|
||||
static const IndexEntry AttributePointers[]; // @00511270
|
||||
protected:
|
||||
static AttributeIndexSet AttributeIndex;
|
||||
static AttributeIndexSet& GetAttributeIndex(); // @00511270 table (task #16)
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Local data.
|
||||
|
||||
Reference in New Issue
Block a user