docs: record the gauge attribute-wave (mechanism + 4 increments) + durable RE tools
Document the cockpit-gauge attribute-pointer system in GAUGE_COMPOSITE.md (§Attribute wave) and CLAUDE.md §10: how a Subsystem/Attribute or bare-Attribute binding resolves (ParseAttribute -> FindSubsystem/GetName -> GetAttributePointer -> activeAttributeIndex), what a class must publish (AttributeID enum + AttributePointers[] + GetAttributeIndex chained + DefaultData wiring), the DENSE-TABLE HAZARD (Build leaves gap slots uninitialized, Find strcmps every slot -> tables must be a dense prefix), and the base-primitive vs BT-specific widget split. Records the 4 committed increments (HeatSink table, Mech table, vertBar, segmentArcRatio) and the follow-ups (oneOfSeveralPixInt/map/PlayerStatus/ vehicleSubSystems, the #if0'd HeatSink-bank AmbientTemperature, the Reservoir coolantCapacity shadow). Promote the two reverse-engineering helpers from the session scratchpad into tools/ (durable): disas2.py (capstone + PE parse -- recovers x87 float math Ghidra drops; FUN_004dcd94=round, FUN_004dcd00=fabs) and vtdump.py (dump a class vtable to find an override the assert-anchored decomp never exported, e.g. SegmentArcRatio::Execute). Doc references updated scratchpad/ -> tools/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a2ee1181e6
commit
c2c4ab1f67
@@ -344,3 +344,81 @@ reads the public `currentTemperature`). Verified: parses, builds, all subsystems
|
||||
`segmentArcRatio` speed/`GeneratorCluster` — need `AttributePointers[]` on Mech/HeatableSubsystem, a separate
|
||||
pass); the XL items (`map`/`vehicleSubSystems`/`PlayerStatus`) last. The POD path needs the FULL 23-entry
|
||||
table with real ctors (the dev tolerant-skip is the on-ramp, not the finish).
|
||||
|
||||
## Attribute wave DONE (2026-07) — the cockpit numeric/bar/arc gauges read LIVE data
|
||||
|
||||
The gauge widgets bind to game state by NAME through the engine attribute-pointer system, mapped by the
|
||||
`attr-wave-decomp-map` workflow and implemented as 4 committed increments. **This is the mechanism every
|
||||
data-driven gauge depends on — read it before reconstructing any further widget.**
|
||||
|
||||
### How a gauge binding resolves (engine, already complete)
|
||||
The config binds either `Subsystem/Attribute` (e.g. `HeatSink/CurrentTemperature`) or a bare `Attribute`
|
||||
(e.g. `LinearSpeed`). `GAUGREND.cpp ParseAttribute` (~2130) splits on `/`:
|
||||
- `Subsystem/Attr` → `entity->FindSubsystem(subName)` (ENTITY.cpp:631 — `stricmp` on each subsystem's
|
||||
`GetName()`) → `subsystem->GetAttributePointer(attrName)`.
|
||||
- bare `Attr` → `entity->GetAttributePointer(name)`.
|
||||
`Simulation::GetAttributePointer(name)` (SIMULATE.cpp:427) = `GetSharedData()->activeAttributeIndex->
|
||||
Find(name)` then `&(this->*attr)`.
|
||||
|
||||
### What a class must publish (the per-class work)
|
||||
1. An `<Name>AttributeID` enum chained from the parent (`= Parent::NextAttributeID … NextAttributeID`).
|
||||
2. `static const IndexEntry AttributePointers[]` via `ATTRIBUTE_ENTRY(Class,Name,member)` (SIMULATE.h:284).
|
||||
3. `static AttributeIndexSet& GetAttributeIndex()` chained to the parent (pattern: MOVER.cpp:83).
|
||||
4. The class's `DefaultData`/`SharedData` ctor must PASS `GetAttributeIndex()` (MOVER.cpp:28-35) — this
|
||||
sets `activeAttributeIndex`. Bind `ATTRIBUTE_ENTRY` to a REAL named member (compiled ptr-to-member;
|
||||
NEVER a raw binary offset — the compiled layout != binary). The engine `Mover`/`JointedMover`/`Entity`
|
||||
tables are dense and real; chain to them.
|
||||
|
||||
### ⚠ DENSE-TABLE HAZARD (systemic-bug class — verified in SIMULATE.cpp:565/663)
|
||||
`AttributeIndexSet::Build` sizes the merged index to `max(entryID)`, copies the inherited slots, places own
|
||||
entries at `[id-1]`, and **leaves gap slots uninitialized** (`new IndexEntry[n]` on a POD = garbage
|
||||
`entryName`). `Find(name)` then `strcmp`s EVERY slot → a gap between the parent's `NextAttributeID` and the
|
||||
highest id you publish = strcmp on a garbage pointer = AV. So a published table MUST be a **dense prefix**
|
||||
from the parent's `NextAttributeID` up to the max id it includes. You cannot ship just the ids you need:
|
||||
fill the gaps (bind not-yet-needed ids to a shared read-only pad member).
|
||||
|
||||
### Base engine primitives vs BT-specific widgets (don't reconstruct the base ones)
|
||||
- **Engine base primitives (already registered + drawing; only need the DATA):** `numeric`, `numericSpeed`,
|
||||
`digitalClock`, `rankAndScore`, `bgPixelMap`, `bgBitMap` (L4GAUGE.cpp). `digitalClock`'s `time` arg is a
|
||||
FORMAT enum, NOT an attribute — the value comes from the mission clock inside the widget.
|
||||
- **BT-specific (need reconstruction + registration in `BTL4MethodDescription[]`):** `vertBar`,
|
||||
`segmentArcRatio`, `oneOfSeveralPixInt`, `GeneratorCluster`, `map` (radar), `PlayerStatus`,
|
||||
`vehicleSubSystems`, plus the done ColorMapper family + headingPointer.
|
||||
|
||||
### Committed increments (each verified live in the dev gauge window; combat un-regressed; heap-clean)
|
||||
1. **HeatSink attribute table** (heat.{hpp,cpp}) — `CoolantMass`→coolantLevel, `CoolantCapacity`→
|
||||
thermalCapacity, `CurrentTemperature`→currentTemperature. Condenser/Reservoir inherit it (both derive
|
||||
from HeatSink). → Heat surface temp readout `S` now shows **77** (was 0).
|
||||
2. **Mech attribute table** (mech.{hpp,cpp}, mech4.cpp) — full enum 0x15..0x38 declared; DENSE PREFIX
|
||||
0x15..0x21 published (gap ids share `attrPad`; `CurrentSpeed`→legCycleSpeed, `MaxRunSpeed`→
|
||||
reverseStrideLength, `LinearSpeed`→new `linearSpeed` member set to |adv|/dt per frame). Chained to
|
||||
`JointedMover::GetAttributeIndex()`. → radar SPEED readout now shows **~225** (was 0).
|
||||
3. **vertBar** (VertTwoPartBar, btl4gaug) — the COOLANT bars. x87 pixel math recovered by disassembly
|
||||
(`FUN_004dcd94`=`(int)ROUND(ST0)`): `warnPix/valPix = clamp(round(height*low_or_value/high + 0.5),0,
|
||||
height)`; tile-blit `[0,warnPix)`, fill `[warnPix,valPix)`, clear `[valPix,height)`.
|
||||
4. **segmentArcRatio** (SegmentArcRatio, btl4gaug) — the SPEED arc. Thin subclass of engine `SegmentArc`;
|
||||
Execute override (`FUN_004dcd00`=fabs): `currentValue = clamp(|num/den * segmentSpan|,0,1)` then
|
||||
`SegmentArc::Execute()`. `segmentSpan = (Scalar)(|n|/(|n|-1))*0.75f` (int division → 0.75 for 36 segs).
|
||||
|
||||
### Reconstruction-technique additions (durable)
|
||||
- **x87 float math Ghidra drops** (`FUN_004dcd94()`/`FUN_004dcd00()` show no args = FPU-stack operands):
|
||||
disassemble the fn with `tools/disas2.py <VA> <len>` (capstone + PE parse; annotates `.data` float
|
||||
constants + known call targets). `FUN_004dcd94`=round-to-int, `FUN_004dcd00`=fabs.
|
||||
- **Find a vtable override the assert-anchored decomp didn't export** (e.g. SegmentArcRatio::Execute wasn't
|
||||
in `part_*.c`): dump the class vtable with `tools/vtdump.py <vtableVA> <n>`; a slot pointing into the
|
||||
module's own code range (btl4gaug = 0x4c2f94..0x4c6771) is the override → disassemble it.
|
||||
- **GraphicsView method vtable map** (GRAPH2D.h): +0x08 SetPositionWithinPort, +0x10 SetOrigin, +0x18
|
||||
SetColor, +0x24 MoveToAbsolute, +0x38 DrawThickLineToAbsolute, +0x48 DrawFilledRectangleToAbsolute.
|
||||
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).
|
||||
- Remaining widgets (task #13): `oneOfSeveralPixInt` (button lamps — `ControlsMapper/DisplayMode` already
|
||||
publishes; `DuckState`/`Searchlight/LightOn` need the Mech dense table extended to 0x37 + a Sensor/
|
||||
Searchlight table), `map` (radar — needs Mech RadarRange/LinearPos/AngularPos published + Sensor
|
||||
RadarPercent), `PlayerStatus`, `vehicleSubSystems`.
|
||||
|
||||
Reference in New Issue
Block a user