gauges: reconstruct headingPointer -- the rotating compass + heading readout [widget recon 2]
The visible money-shot: a green compass needle that rotates with the mech's facing plus a numeric heading in whole degrees (render-verified: the needle swung and the number went 247 -> 182 as the mech turned). Full class reconstruction from the binary (Make/ctor/dtor/TestInstance/ ShowInstance/BecameActive/Execute). Ghidra dropped the x87 float math feeding Execute's needle endpoints and the ctor's NumericDisplay centering, so it was recovered by disassembling BTL4OPT.EXE with capstone (scratchpad/disas_hp.py): the needle is a radial line from innerRadius(20) to outerRadius(39) at the heading angle, endpoints rounded half-up; the readout is round(360 - deg). Two corrections vs the map: - The header ctor was 12 args; the binary's is 14 -- widened it. Fields @0x98/ @0x9C are the needle's inner/outer RADIUS (Execute multiplies them by sin/cos), not "color/spacing" as the old field names implied; renamed accordingly. - ENGINE-CONVENTION FIX: the binary read the heading from EulerAngles index [0], but the WinTesla EulerAngles(Quaternion) decomposition is ambiguous for a yawing mech (the quaternion double-cover flips it to a pitch=roll=pi branch as yaw sweeps past +/-pi -> the needle spins erratically). Switched to YawPitchRoll, whose yaw-first .yaw is the clean continuous heading (pitch=roll~0). Faithful to the binary's intent (heading), correct for this engine's decomposition. renderer->GetLinkedEntity() resolves the viewpoint mech (a GetViewpointEntity fallback is kept belt-and-braces). Deps (NumericDisplay/GraphicsViewRecord/ GraphicGauge) are real engine classes -> no /FORCE risk; verified no unresolved HeadingPointer externals, no parse desync, combat un-regressed (TARGET DESTROYED, 0 crashes). Details + the disasm technique: docs/GAUGE_COMPOSITE.md. 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
547f25e043
commit
e869b00181
+26
-3
@@ -257,9 +257,32 @@ Execute→palette-push runs end-to-end; combat un-regressed (TARGET DESTROYED, 0
|
||||
schematic zones are now tinted by the REAL subsystem `currentTemperature` (stable tint since temp is stable —
|
||||
the visible-animation win is `headingPointer` next). **Method established for every remaining widget.**
|
||||
|
||||
**Next increments (priority order from the map):** `headingPointer` (rotating compass + numeric heading —
|
||||
reconstruct from prose `btl4gaug.cpp:644`, reads owner quaternion directly, no attribute table/connection
|
||||
needed — the visible money-shot); then `cmArmor`/`colorMapperMultiArmor` (armor schematic, needs the
|
||||
**✅ Increment 2 — `headingPointer` (HeadingPointer) DONE — the rotating compass + live heading readout.**
|
||||
The visible money-shot: a green needle that rotates with the mech's facing + a numeric heading in whole
|
||||
degrees (render-verified: needle swung down-left→right and the number went 247°→182° as the mech turned).
|
||||
Full class reconstruction from the binary (Make/ctor/dtor/TestInstance/ShowInstance/BecameActive/Execute) —
|
||||
the Ghidra pseudo-C for Execute@004c5914 + ctor@004c562c had the x87 endpoint math dropped, so it was
|
||||
**recovered by disassembling BTL4OPT.EXE** (capstone, `scratchpad/disas_hp.py`). Key facts learned:
|
||||
- **The header ctor arity was wrong (12 vs the binary's 14).** Widened it; fields `@0x98`/`@0x9C` are the
|
||||
needle's inner/outer RADIUS (Execute does `fild [@0x98] ; fmul sin`), not "color/spacing" as named. The
|
||||
needle is a radial line innerRadius(20)..outerRadius(39) at the heading angle; endpoints round half-up
|
||||
(`(int)(r*trig + 0.5f)` == the binary's `fadd 0.5 ; _ftol`). The readout = `round(360 - normalize(deg))`.
|
||||
- **⭐ ENGINE-CONVENTION FIX (empirical, as the map's data-binding agent flagged): the binary read the
|
||||
heading from `EulerAngles` index [0] (`.pitch`), but the WinTesla MUNGA `EulerAngles(Quaternion)`
|
||||
decomposition is AMBIGUOUS for a yawing mech** — the quaternion double-cover flips it to a `pitch=roll=π`
|
||||
branch as the yaw sweeps past ±π, so `euler.yaw` jumps discontinuously (needle spins erratically). Switched
|
||||
to **`YawPitchRoll`** (yaw applied first → `.yaw` is the clean continuous heading, `pitch=roll≈0`
|
||||
throughout). This is a legitimate port of the binary's INTENT, not a stand-in: the original engine's euler
|
||||
put the yaw in [0]; WinTesla's puts it in a different, ambiguous slot, so read the unambiguous decomposition.
|
||||
- `renderer->GetLinkedEntity()` (Renderer::GetLinkedEntity, RENDERER.h:374) resolves the viewpoint mech
|
||||
(the entitySocket IS wired — the map's NULL-socket worry didn't materialize; a `GetViewpointEntity()`
|
||||
fallback is kept belt-and-braces). Deps (`NumericDisplay`, `GraphicsViewRecord`, `GraphicGauge`) are all
|
||||
real engine classes, so no compounding `/FORCE` risk. Verified: no unresolved `HeadingPointer` externals,
|
||||
no parse desync, combat un-regressed (TARGET DESTROYED, 0 crashes). **TECHNIQUE (reusable): recover an x87
|
||||
float computation Ghidra dropped by PE-parsing + capstone-disassembling the fn (`scratchpad/disas_hp.py`) —
|
||||
read the `fild/fmul/fadd` stream + the `.data` float pool.**
|
||||
|
||||
**Next increments (priority order from the map):** `cmArmor`/`colorMapperMultiArmor` (armor schematic, needs the
|
||||
`ArmorZoneConnection`/`MultiArmorConnection` classes reconstructed); then the attribute-table wave (`vertBar`/
|
||||
`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
|
||||
|
||||
Reference in New Issue
Block a user