SeekVoltageGraph: full reconstruction -- the eng-page POWER graph + top-box eraser (Gitea #11)
The #10 audit's one WRONG: the port Execute was a bring-up no-op, so the emitter/myomer engineering pages never erased their top data box -> stale sibling-page ghosts on the shared Eng bit-plane (SYSTEM 10 PPC showing the Streak ammo box, etc). Full faithful widget landed: - btl4gau2: ctor/BecameActive/Execute/clear/ticks/cursor recovered from the capstone disasm of @004c6798/@004c6920/@004c6934/@004c6be4/@004c6c30/ @004c6c6c (Ghidra dropped every x87 arg). Plot: v=0..12000 step 1200, x=Round(response(v)*230), y=Round(v*(1/12000)*187) (ld80 @004c6bd0/@004c6d74 = exactly 1/12000); change-test samples the response at 12000V vs the 9999 activation sentinel; XOR op for tick/cursor move-by-redraw; destroyed branch centres edestryd.pcc and revives via own vtbl+0xC (BecameActive, slot 3 of PTR_0051a1fc -- vtable-dump verified). - The vtbl+0x3C sampler identified from the binary vtables: Emitter slot 15 @004bb42c = sqrt(P(v)/2.0e8), P @004bb3f4 = damageFraction*v^2*0.5* energyCoefficient; Myomers slot 15 @004b8f94 = sqrt(AvailableOutput(v)*3.6/ 350). FUN_004dd138 == sqrt (part_015.c:4026): myomers' fabs reading corrected, GetSpeedReading renamed SeekVoltageResponse. Port dispatch via complete-type bridges BTSeekVoltageSample/BTMyomersSeekSample + BTSubsystemDestroyed (databinding rule). - Emitter's AUTHENTIC attribute table recovered (binary @0x511dd4, ids 0x1D-0x25) and published: Laser*/Seek*/OutputVoltage@0x414 (currentLevel, RAW volts -- the live-cursor feed). Field renames per the table: 0x3F8 minSeekVoltageIndex / 0x3FC maxSeekVoltageIndex (static_assert-locked). The MechWeapon 0x1D OutputVoltage PORT ALIAS retired (binary table ends 0x1C; Find walks lowest-id-first, the alias shadowed the authentic row). PPC::DefaultData -> Emitter::GetAttributeIndex(). - mech4: BT_PRESET_HOLD=<n> (freeze the #9 preset cycler after n pulses) for steady-state pixel verification; BT_SEEK_LOG diag. Verified live (BLH, autofire): both #10 repro pairs held ghost-free for minutes (SYS09->SYS10 PPC, SYS02->SYS04 ERMed, SYS05->SYS06 Myomers); curves draw with live cursors; one replot per activation; quad panels/J-K-L/sec panel un-regressed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f1a6ccbaaf
commit
63b168cb92
@@ -286,6 +286,8 @@ default-ON (`'0'` disables).
|
||||
| `BT_MODE_LOG` | `[mode] display notify` / `[mode] preset (g,i)` — the ModeManager mask after each secondary-view swap (Gitea #6) or upper-MFD preset-page swap (SetPresetMode, Gitea #9) (btl4mppr.cpp) |
|
||||
| `BT_VIEWCYCLE_TEST=<frame>` / `BT_MODECYCLE_TEST=<frame>` | scripted verify (mech4.cpp): pulse a secondary-schematic cycle / a control-mode cycle at the frame and every 300 after — pixel-verifiable with `BT_DEV_GAUGES_DOCK`+`BT_SHOT` |
|
||||
| `BT_PRESET_TEST=<frame>` | scripted verify (mech4.cpp, Gitea #9): from the frame, pulse an upper-MFD preset-page cycle on all three MFDs every 120 frames (Quad → populated Eng pages → Quad) — pixel-verifiable with `BT_DEV_GAUGES_DOCK`+`BT_SHOT` |
|
||||
| `BT_PRESET_HOLD=<n>` | (with BT_PRESET_TEST, Gitea #11) stop after n pulses so a page is HELD — steady-state pixel verification; the 120-frame cycle phase-locks with BT_SHOT's 90-frame cadence, so cycling shots periodically capture the 1-frame page-switch transition |
|
||||
| `BT_SEEK_LOG` | `[seek] BecameActive/replot` — SeekVoltageGraph activation sentinel + clear/replot events with the response-at-12kV value (Gitea #11, btl4gau2.cpp) |
|
||||
| `BT_CTRLMAP_LOG` | streamed `.CTL` record audit (L4CTRL.cpp CreateStreamedMappings): Direct records with the resolved member offset + EVENT records (button→subsystem message; found the pod's secondary-panel Cycle buttons 0x15/0x18) |
|
||||
| `BT_CAM_LOG` | camera-seat/broadcast trace: streamed camera network count, director pick + Players-group census, ship follow state, PlayerLink dispatch/receive, ranking-window render ([[multiplayer]] camera seat) |
|
||||
| `BT_SPEC_LOG` | manual spec-audit dump at subsystem ctor: torso speed/limits, heat-sink count, reservoir capacity, per-subsystem coolant loop ([[pod-hardware]] §Spec cross-check) |
|
||||
|
||||
+49
-5
@@ -8,7 +8,7 @@ key_terms: [gauge, methodDescription, attribute-pointer, GaugeRenderer, MFD, PCC
|
||||
open_questions:
|
||||
- "MessageBoard LIVE 2026-07-12 (kill ticker); remaining: only strip 0 (kill) is produced -- survey the other btsmsgs.pcx strips for authentic producers"
|
||||
- "HUD attr table RESOLVED 2026-07-19 (Gitea #10 audit): re-dumped @5110b8 -- the table starts one record earlier (id 3 FlickerRate@0x1D8), every hud.hpp offset was shifted one slot; hud.hpp/CLASSMAP corrected, no behavioral impact (the reticle feeds via port globals). See GAUGE_COMPOSITE.md AUDIT finding B"
|
||||
- "SeekVoltageGraph reconstruction FILED 2026-07-19 (Gitea #10 finding A): the port Execute is a bring-up no-op, so the emitter/myomer eng pages never erase their top data box (the binary graph IS the box eraser: view=(0x97,0x80)-(0x17d,0x13b), clear @004c6be4 on the @004c6920 activation sentinel) -> stale ammo/sensor ghosts from sibling pages (the 'SYSTEM 10 PPC ammo readout'). Needs: 4 Seek* attrs on Emitter+Myomers, the subsystem vtbl+0x3C sampler, x87 recovery of @004c6934/@004c6c6c/@004c6c30"
|
||||
- "SeekVoltageGraph RECONSTRUCTED 2026-07-19 (Gitea #11, was #10 finding A): full widget landed (see §SeekVoltageGraph below) -- ghosts gone steady-state (BT_PRESET_HOLD verification); remaining polish: a 1-frame transition artifact when a BT_SHOT lands on the exact page-switch frame (label BecameActive vs the graph's next rated Execute -- same lag class as the binary; self-heals next frame)"
|
||||
- "Secondary-view cycling RESOLVED 2026-07-19 (Gitea #6): the selector is the DISPLAY mode (CycleDisplayMode -> vtbl+0x4C override @4d1ae4), NOT CycleControlMode; desktop 'N' / pad RightThumb wired; pixel-verified dama->crit->heat"
|
||||
- "Upper-MFD PRESET pages RESOLVED 2026-07-19 (Gitea #9): SetPresetMode table @0051dbf0 re-decoded (little-endian -> ModeMFD bits 0-14), per-MFD pod button banks identified from the .CTL dump, desktop J/K/L cycle wired; remaining: the always-active 0x28-0x2F / 0x1A-0x1D per-subsystem msg-4 records (subs 3-13 -- likely the condenser-valve/coolant buttons) unidentified"
|
||||
- "MP DEATHS resolved 2026-07-12 (observed-death tally + display clamp); remaining: verify multi-death tallies stay in sync across a long session (GAUGE_COMPOSITE.md)"
|
||||
@@ -22,9 +22,9 @@ system is COMPLETE** at the registration+binding level: all 50 config attribute
|
||||
(0 NULL) and every config gauge primitive is registered + built (0 parse-skips). [T2]
|
||||
**AUDIT 2026-07-19 (Gitea #10):** the full per-widget verdict table (45 rows: 34 CORRECT / 1
|
||||
WRONG-filed / 6 AUTH-STATIC / 6 DEFERRED-FEED) is in `docs/GAUGE_COMPOSITE.md` §"AUDIT
|
||||
2026-07-19" — the one WRONG is the missing SeekVoltageGraph reconstruction (the emitter/myomer
|
||||
eng pages' POWER curve + the authentic top-box eraser; causes the stale-ammo "SYSTEM 10 PPC"
|
||||
ghost). [T2]
|
||||
2026-07-19" — the one WRONG was the missing SeekVoltageGraph reconstruction (the emitter/myomer
|
||||
eng pages' POWER curve + the authentic top-box eraser; caused the stale-ammo "SYSTEM 10 PPC"
|
||||
ghost). **RESOLVED (Gitea #11): full reconstruction landed — §SeekVoltageGraph below.** [T2]
|
||||
|
||||
## Architecture (three layers)
|
||||
1. **The config** `content/GAUGE/l4gauge.cfg` — a text file the engine `GaugeInterpreter` parses
|
||||
@@ -240,7 +240,51 @@ cloud (psfx 19) spawns on the ReservoirState 0→1 edge. Desktop: **'H' HELD**
|
||||
Full chain + the two ctor decode corrections it surfaced: [[subsystems]] §coolant FLUSH.
|
||||
Diags: `BT_FLUSH_LOG`, `BT_FLUSH_TEST=<frame>`.
|
||||
The **MessageBoard** feed needs **StatusMessagePool** (a NULL stub) + the per-player status
|
||||
queue. `SeekVoltageGraph`'s 4 Seek* attrs are a cluster-child (not config-called). [T2]
|
||||
queue. [T2]
|
||||
|
||||
## SeekVoltageGraph — the eng-page POWER graph AND the top-box eraser (Gitea #11, 2026-07-19) [T1→T2]
|
||||
The emitter/PPC + myomer engineering pages' "POWER" box widget (btl4gau2.cpp/.hpp; ctor
|
||||
@004c6798, BecameActive @004c6920, Execute @004c6934; a CLUSTER-CHILD — built by
|
||||
EnergyWeaponCluster @004c93b0 / MyomerCluster @004c8df4, not a config keyword). Fully
|
||||
reconstructed from the capstone disasm (Ghidra dropped every x87 arg; tools/disas2.py):
|
||||
- **View/geometry:** localView = the page's top data box (0x97,0x80)-(0x17d,0x13b) — exactly
|
||||
230×187, the plot scales; view coords are view-relative (GRAPH2D.h `origin =
|
||||
areaWithinPort.bottomLeft`). Ctor sets **SetOperation(Xor)** — ticks/cursor erase by redraw.
|
||||
- **The eraser role:** BecameActive poisons the cached sample (previousVoltage@0xAC = 9999);
|
||||
the next Execute's change-test (sample the response at **12000 V**) then runs the CLEAR
|
||||
@004c6be4 (SetOperation(Replace), color 0, filled 1000×1000 clipped to the box) before
|
||||
replotting — THIS is what erases the sibling pages' stale pixels on the shared Eng bit-plane
|
||||
(the #10 ghosts). Every topBox=0 PrepEngr page owns a graph; the box erase design is coherent.
|
||||
- **Plot math (recovered):** polyline v = 0..12000 step 1200 (_DAT_004c6bdc/_be0):
|
||||
x = Round(response(v)·230), y = Round(v·(1/12000)·187) — the 80-bit consts @004c6bd0/@004c6d74
|
||||
are EXACTLY 1/12000. Ticks (@004c6c6c): per gear i in [*min..*max], the current gear draws a
|
||||
full L (axis→point→axis), others 10-px axis stubs; XOR pair moves the highlight. Cursor
|
||||
(@004c6c30, emitter pages only): 8×8 XOR square at the LIVE voltage (the ctor's
|
||||
"OutputVoltage" attr pointer). Destroyed subsystem (simulationState==1 via the
|
||||
BTSubsystemDestroyed bridge, powersub.cpp): centred edestryd.pcc once; revive calls own
|
||||
BecameActive (vtbl+0xC, slot 3 of PTR_0051a1fc — verified by vtable dump).
|
||||
- **The sampler == subsystem vtbl+0x3C (slot 15), reached via the BTSeekVoltageSample dispatch
|
||||
bridge (emitter.cpp → myomers.cpp; databinding rule):** Emitter @004bb42c =
|
||||
`sqrt(SeekPower(v)/2.0e8)` with SeekPower @004bb3f4 = `damageFraction·v²·0.5·energyCoefficient`
|
||||
(a LINE in v — the emitter graph is authentically straight); Myomers @004b8f94 =
|
||||
`sqrt(AvailableOutput(v)·3.6/350)` (steep near-vertical curve at BLH values).
|
||||
**FUN_004dd138 = sqrt** (part_015.c:4026) — the old "fabs/fp-magnitude" reading was wrong;
|
||||
Myomers' old best-effort name "GetSpeedReading" renamed SeekVoltageResponse.
|
||||
- **The attributes:** Myomers already published all 4 Seek* (@0x320-0x330). Emitter's AUTHENTIC
|
||||
table was recovered (binary @0x511dd4, ids 0x1D-0x25) and published (emitter.cpp):
|
||||
LaserOn@0x418, LaserScale@0x42C, LaserRotation@0x41C, Current/Recommended/Min/Max
|
||||
SeekVoltageIndex@0x3F0-0x3FC, SeekVoltage@0x400, **OutputVoltage@0x414 = currentLevel (RAW
|
||||
volts — the cursor feed)**. ⚠ The MechWeapon 0x1D "OutputVoltage" PORT ALIAS (→ rechargeLevel
|
||||
0..1) was RETIRED: the binary MechWeapon table ends at 0x1C, and `AttributeIndexSet::Find`
|
||||
walks lowest-id-first, so the alias SHADOWED the authentic Emitter row. Emitter renames:
|
||||
@0x3F8 minSeekVoltageIndex / @0x3FC maxSeekVoltageIndex (were seekStepCounter/seekVoltageCount
|
||||
guesses; the attr table proves the identities). PPC chains Emitter::GetAttributeIndex().
|
||||
- **Verified live (BLH, autofire):** page-cycling + BT_PRESET_HOLD steady-state shots — both #10
|
||||
repro pairs ghost-free (SYSTEM 09 Streak → SYSTEM 10 PPC held clean; SYSTEM 02 → SYSTEM 04
|
||||
clean; SYSTEM 05 → 06 Myomers clean); curves draw with moving cursors (charge cycling);
|
||||
exactly ONE replot per activation ([seek] log). Diags: `BT_SEEK_LOG` ([seek]
|
||||
BecameActive/replot), `BT_PRESET_HOLD=<n>` (freeze the #9 preset cycler after n pulses —
|
||||
steady-state pixel verification; the 120-frame cycle phase-locks with BT_SHOT's 90).
|
||||
|
||||
## Cockpit HUD reticle (main screen, inside view) — LIVE
|
||||
`BTReticleRenderable` (0x358 bytes, ctor @004cc40c) draws over the finished 3D frame in cockpit
|
||||
|
||||
+44
-5
@@ -567,8 +567,9 @@ dev-parse skips — the tool that pinned the last 3 (⚠ the gauge renderer buil
|
||||
long enough for BuildConfigurationFile; a too-early kill shows [gparse]=0 and looks like "not built"). Remaining gauge
|
||||
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.
|
||||
status-message queue (StatusMessagePool) — deferred systems, cleanly marked. [UPDATE 2026-07-19 Gitea #11:
|
||||
`SeekVoltageGraph` (@004c6934, a cluster-child not config-called) is now FULLY reconstructed and its Seek*
|
||||
attributes published on Emitter+Myomers — see the AUDIT finding A addendum below.]
|
||||
|
||||
### Phase 3 — SIM/MODEL
|
||||
- ✅ **Aggregate HeatSink-bank** (0xBBE) — DONE (commit 16f5f54). Reconstructed `AggregateHeatSink : HeatSink`
|
||||
@@ -679,7 +680,7 @@ reconfigure/externalConfigure); parse-skip list EMPTY ([gskip]=0), all 50 attr b
|
||||
| 16 | Eng pages: prepEngr SYSTEM NN + subsystem label + class cells | roster walk, sub+0x1dc auxScreen == screen (BTGetSubsystemAuxScreen) | FUN_004c7e48 matched line-by-line; [vss] dump: scr1/2/4=PPC/Strk6/ERMed, 5-8=Sens/Myo/ERMed/ERMed, 9/10=Strk6/PPC — page headers+labels CORRECT | T1/T2 | CORRECT |
|
||||
| 17 | Eng ballistic page: ammoCountB / jam / fire / reload numeric / destroyed lamp | bin count bridge + weaponAlarm@0x364 (MechWeapon layout static_assert-locked) | ctor @004c9558 coords matched; LIVE renders | T2 | CORRECT |
|
||||
| 18 | Eng ballistic page: eject wipe (BitMapInverseWipeScalar @004c61c8) | subsys+0x3f8 eject timer | port tracked NULL (class not reconstructed) | T3 | DEFERRED-FEED |
|
||||
| 19 | Eng emitter/myomer pages: SeekVoltageGraph (POWER curve) | 4 Seek* attrs + subsystem vtbl+0x3C sampler | WRONG — port Execute is a bring-up no-op (btl4gau2.cpp:331); see finding A below | T1 (binary) | WRONG (filed) |
|
||||
| 19 | Eng emitter/myomer pages: SeekVoltageGraph (POWER curve) | 4 Seek* attrs + subsystem vtbl+0x3C sampler | WAS WRONG (bring-up no-op Execute) — **FIXED (Gitea #11, 2026-07-19)**: full reconstruction, see finding A addendum | T2 | CORRECT (fixed) |
|
||||
| 20 | Eng emitter page: seek-step lamp (bteseek) | Emitter::seekVoltageIndex@0x3F0 (static_assert-locked) | @004c93b0 :2146 | T2 | CORRECT |
|
||||
| 21 | Eng myomer page: seek-step lamp | Myomers@0x320 INFERRED (ctor @004c8df4 not in the decomp export) | marked in code | T3 | CORRECT (best-effort; flagged) |
|
||||
| 22 | Eng page: linked heat-sink number (hs+0x1d4 guarded raw) | PrepEngr numeric #2 | marked BEST-EFFORT (guarded) | T3 | CORRECT (guarded; flagged) |
|
||||
@@ -707,8 +708,9 @@ reconfigure/externalConfigure); parse-skip list EMPTY ([gskip]=0), all 50 attr b
|
||||
| 44 | HUD: PNAME1-8.bgf 3D name marker chain | — | deferred | T4 | DEFERRED-FEED |
|
||||
| 45 | HUD attr table @005110b8 (ids 3-0xD) | hud.hpp/CLASSMAP corrected this audit | finding B below | T1 | CORRECT (fixed) |
|
||||
|
||||
**Counts: 34 CORRECT · 1 WRONG (filed: SeekVoltageGraph) · 6 AUTH-STATIC (incl. dual-verdict rows
|
||||
13/29/39) · 6 DEFERRED-FEED (rows 18/24/34-duck/35/44 + the sec messageBoard partial).**
|
||||
**Counts: 34 CORRECT · 1 WRONG (filed: SeekVoltageGraph — since FIXED, Gitea #11) · 6 AUTH-STATIC
|
||||
(incl. dual-verdict rows 13/29/39) · 6 DEFERRED-FEED (rows 18/24/34-duck/35/44 + the sec
|
||||
messageBoard partial).**
|
||||
(Dual-verdict rows counted in both columns.)
|
||||
|
||||
### Finding A — the "SYSTEM 10 PPC ammo readout" (issue entry point a) — ROOT-CAUSED, FILED
|
||||
@@ -741,6 +743,43 @@ DrawTicks @004c6c6c, DrawCursor @004c6c30, clear @004c6be4 — Ghidra dropped th
|
||||
tools/disas2.py). An erase-only patch was deliberately NOT made (it would be a stand-in for the
|
||||
missing curve).
|
||||
|
||||
**✅ RESOLVED (Gitea #11, 2026-07-19) — the full widget landed (btl4gau2.cpp/.hpp):**
|
||||
- **x87 recovery (capstone, tools/disas2.py):** plot step/limit _DAT_004c6bdc/_be0 = 1200/12000;
|
||||
the 80-bit consts @004c6bd0/@004c6d74 = EXACTLY 1/12000 (the voltage→y normaliser);
|
||||
x = Round(response(v)·xScale 230), y = Round(v·(1/12000)·yScale 187); change-test = the
|
||||
response sampled at 12000 V vs the @0xAC cache (9999 activation sentinel); clear @004c6be4 =
|
||||
SetOperation(Replace)+color 0+filled 1000×1000 clipped to the view; ticks @004c6c6c = per-gear
|
||||
loop [*min..*max], current gear = full L, others 10-px stubs, XOR pair to move; cursor
|
||||
@004c6c30 = 8×8 XOR square at (x-4,y-4) from the LIVE voltage (*this[0x29]); destroyed branch =
|
||||
centred edestryd.pcc via (Round(xScale)-w)/2; revive → own vtbl+0xC == BecameActive (slot 3 of
|
||||
PTR_0051a1fc, vtable-dump verified). Members this[0x30..0x32] = cursorX/cursorY/tickIndexShown.
|
||||
- **The vtbl+0x3C sampler identified from the vtable dumps** (Emitter @00512078 slot 15 =
|
||||
@004bb42c; Myomers @005117dc slot 15 = @004b8f94): Emitter = sqrt(P(v)/2.0e8f [0x4d3ebc20]),
|
||||
P @004bb3f4 = damageFraction@0x444 · v² · 0.5 [_DAT_004bb428] · energyCoefficient@0x454;
|
||||
Myomers = sqrt(AvailableOutput(v)·3.6/350). **FUN_004dd138 == sqrt** (part_015.c:4026) — the
|
||||
myomers.cpp "fabs / fp-magnitude" reading was corrected and the best-effort name
|
||||
GetSpeedReading renamed **SeekVoltageResponse**. Port dispatch: `BTSeekVoltageSample`
|
||||
(emitter.cpp) → `BTMyomersSeekSample` (myomers.cpp) complete-type bridges;
|
||||
`BTSubsystemDestroyed` (powersub.cpp) for the simulationState==1 test.
|
||||
- **Emitter's AUTHENTIC attribute table recovered + published** (binary @0x511dd4, ids
|
||||
0x1D-0x25 chained after MechWeapon's 0x1C): LaserOn@0x418(firingActive), LaserScale@0x42C,
|
||||
LaserRotation@0x41C, CurrentSeekVoltageIndex@0x3F0, RecommendedSeekVoltageIndex@0x3F4,
|
||||
MinSeekVoltageIndex@0x3F8, MaxSeekVoltageIndex@0x3FC, SeekVoltage@0x400,
|
||||
OutputVoltage@0x414(currentLevel — RAW volts, the cursor feed). Emitter field renames per the
|
||||
table: @0x3F8 minSeekVoltageIndex, @0x3FC maxSeekVoltageIndex (static_assert-locked). The
|
||||
MechWeapon 0x1D "OutputVoltage" PORT ALIAS was RETIRED (binary table ends 0x1C; Find walks
|
||||
lowest-id-first so the alias shadowed the authentic row — the cursor would have read the 0..1
|
||||
rechargeLevel instead of volts). PPC::DefaultData → Emitter::GetAttributeIndex().
|
||||
- **Verified live** (BLH DEV.EGG, BT_AUTOFIRE + BT_PRESET_TEST + the new **BT_PRESET_HOLD=<n>**
|
||||
steady-state hold): both #10 repro pairs ghost-free held for minutes (SYS09 Streak→SYS10 PPC;
|
||||
SYS02 Streak→SYS04 ERMed; SYS05 Sensors→SYS06 Myomers); curves draw (emitter = straight line,
|
||||
authentically — sqrt of v² is linear; myomer = steep low curve), cursors track the charge
|
||||
cycle, gear ticks render; [seek] log (BT_SEEK_LOG) shows exactly ONE replot per activation,
|
||||
all 6 graph instances. NOTE: a BT_SHOT landing exactly on a page-switch frame can capture the
|
||||
1-frame label-vs-box transition (PrepEngr labels draw in BecameActive; the box clears at the
|
||||
graph's next rated Execute) — the binary has the same lag class; it self-heals next frame and
|
||||
the 90/120 cadence phase-lock made it look persistent during cycling verification.
|
||||
|
||||
### Finding B — HUD binary attr table @005110c0 conflict (issue entry point b) — RESOLVED, FIXED
|
||||
Re-dumped section_dump.txt @5110b8: the table starts ONE RECORD EARLIER than transcribed — a full
|
||||
16-byte record {id 3, "FlickerRate", 0x1D8|1, 0} (the old reading took the FlickerRate name ptr
|
||||
|
||||
@@ -34,7 +34,8 @@ PPC::SharedData
|
||||
&PPC::ClassDerivations,
|
||||
Emitter::GetMessageHandlers(), // task #6: the port's accessor idiom -- inherits
|
||||
// MechWeapon's ConfigureMappables/ChooseButton
|
||||
MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty)
|
||||
Emitter::GetAttributeIndex(), // issue #11: the authentic Emitter table (Seek*/Laser*/
|
||||
// OutputVoltage=currentLevel) -- chains MechWeapon's
|
||||
PPC::StateCount
|
||||
);
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ Subsystem (MUNGA base — have source: RP/MUNGA/SUBSYSTM.HPP)
|
||||
│ │ resource (size 0x1B0) adds +0x190 VelocityEfficiency, +0x194 AccelerationEfficiency, +0x198 SeekVoltage[5](fractions×ratedVoltage),
|
||||
│ │ +0x1AC SeekVoltageRecommendedIndex (parser @4b9140). attr table @00511588; string pool @00511660 ("Myomers"@0051166d).
|
||||
│ │ vtable overrides vs PoweredSubsystem: slot0 dtor@4b9114, slot9 HandleMessage@4b8a8c(→4b0efc), slot10 ResetToInitialState@4b8aa4(→4b0e6c),
|
||||
│ │ slot15 GetSpeedReading@4b8f94 (base 4b1780; km/h÷350 gauge readout, best-effort), slot16 HasAdequateVoltage@4b8f3c (overrides IsSourceShorted base 4b0b5c).
|
||||
│ │ slot15 SeekVoltageResponse@4b8f94 (base 4b1780; the SeekVoltageGraph vtbl+0x3C sampler: sqrt(AvailableOutput(v)·3.6/350) — issue #11 resolved the old "GetSpeedReading km/h gauge" guess; FUN_004dd138 = sqrt not fabs), slot16 HasAdequateVoltage@4b8f3c (overrides IsSourceShorted base 4b0b5c).
|
||||
│ │ helpers: AvailableOutput@4b8ac0 (drive = MechBaseSpeed × V/recommendedSeekV × heatFactor × (1-damage)),
|
||||
│ │ RegisterMaxOutput@4b8ef0 (→ Mech maxSpeed@0x7A0; loop @part_012:10322 over Mech myomer list@0x1EB),
|
||||
│ │ MyomersSimulation@4b8d18 (Performance, per-tick work→heat accum@0x1C8, gated by FUN_004ad7d4; best-effort registration),
|
||||
@@ -487,11 +487,19 @@ Searchlight 0xBD8 and ThermalSight 0xBDE are recovered, not gaps.)
|
||||
NB: MechWeapon's recharge-level slot @0x320 is reused by `Emitter` as **outputVoltage** (the
|
||||
normalised discharge readiness; ==1.0 ⇒ fully charged, used in EMITTER.TCP).
|
||||
- `Emitter` adds from @0x3F0 (object ~0x478): seekVoltageIndex@0x3F0, seekVoltageRecommendedIndex@0x3F4,
|
||||
seekVoltageCount@0x3FC, seekVoltage[5]@0x400, currentLevel@0x414, firingActive@0x418,
|
||||
minSeekVoltageIndex@0x3F8, maxSeekVoltageIndex@0x3FC (issue #11 renames — the binary attribute
|
||||
table @0x511dd4 publishes them as Min/MaxSeekVoltageIndex; the old seekStepCounter/seekVoltageCount
|
||||
names were guesses), seekVoltage[5]@0x400, currentLevel@0x414, firingActive@0x418,
|
||||
beamOrientation@0x41C, beamScale@0x42C, beamLengthRatio@0x434, graphicLength@0x438, dischargeTime@0x43C,
|
||||
dischargeTimer@0x440, damageFraction@0x444, energyTotal@0x448, damagePortion@0x44C, heatPortion@0x450,
|
||||
energyCoefficient@0x454, seekRate@0x45C, beamEndpoint@0x460, beamFlag@0x46C, targetLocalFlag@0x470,
|
||||
targetEntity@0x474. Firing state machine carried in weaponAlarm level @0x364 (0=Firing,2=Loaded,3=Loading,4=TriggerDuringLoad).
|
||||
Attr table @0x511dd4 (ids 0x1D-0x25, issue #11 dump): LaserOn@0x418, LaserScale@0x42C,
|
||||
LaserRotation@0x41C, CurrentSeekVoltageIndex@0x3F0, RecommendedSeekVoltageIndex@0x3F4,
|
||||
MinSeekVoltageIndex@0x3F8, MaxSeekVoltageIndex@0x3FC, SeekVoltage@0x400, OutputVoltage@0x414
|
||||
(currentLevel — RAW volts, the SeekVoltageGraph cursor feed). vtable slot 15 @4bb42c =
|
||||
SeekVoltageResponse (the graph sampler): sqrt(damageFraction·v²·0.5·energyCoefficient / 2.0e8)
|
||||
(power helper @4bb3f4; FUN_004dd138 = sqrt).
|
||||
|
||||
## Recovered resource structs (parsed by each CreateStreamedSubsystem)
|
||||
- HeatableSubsystem/HeatSink: +0xE4 StartingTemperature, +0xE8 Degradation, +0xEC Failure, +0xF0 ThermalConductance, +0xF4 ThermalMass, +0xF8 HeatSink index
|
||||
|
||||
+216
-41
@@ -57,7 +57,9 @@
|
||||
// 0x463b8000=12000.0f generator max voltage
|
||||
// 0x42c80000=100.0f HeatSinkCluster numeric scale base
|
||||
// _DAT_004c8df0 heat numeric scale _DAT_004c92e0 warning threshold
|
||||
// _DAT_004c6bdc/_DAT_004c6be0 seek-curve plot step/limit
|
||||
// _DAT_004c6bdc=1200.0f / _DAT_004c6be0=12000.0f seek-curve plot step/limit
|
||||
// ld80 @004c6bd0 / @004c6d74 = 8.3333333333333e-05 (exactly 1/12000) --
|
||||
// the voltage->y normaliser (y = v * (1/12000) * yScale)
|
||||
// DAT_0051936c PrepEngr engineer-screen number (1..12)
|
||||
//
|
||||
|
||||
@@ -84,6 +86,10 @@ static const Scalar SeekVoltageYScale = 187.0f; // 0x433b0000
|
||||
static const Scalar RedrawSentinel = 9999.0f; // 0x461c3c00
|
||||
static const Scalar GeneratorMaxVoltage = 12000.0f; // 0x463b8000
|
||||
static const Scalar HeatNumericBase = 100.0f; // 0x42c80000
|
||||
// SeekVoltageGraph plot constants (capstone disasm of @004c6934, task/issue #11):
|
||||
static const Scalar SeekCurveStep = 1200.0f; // _DAT_004c6bdc (10 segments)
|
||||
static const Scalar SeekCurveLimit = 12000.0f; // _DAT_004c6be0 (y-axis full scale)
|
||||
static const Scalar SeekCurveYNorm = (Scalar)(1.0 / 12000.0); // ld80 @004c6bd0/@004c6d74
|
||||
|
||||
// (DebugStream warning channel, DAT_00524e20, is provided by the shared
|
||||
// reconstruction headers -- heat.hpp / mechrecon.hpp.)
|
||||
@@ -97,15 +103,6 @@ static char DAT_00518f24[] = "GeneratorCluster"; // subsystem name to resolve
|
||||
static int DAT_00518ee0 = 0; // GaugeRate
|
||||
static char DAT_00518f68[] = ""; // background image name
|
||||
|
||||
//
|
||||
// Original FUN_0041bfc0 = Entity::FindAttributeIndex(name) -> ordinal. The
|
||||
// WinTesla engine exposes attribute lookup via Simulation::AttributeIndexSet
|
||||
// (Find/FindEntry by name) rather than a name->ordinal call, so this best-effort
|
||||
// shim stands in for it; the stored indices are consumed by the (summarised)
|
||||
// SeekVoltageGraph::Execute plot.
|
||||
//
|
||||
static int FindAttributeIndex(Entity *, const char *) { return -1; }
|
||||
|
||||
//
|
||||
// FUN_0041bfc0 == Entity::FindAttributeIndex(name) returns a POINTER into the
|
||||
// subsystem (subsystem + attributeMemberOffset), or 0 on miss. The composite
|
||||
@@ -250,14 +247,31 @@ protected:
|
||||
//###########################################################################
|
||||
//###########################################################################
|
||||
// SeekVoltageGraph @004c6798
|
||||
// (issue #11: fully reconstructed from the capstone disasm of
|
||||
// @004c6798/@004c6920/@004c6934/@004c6be4/@004c6c30/@004c6c6c --
|
||||
// Ghidra had dropped every x87 argument; tools/disas2.py recovered them.)
|
||||
//###########################################################################
|
||||
//###########################################################################
|
||||
|
||||
//
|
||||
// The subsystem's voltage-response sampler == the binary subsystem vtbl+0x3C
|
||||
// (slot 15): Emitter @004bb42c = sqrt(seekPower(v)/2.0e8), Myomers @004b8f94 =
|
||||
// sqrt(speed(v)*3.6/350). Reached through a complete-type dispatch bridge
|
||||
// (emitter.cpp -> myomers.cpp) per the databinding rule (gotcha 8).
|
||||
//
|
||||
extern Scalar BTSeekVoltageSample(void *subsystem, Scalar voltage);
|
||||
//
|
||||
// The destroyed test == *(subsystem+0x40) == 1 (MechSubsystem::simulationState,
|
||||
// 1 = Destroyed). Named-member bridge in powersub.cpp (databinding rule).
|
||||
//
|
||||
extern int BTSubsystemDestroyed(Subsystem *subsystem);
|
||||
|
||||
//
|
||||
// @004c6798 -- ctor (vtable PTR_FUN_0051a1fc). GraphicGauge base, interns and
|
||||
// ref-counts the "destroyed" bitmap, resolves the four SeekVoltage attribute
|
||||
// indices off the weapon subsystem, sets the port extent (0x97,0x80,0x17d,0x13b)
|
||||
// and stashes the 230x187 plot scale.
|
||||
// POINTERS off the weapon subsystem (FUN_0041bfc0 returns subsystem+offset or
|
||||
// 0), sets the view XOR draw op + the port extent (0x97,0x80,0x17d,0x13b) and
|
||||
// stashes the 230x187 plot scale.
|
||||
//
|
||||
SeekVoltageGraph::SeekVoltageGraph(
|
||||
GaugeRate rate,
|
||||
@@ -278,22 +292,34 @@ SeekVoltageGraph::SeekVoltageGraph(
|
||||
destroyedImage = (char *)destroyed_image;
|
||||
renderer->warehousePointer->bitMapBin.Get(destroyedImage); // FUN_00442aec (AddRef)
|
||||
|
||||
currentSeekIndex = FindAttributeIndex(subsystem, "CurrentSeekVoltageIndex"); // FUN_0041bfc0
|
||||
minSeekIndex = FindAttributeIndex(subsystem, "MinSeekVoltageIndex");
|
||||
maxSeekIndex = FindAttributeIndex(subsystem, "MaxSeekVoltageIndex");
|
||||
seekVoltageTable = FindAttributeIndex(subsystem, "SeekVoltage");
|
||||
// FUN_0041bfc0(subsystem, name) == the engine's name-keyed attribute-pointer
|
||||
// resolve (subsystem+memberOffset, or 0 on a miss); the composite ctors gate
|
||||
// the tick/cursor draws on a 0 result.
|
||||
currentSeekIndexPtr = (int *)AttributePointerOf(subsystem, "CurrentSeekVoltageIndex"); // this[0x25]
|
||||
minSeekIndexPtr = (int *)AttributePointerOf(subsystem, "MinSeekVoltageIndex"); // this[0x26]
|
||||
maxSeekIndexPtr = (int *)AttributePointerOf(subsystem, "MaxSeekVoltageIndex"); // this[0x27]
|
||||
seekVoltageTablePtr = (Scalar *)AttributePointerOf(subsystem, "SeekVoltage"); // this[0x28]
|
||||
|
||||
voltageSubsystem = (Entity *)seek_voltage_pointer; // this[0x29]
|
||||
liveVoltage = (Scalar *)seek_voltage_pointer; // this[0x29]
|
||||
this->subsystem = (Subsystem *)subsystem; // this[0x2A]
|
||||
|
||||
// graphicsView -> GraphicGauge::localView. SetExtent == SetPositionWithinPort;
|
||||
// the original SetClip() (vtbl+0x0C) constrained drawing to that extent.
|
||||
// @004c6798: (vtbl+0x0C)(3) then (vtbl+0x08)(0x97,0x80,0x17d,0x13b) --
|
||||
// SetOperation(Xor) + SetPositionWithinPort (GRAPH2D.h vtable order). The
|
||||
// XOR op is what lets DrawTicks/DrawCursorBox erase by redrawing.
|
||||
localView.SetOperation((GraphicsDisplay::Operation)3); // vtbl+0x0C Xor
|
||||
localView.SetPositionWithinPort(0x97, 0x80, 0x17d, 0x13b); // vtbl+0x08
|
||||
|
||||
xScale = SeekVoltageXScale; // this[0x2C]
|
||||
yScale = SeekVoltageYScale; // this[0x2D]
|
||||
drawCursor = draw_cursor; // this[0x2F]
|
||||
destroyedShown = 0; // this[0x2E]
|
||||
// (binary leaves this[0x2B]/[0x30..0x32] uninitialised -- activation's
|
||||
// BecameActive sentinel forces the first replot, which seeds them before
|
||||
// any read. Seeded here for determinism.)
|
||||
previousVoltage = RedrawSentinel; // this[0x2B]
|
||||
cursorX = -1; // this[0x30]
|
||||
cursorY = -1; // this[0x31]
|
||||
tickIndexShown = -1; // this[0x32]
|
||||
}
|
||||
|
||||
//
|
||||
@@ -311,38 +337,187 @@ Logical SeekVoltageGraph::TestInstance() const
|
||||
}
|
||||
|
||||
//
|
||||
// @004c6920 -- BecameActive: previousVoltage = 9999.0f (force first redraw).
|
||||
// @004c6920 -- BecameActive: previousVoltage = 9999.0f (poison the cached
|
||||
// response so the next Execute clears + replots the whole box -- THE top-box
|
||||
// eraser on a page switch; the binary body writes ONLY this[0x2B]).
|
||||
//
|
||||
void SeekVoltageGraph::BecameActive()
|
||||
{
|
||||
previousVoltage = RedrawSentinel; // @0xAC force first redraw
|
||||
destroyedShown = 0;
|
||||
previousVoltage = RedrawSentinel; // @0xAC force full redraw
|
||||
if (getenv("BT_SEEK_LOG"))
|
||||
DEBUG_STREAM << "[seek] BecameActive sub="
|
||||
<< (subsystem ? subsystem->GetName() : "?") << "\n" << std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
// @004c6934 -- Execute (the seek-voltage curve plot). BRING-UP: the full plot
|
||||
// (SetupClip/DrawTicks/DrawCursor over a 0..1 voltage polyline) needs the live
|
||||
// seek-voltage sampler (subsys vtbl+0x3c) + the four attribute indices; the
|
||||
// attribute resolve is summarised (FindAttributeIndex returns -1), so the plot is
|
||||
// deferred and Execute is inert until the sampler + attribute reads land. Kept as
|
||||
// a defined no-op so the gauge's vtable is complete (no /FORCE crash when the
|
||||
// Myomer/Energy cluster ticks its SeekVoltageGraph child).
|
||||
// @004c6be4 -- the full-box erase ("SetupClip" in the old notes; it is really
|
||||
// a clear): SetOperation(Replace), SetColor(0), MoveToAbsolute(0,0),
|
||||
// DrawFilledRectangleToAbsolute(1000,1000) -- clipped to the view = the whole
|
||||
// (0x97,0x80)-(0x17d,0x13b) top data box painted black. NOTE: leaves the view
|
||||
// in Replace mode (the polyline plot relies on that; Execute restores Xor).
|
||||
//
|
||||
void SeekVoltageGraph::ClearGraph()
|
||||
{
|
||||
localView.SetOperation((GraphicsDisplay::Operation)0); // vtbl+0x0C Replace
|
||||
localView.SetColor(0); // vtbl+0x18
|
||||
localView.MoveToAbsolute(0, 0); // vtbl+0x24
|
||||
localView.DrawFilledRectangleToAbsolute(1000, 1000); // vtbl+0x48
|
||||
}
|
||||
|
||||
//
|
||||
// @004c6c30 -- the live cursor: an 8x8 filled square centred on
|
||||
// (cursorX,cursorY), drawn in the view's current (Xor) op so a second call
|
||||
// erases it. MoveToAbsolute(x-4,y-4) + DrawFilledRectangleToRelative(8,8).
|
||||
//
|
||||
void SeekVoltageGraph::DrawCursorBox()
|
||||
{
|
||||
localView.MoveToAbsolute(cursorX - 4, cursorY - 4); // vtbl+0x24
|
||||
localView.DrawFilledRectangleToRelative(8, 8); // vtbl+0x4C
|
||||
}
|
||||
|
||||
//
|
||||
// @004c6c6c -- the per-gear tick marks (XOR). For each seek index i in
|
||||
// [*min..*max]: V = seekVoltage[i]; x = Round(sample(V) * xScale);
|
||||
// y = Round(V * (1/12000) * yScale) [ld80 @004c6d74]. The gear the highlight
|
||||
// was last drawn at (this[0x32]) gets the full L (axis->point->axis); the rest
|
||||
// get 10-pixel axis stubs. Called in pairs to move the highlight (XOR erase +
|
||||
// redraw).
|
||||
//
|
||||
void SeekVoltageGraph::DrawTicks()
|
||||
{
|
||||
// (port guard: the binary derefs min/max unguarded -- its call sites only
|
||||
// guarantee current+table non-null; all four always resolve on the
|
||||
// emitter/myomer subsystems that own a graph.)
|
||||
if (minSeekIndexPtr == 0 || maxSeekIndexPtr == 0)
|
||||
return;
|
||||
int last = *maxSeekIndexPtr; // this[0x27] deref
|
||||
for (int i = *minSeekIndexPtr; i <= last; i++) // this[0x26] deref
|
||||
{
|
||||
Scalar gearVoltage = seekVoltageTablePtr[i]; // this[0x28][i]
|
||||
int x = Round(BTSeekVoltageSample(subsystem, gearVoltage) * xScale); // (vtbl+0x3C)(V)*230
|
||||
int y = Round(gearVoltage * SeekCurveYNorm * yScale); // V/12000*187
|
||||
if (i == tickIndexShown) // this+0xC8
|
||||
{
|
||||
localView.MoveToAbsolute(0, y); // vtbl+0x24
|
||||
localView.DrawLineToAbsolute(x, y); // vtbl+0x30
|
||||
localView.DrawLineToAbsolute(x, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
localView.MoveToAbsolute(0, y);
|
||||
localView.DrawLineToAbsolute(10, y);
|
||||
localView.MoveToAbsolute(x, 0);
|
||||
localView.DrawLineToAbsolute(x, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @004c6934 -- Execute (the seek-voltage response plot; full x87 recovery).
|
||||
//
|
||||
// DESTROYED branch (subsys simulationState==1): once, clear the box and draw
|
||||
// the centred "destroyed" bitmap (edestryd.pcc) at
|
||||
// ((Round(xScale)-w)/2, (Round(yScale)-h)/2), color 0xFF.
|
||||
//
|
||||
// LIVE branch:
|
||||
// * on the destroyed->alive edge, call BecameActive (own vtbl+0xC == slot 3
|
||||
// of PTR_0051a1fc = @004c6920) to poison the cache;
|
||||
// * change-test: sample the response at 12000 V (0x463b8000) and compare with
|
||||
// the cached this[0x2B]; on change (or the 9999 activation sentinel):
|
||||
// CLEAR (@004c6be4 -- the ghost eraser), SetColor(0xFF), MoveTo(0,0), then
|
||||
// the polyline v = 0,1200,...,10800 (step _DAT_004c6bdc=1200 while
|
||||
// v < _DAT_004c6be0=12000): LineTo(Round(sample(v)*230),
|
||||
// Round(v * [1/12000 * 187])), reset the cursor slots to -1, restore
|
||||
// SetOperation(Xor), and draw the gear ticks at the current index;
|
||||
// * tick maintenance: when the current seek index moved, XOR-erase the old
|
||||
// highlight + redraw at the new (two DrawTicks calls);
|
||||
// * cursor (draw_cursor pages only): XOR-erase the old box (if any), sample
|
||||
// at the LIVE voltage (*this[0x29]) and draw the new 8x8 box at
|
||||
// (Round(sample(V)*230), Round(V * [1/12000 * 187])).
|
||||
//
|
||||
void SeekVoltageGraph::Execute()
|
||||
{
|
||||
// (seek-voltage plot deferred -- see note above.)
|
||||
}
|
||||
if (BTSubsystemDestroyed(subsystem)) // *(subsys+0x40) == 1
|
||||
{
|
||||
if (destroyedShown == 0)
|
||||
{
|
||||
destroyedShown = 1; // this[0x2E]
|
||||
ClearGraph(); // @004c6be4
|
||||
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
||||
BitMap *image = warehouse->bitMapBin.Get(destroyedImage); // FUN_00442aec
|
||||
if (image != NULL) // (port guard; binary derefs unchecked)
|
||||
{
|
||||
localView.MoveToAbsolute( // vtbl+0x24 centre the bitmap
|
||||
(Round(xScale) - image->Data.Size.x) >> 1,
|
||||
(Round(yScale) - image->Data.Size.y) >> 1);
|
||||
localView.SetColor(0xff); // vtbl+0x18
|
||||
localView.DrawBitMap(0, image, // vtbl+0x54
|
||||
0, 0, image->Data.Size.x - 1, image->Data.Size.y - 1);
|
||||
}
|
||||
warehouse->bitMapBin.Release(destroyedImage); // FUN_00442c12
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// @004c6934 (original notes preserved) -- Execute. If the subsystem is destroyed
|
||||
// (subsys+0x40 == 1) draw
|
||||
// the centred "destroyed" bitmap once. Otherwise, when the live seek voltage
|
||||
// (subsys vtbl+0x3c sampler) changes: clear (SetupClip @004c6be4), re-plot the
|
||||
// 0..1 voltage curve as a polyline (step _DAT_004c6bdc up to _DAT_004c6be0,
|
||||
// Round each point), then draw the min/max tick marks (DrawTicks @004c6c6c)
|
||||
// and, if drawCursor, the live cursor at the current seek index
|
||||
// (DrawCursor @004c6c30).
|
||||
//
|
||||
if (destroyedShown != 0)
|
||||
{
|
||||
destroyedShown = 0;
|
||||
BecameActive(); // own vtbl+0xC (@004c6920)
|
||||
}
|
||||
|
||||
Scalar yStep = SeekCurveYNorm * yScale; // fld tbyte @004c6bd0 * this[0x2D]
|
||||
|
||||
// change-test: the response curve's value at max voltage.
|
||||
Scalar response = BTSeekVoltageSample(subsystem, GeneratorMaxVoltage); // (vtbl+0x3C)(12000.0f)
|
||||
if (previousVoltage != response) // this[0x2B]
|
||||
{
|
||||
if (getenv("BT_SEEK_LOG"))
|
||||
DEBUG_STREAM << "[seek] replot sub="
|
||||
<< (subsystem ? subsystem->GetName() : "?")
|
||||
<< " response12k=" << response << "\n" << std::flush;
|
||||
previousVoltage = response;
|
||||
ClearGraph(); // @004c6be4 -- erases the WHOLE box
|
||||
localView.SetColor(0xff); // vtbl+0x18 (Replace mode from the clear)
|
||||
localView.MoveToAbsolute(0, 0); // vtbl+0x24
|
||||
for (Scalar v = 0.0f; v < SeekCurveLimit; v += SeekCurveStep)
|
||||
{
|
||||
int x = Round(BTSeekVoltageSample(subsystem, v) * xScale);
|
||||
int y = Round(v * yStep);
|
||||
localView.DrawLineToAbsolute(x, y); // vtbl+0x30
|
||||
}
|
||||
cursorX = -1; // this[0x30]
|
||||
cursorY = -1; // this[0x31]
|
||||
localView.SetOperation((GraphicsDisplay::Operation)3); // vtbl+0x0C back to Xor
|
||||
if (currentSeekIndexPtr != 0 && seekVoltageTablePtr != 0)
|
||||
{
|
||||
tickIndexShown = *currentSeekIndexPtr; // this[0x32]
|
||||
DrawTicks(); // @004c6c6c
|
||||
}
|
||||
}
|
||||
|
||||
// gear-change tick maintenance (XOR pair: erase old highlight, draw new).
|
||||
if (currentSeekIndexPtr != 0 && seekVoltageTablePtr != 0)
|
||||
{
|
||||
int currentIndex = *currentSeekIndexPtr;
|
||||
if (currentIndex != tickIndexShown)
|
||||
{
|
||||
DrawTicks(); // erase at the old index
|
||||
tickIndexShown = currentIndex;
|
||||
DrawTicks(); // draw at the new
|
||||
}
|
||||
}
|
||||
|
||||
// live-voltage cursor (drawCursor pages: the emitter/PPC graphs).
|
||||
if (drawCursor != 0) // this[0x2F]
|
||||
{
|
||||
if (cursorX >= 0)
|
||||
DrawCursorBox(); // XOR-erase the old box
|
||||
Scalar live = *liveVoltage; // *this[0x29]
|
||||
cursorX = Round(BTSeekVoltageSample(subsystem, live) * xScale);
|
||||
cursorY = Round(live * yStep);
|
||||
DrawCursorBox(); // @004c6c30
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//###########################################################################
|
||||
|
||||
@@ -86,13 +86,22 @@
|
||||
|
||||
|
||||
//#######################################################################
|
||||
// SeekVoltageGraph -- the energy-weapon "seek voltage" curve display.
|
||||
// SeekVoltageGraph -- the energy-weapon / myomer "seek voltage" response
|
||||
// graph on the engineering detail pages (the "POWER" box).
|
||||
// @004c6798 ctor / @004c68ac dtor / @004c6920 BecameActive / @004c6934 Execute.
|
||||
// vtable PTR_FUN_0051a1fc. Reads the four SeekVoltage attribute indices off
|
||||
// the weapon subsystem ("CurrentSeekVoltageIndex" / "Min" / "Max" /
|
||||
// "SeekVoltage") and plots a 0..230 x 0..187 curve, with min/max tick marks
|
||||
// and a live cursor at the current seek index. When the subsystem is
|
||||
// destroyed (subsys+0x40==1) it shows a centred "destroyed" bitmap instead.
|
||||
// vtable PTR_FUN_0051a1fc. Resolves the four SeekVoltage attribute
|
||||
// POINTERS off the weapon subsystem (FUN_0041bfc0 returns subsystem+offset:
|
||||
// "CurrentSeekVoltageIndex" / "MinSeekVoltageIndex" / "MaxSeekVoltageIndex"
|
||||
// / "SeekVoltage") and plots the subsystem's voltage-response curve
|
||||
// (vtbl slot 15 sampler: Emitter @004bb42c / Myomers @004b8f94) over the
|
||||
// top data box (0x97,0x80)-(0x17d,0x13b): x = response(v)*230 (0..1
|
||||
// normalised sqrt response), y = v/12000*187 (voltage axis), with per-gear
|
||||
// tick marks (the current gear drawn as a full L) and, when draw_cursor,
|
||||
// a live 8x8 XOR cursor at the subsystem's live voltage. When the
|
||||
// subsystem is destroyed (simulationState@0x40==1) it shows a centred
|
||||
// "destroyed" bitmap instead. Its Execute's full-box CLEAR (@004c6be4)
|
||||
// is what erases the sibling eng pages' stale pixels on the shared Eng
|
||||
// bit-plane -- the widget IS the top-box eraser (Gitea #10 finding A).
|
||||
//#######################################################################
|
||||
class SeekVoltageGraph :
|
||||
public GraphicGauge
|
||||
@@ -109,19 +118,30 @@
|
||||
void BecameActive(); // @004c6920
|
||||
void Execute(); // @004c6934
|
||||
protected:
|
||||
void ClearGraph(); // @004c6be4 (full-box erase)
|
||||
void DrawTicks(); // @004c6c6c (per-gear ticks, XOR)
|
||||
void DrawCursorBox(); // @004c6c30 (8x8 cursor, XOR)
|
||||
|
||||
char *destroyedImage; // @0x90 this[0x24]
|
||||
int currentSeekIndex; // @0x94 this[0x25] attr idx
|
||||
int minSeekIndex; // @0x98 this[0x26] attr idx
|
||||
int maxSeekIndex; // @0x9C this[0x27] attr idx
|
||||
int seekVoltageTable; // @0xA0 this[0x28] attr idx
|
||||
Entity *voltageSubsystem; // @0xA4 this[0x29]
|
||||
int *currentSeekIndexPtr; // @0x94 this[0x25] attr ptr (subsys+off)
|
||||
int *minSeekIndexPtr; // @0x98 this[0x26] attr ptr
|
||||
int *maxSeekIndexPtr; // @0x9C this[0x27] attr ptr
|
||||
Scalar *seekVoltageTablePtr; // @0xA0 this[0x28] attr ptr (array base)
|
||||
Scalar *liveVoltage; // @0xA4 this[0x29] the ctor's
|
||||
// seek_voltage_pointer ("OutputVoltage"
|
||||
// attr ptr / cluster seekValue cache) --
|
||||
// Execute derefs it for the live cursor
|
||||
Subsystem *subsystem; // @0xA8 this[0x2A]
|
||||
Scalar previousVoltage; // @0xAC this[0x2B] (9999 = redraw)
|
||||
Scalar previousVoltage; // @0xAC this[0x2B] cached response at
|
||||
// 12000 V (9999 sentinel = force replot)
|
||||
Scalar xScale; // @0xB0 this[0x2C] =230.0f
|
||||
Scalar yScale; // @0xB4 this[0x2D] =187.0f
|
||||
int destroyedShown; // @0xB8 this[0x2E]
|
||||
Logical drawCursor; // @0xBC this[0x2F]
|
||||
// helpers: SetupClip @004c6be4, DrawCursor @004c6c30, DrawTicks @004c6c6c
|
||||
int cursorX; // @0xC0 this[0x30] (-1 = no cursor drawn)
|
||||
int cursorY; // @0xC4 this[0x31]
|
||||
int tickIndexShown; // @0xC8 this[0x32] gear index the tick
|
||||
// highlight was last drawn at
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
# include <testbt.hpp>
|
||||
#endif
|
||||
#include <hostmgr.hpp> // HostManager::GetEntityPointer -- the update-record target resolve (task #51)
|
||||
#include <math.h> // sqrt -- the SeekVoltageResponse sampler (issue #11)
|
||||
|
||||
// E8 (bring-up): the player fire input. The controls mapper that would normally write
|
||||
// the weapon's fireImpulse is bypassed (mech4.cpp), so EmitterSimulation reads this
|
||||
@@ -153,13 +154,49 @@ Receiver::MessageHandlerSet&
|
||||
MechWeapon::GetMessageHandlers()); // copy-inherit
|
||||
return messageHandlers;
|
||||
}
|
||||
Simulation::AttributeIndexSet Emitter::AttributeIndex;
|
||||
//
|
||||
// issue #11: the AUTHENTIC Emitter attribute table (binary @0x511dd4, dumped
|
||||
// records {id, name*, offset|1, 0}, ids 0x1D..0x25 -- a dense run chained
|
||||
// straight after MechWeapon's 0x1C WeaponState, so no pads needed). The four
|
||||
// Seek* rows are what the SeekVoltageGraph ctor (@004c6798) resolves by name;
|
||||
// OutputVoltage here binds currentLevel@0x414 -- the RAW charge voltage (the
|
||||
// graph's live-cursor feed), which the retired MechWeapon 0x1D port alias
|
||||
// (rechargeLevel, 0..1) used to shadow. Function-local statics per the
|
||||
// static-init-order rule (reconstruction-gotchas #9).
|
||||
//
|
||||
// The dense-prefix lock (gotcha #11): the run must start exactly one past
|
||||
// MechWeapon's last id (0x1C WeaponState) == the binary's own numbering.
|
||||
static_assert((int)Emitter::LaserOnAttributeID == 0x1D,
|
||||
"Emitter attr run must chain at 0x1D (binary table @0x511dd4) -- "
|
||||
"did a MechWeapon attribute get added/removed?");
|
||||
|
||||
Emitter::AttributeIndexSet&
|
||||
Emitter::GetAttributeIndex()
|
||||
{
|
||||
static const Emitter::IndexEntry entries[] =
|
||||
{
|
||||
ATTRIBUTE_ENTRY(Emitter, LaserOn, firingActive), // 0x1D @0x418
|
||||
ATTRIBUTE_ENTRY(Emitter, LaserScale, beamScale), // 0x1E @0x42C
|
||||
ATTRIBUTE_ENTRY(Emitter, LaserRotation, beamOrientation), // 0x1F @0x41C
|
||||
ATTRIBUTE_ENTRY(Emitter, CurrentSeekVoltageIndex, seekVoltageIndex), // 0x20 @0x3F0
|
||||
ATTRIBUTE_ENTRY(Emitter, RecommendedSeekVoltageIndex, seekVoltageRecommendedIndex), // 0x21 @0x3F4
|
||||
ATTRIBUTE_ENTRY(Emitter, MinSeekVoltageIndex, minSeekVoltageIndex), // 0x22 @0x3F8
|
||||
ATTRIBUTE_ENTRY(Emitter, MaxSeekVoltageIndex, maxSeekVoltageIndex), // 0x23 @0x3FC
|
||||
ATTRIBUTE_ENTRY(Emitter, SeekVoltage, seekVoltage), // 0x24 @0x400 (array base)
|
||||
ATTRIBUTE_ENTRY(Emitter, OutputVoltage, currentLevel) // 0x25 @0x414 (RAW volts)
|
||||
};
|
||||
static Emitter::AttributeIndexSet attributeIndex(
|
||||
ELEMENTS(entries), entries,
|
||||
MechWeapon::GetAttributeIndex()
|
||||
);
|
||||
return attributeIndex;
|
||||
}
|
||||
|
||||
Emitter::SharedData
|
||||
Emitter::DefaultData(
|
||||
&Emitter::ClassDerivations,
|
||||
Emitter::GetMessageHandlers(),
|
||||
MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty)
|
||||
Emitter::GetAttributeIndex(), // own table (Seek*/Laser*/OutputVoltage) chained to MechWeapon's
|
||||
Emitter::StateCount
|
||||
);
|
||||
|
||||
@@ -590,7 +627,7 @@ int
|
||||
}
|
||||
|
||||
int prev = seekVoltageIndex; // 0x3f0
|
||||
int modulus = seekVoltageCount + 1; // 0x3fc + 1
|
||||
int modulus = maxSeekVoltageIndex + 1; // 0x3fc + 1
|
||||
int next = (prev + 1) % modulus;
|
||||
seekVoltageIndex = next; // 0x3f0
|
||||
if (prev < next)
|
||||
@@ -663,6 +700,35 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @004bb3f4 -- the seek discharge power at a supply voltage (issue #11,
|
||||
// capstone disasm: fld v; fmul v; fmul [0x4bb428]=0.5f; fmul [this+0x454];
|
||||
// -> * [this+0x444]):
|
||||
// P(v) = damageFraction * (v^2 * 0.5 * energyCoefficient)
|
||||
// i.e. the damage share of the E = 1/2 k V^2 discharge energy at voltage v.
|
||||
//
|
||||
Scalar
|
||||
Emitter::SeekPower(Scalar voltage)
|
||||
{
|
||||
return damageFraction * (voltage * voltage * 0.5f * energyCoefficient); // 0x444 / 0x454
|
||||
}
|
||||
|
||||
//
|
||||
// @004bb42c (vtable slot 15) -- the SeekVoltageGraph voltage-response sampler
|
||||
// (issue #11). Disasm: locals A = 0x4d3ebc20 (exactly 2.0e8f), B = 0;
|
||||
// r = (SeekPower(v) - B) / (A - B); FUN_004dd138(r) == sqrt (part_015.c:4026 --
|
||||
// the old "fp magnitude" reading was wrong). So: the graph's x-axis is the
|
||||
// square root of the discharge power normalised to a 2.0e8 full scale.
|
||||
//
|
||||
Scalar
|
||||
Emitter::SeekVoltageResponse(Scalar voltage)
|
||||
{
|
||||
Scalar r = (SeekPower(voltage) - 0.0f) / (2.0e8f - 0.0f); // 0x4d3ebc20
|
||||
// (FUN_004dd138 routes a negative operand to the matherr handler; P >= 0
|
||||
// always here -- guarded for port safety.)
|
||||
return (r > 0.0f) ? (Scalar)sqrt((double)r) : 0.0f;
|
||||
}
|
||||
|
||||
|
||||
//#############################################################################
|
||||
// Subsystem virtual overrides
|
||||
@@ -855,7 +921,7 @@ Emitter::Emitter(
|
||||
energyCoefficient = 1.0f; // 0x454
|
||||
seekVoltageIndex = 0; // 0x3f0
|
||||
seekVoltageRecommendedIndex = 0; // 0x3f4
|
||||
seekVoltageCount = 0; // 0x3fc
|
||||
maxSeekVoltageIndex = 0; // 0x3fc
|
||||
for (int sv = 0; sv < 5; ++sv)
|
||||
seekVoltage[sv] = 1.0f; // 0x400[]
|
||||
|
||||
@@ -894,7 +960,7 @@ Emitter::Emitter(
|
||||
{
|
||||
if (subsystem_resource->seekVoltage[i] == -1.0f) // _DAT_004bb3b0 sentinel
|
||||
{
|
||||
seekVoltageCount = i - 1; // 0x3fc
|
||||
maxSeekVoltageIndex = i - 1; // 0x3fc (= count-1)
|
||||
break;
|
||||
}
|
||||
// the authored values are FRACTIONS of the generator's rated
|
||||
@@ -904,7 +970,7 @@ Emitter::Emitter(
|
||||
}
|
||||
seekVoltageRecommendedIndex = subsystem_resource->seekVoltageRecommendedIndex; // 0x3f4 <- +0x1d8
|
||||
seekVoltageIndex = seekVoltageRecommendedIndex; // 0x3f0
|
||||
seekStepCounter = 0; // 0x3f8
|
||||
minSeekVoltageIndex = 0; // 0x3f8
|
||||
|
||||
// energyCoefficient = energyTotal / (seekVoltage[rec]^2 * 0.5)
|
||||
// -> E = 0.5 * V^2 * EC lands exactly on energyTotal at V=seekV[rec]
|
||||
@@ -935,7 +1001,7 @@ Emitter::Emitter(
|
||||
// damageFraction = damageAmount / (damageAmount + heatCostToFire)
|
||||
damageFraction = damageData.damageAmount /
|
||||
(damageData.damageAmount + heatCostToFire); // 0x444
|
||||
seekStepCounter = 0; // 0x3f0/0x45c bookkeeping
|
||||
minSeekVoltageIndex = 0; // 0x3f8 (always 0 -- attr 0x22)
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
@@ -1063,3 +1129,27 @@ Subsystem *CreateEmitterSubsystem(Mech *owner, int id, void *seg)
|
||||
return (Subsystem *) new (Memory::Allocate(0x478))
|
||||
Emitter(owner, id, (Emitter::SubsystemResource *)seg, Emitter::DefaultData);
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// issue #11 bridge -- the SeekVoltageGraph's voltage-response sample (the
|
||||
// binary calls the SUBSYSTEM vtbl+0x3C / slot 15 virtually; the port's gauge
|
||||
// TU cannot include the weapon headers, so the dispatch lives here in a
|
||||
// complete-type TU per the databinding rule, gotcha 8). Emitter (and PPC,
|
||||
// which IS an Emitter both by derivation and by factory) -> @004bb42c;
|
||||
// everything else forwards to the Myomers bridge (myomers.cpp, @004b8f94).
|
||||
//===========================================================================//
|
||||
extern Scalar BTMyomersSeekSample(void *subsystem, Scalar voltage); // myomers.cpp
|
||||
|
||||
Scalar BTSeekVoltageSample(void *subsystem, Scalar voltage)
|
||||
{
|
||||
Entity *entity = (Entity *)subsystem;
|
||||
if (entity == 0)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
if (entity->IsDerivedFrom(Emitter::ClassDerivations))
|
||||
{
|
||||
return ((Emitter *)entity)->SeekVoltageResponse(voltage); // @004bb42c
|
||||
}
|
||||
return BTMyomersSeekSample(subsystem, voltage); // @004b8f94 (guarded)
|
||||
}
|
||||
|
||||
@@ -89,7 +89,29 @@ class NotationFile;
|
||||
public:
|
||||
static Derivation ClassDerivations;
|
||||
static Receiver::MessageHandlerSet& GetMessageHandlers(); // inherits MechWeapon's (task #6)
|
||||
static AttributeIndexSet AttributeIndex;
|
||||
|
||||
//
|
||||
// The AUTHENTIC Emitter attribute table (binary @0x511dd4, 9 records
|
||||
// {id, name, offset|1, 0}, ids 0x1D..0x25 chained after MechWeapon's
|
||||
// 0x1C WeaponState -- dumped issue #11). The Seek* rows feed the
|
||||
// SeekVoltageGraph engineering-page widget (ctor @004c6798 resolves
|
||||
// them by NAME); OutputVoltage binds the RAW charge voltage
|
||||
// currentLevel@0x414 (the graph's live cursor feed) -- NOT the 0..1
|
||||
// rechargeLevel (the retired MechWeapon port alias).
|
||||
//
|
||||
enum {
|
||||
LaserOnAttributeID = MechWeapon::NextAttributeID, // 0x1D @0x418 firingActive
|
||||
LaserScaleAttributeID, // 0x1E @0x42C beamScale
|
||||
LaserRotationAttributeID, // 0x1F @0x41C beamOrientation
|
||||
CurrentSeekVoltageIndexAttributeID, // 0x20 @0x3F0
|
||||
RecommendedSeekVoltageIndexAttributeID, // 0x21 @0x3F4
|
||||
MinSeekVoltageIndexAttributeID, // 0x22 @0x3F8
|
||||
MaxSeekVoltageIndexAttributeID, // 0x23 @0x3FC
|
||||
SeekVoltageAttributeID, // 0x24 @0x400 (array base)
|
||||
OutputVoltageAttributeID, // 0x25 @0x414 currentLevel (RAW volts)
|
||||
NextAttributeID // 0x26
|
||||
};
|
||||
static AttributeIndexSet& GetAttributeIndex();
|
||||
static SharedData DefaultData;
|
||||
|
||||
// Update record (replication) -- extends the MechWeapon record with the
|
||||
@@ -172,6 +194,8 @@ class NotationFile;
|
||||
// slot 9 TakeDamage @004bafc8 (NOT yet reconstructed)
|
||||
// slot 10 ResetToInitialState (MechWeapon base @004b96ec)
|
||||
// slot 13 PrintState (MechWeapon base @004b9d00)
|
||||
// slot 15 SeekVoltageResponse @004bb42c (the SeekVoltageGraph sampler;
|
||||
// Myomers' analog @004b8f94 shares the slot)
|
||||
// slot 16 ReadyToDischarge (Simulation interface)
|
||||
// slot 17 ComputeOutputVoltage (Simulation interface)
|
||||
// slot 18 FireWeapon
|
||||
@@ -184,6 +208,13 @@ class NotationFile;
|
||||
void
|
||||
PrintState(); // slot 13, @004bb014
|
||||
|
||||
// @004bb42c (vtable slot 15) -- the SeekVoltageGraph's voltage-response
|
||||
// sampler: sqrt((SeekPower(v) - 0) / (2.0e8f - 0)) [FUN_004dd138 = sqrt;
|
||||
// consts from the capstone disasm, issue #11]. Maps a supply voltage to
|
||||
// the graph's 0..1 x-axis (normalised sqrt of the discharge power).
|
||||
Scalar
|
||||
SeekVoltageResponse(Scalar voltage); // slot 15, @004bb42c
|
||||
|
||||
//
|
||||
// Beam-render accessors (the visible-beam layer draws each weapon's own
|
||||
// beam from this live sim state -- the authentic per-weapon fire look).
|
||||
@@ -233,6 +264,12 @@ class NotationFile;
|
||||
// Internal charge / discharge helpers (non-virtual; called by the sim)
|
||||
//
|
||||
protected:
|
||||
// @004bb3f4 -- the seek discharge power at a supply voltage:
|
||||
// damageFraction * v^2 * 0.5 [_DAT_004bb428] * energyCoefficient.
|
||||
// (== the E = 1/2 k V^2 discharge energy scaled by the damage split.)
|
||||
Scalar
|
||||
SeekPower(Scalar voltage); // @004bb3f4
|
||||
|
||||
// @004ba838 -- track the linked voltage source: derive a seek rate and
|
||||
// integrate currentLevel; dump the I^2R heat back into the source.
|
||||
void
|
||||
@@ -313,8 +350,15 @@ class NotationFile;
|
||||
// * beamLengthRatio -> is beamScale.z@0x434
|
||||
int seekVoltageIndex; // @0x3F0 (param_1[0xfc]) current charge-curve index
|
||||
int seekVoltageRecommendedIndex;// @0x3F4 (param_1[0xfd]) recommended index
|
||||
int seekStepCounter; // @0x3F8 (param_1[0xfe]) reset 0
|
||||
int seekVoltageCount; // @0x3FC (param_1[0xff]) index modulus / count-1
|
||||
// issue #11 IDENTITY CORRECTION (binary attribute table @0x511dd4):
|
||||
// @0x3F8 is published as "MinSeekVoltageIndex" and @0x3FC as
|
||||
// "MaxSeekVoltageIndex" -- the old names (seekStepCounter, "reset 0" /
|
||||
// seekVoltageCount, "count-1") were best-effort guesses that happened
|
||||
// to carry exactly those values (min is always 0; max = count-1, the
|
||||
// AdvanceSeekVoltage modulus-1). Renamed to the attribute truth --
|
||||
// the exact analog of Myomers @0x328/@0x32C.
|
||||
int minSeekVoltageIndex; // @0x3F8 (param_1[0xfe]) always 0
|
||||
int maxSeekVoltageIndex; // @0x3FC (param_1[0xff]) count-1 (index modulus - 1)
|
||||
Scalar seekVoltage[5]; // @0x400 (param_1[0x100..0x104]) per-index target voltages
|
||||
Scalar currentLevel; // @0x414 (param_1[0x105]) accumulated charge
|
||||
int firingActive; // @0x418 (param_1[0x106]) reset 0; FireWeapon sets 1
|
||||
@@ -340,6 +384,11 @@ class NotationFile;
|
||||
|
||||
struct EmitterLayoutCheck {
|
||||
static_assert(offsetof(Emitter, seekVoltageIndex) == 0x3F0, "Emitter::seekVoltageIndex @0x3F0 (MechWeapon must end 0x3F0)");
|
||||
// issue #11: the four attribute-published Seek* slots (table @0x511dd4)
|
||||
static_assert(offsetof(Emitter, minSeekVoltageIndex) == 0x3F8, "Emitter::minSeekVoltageIndex @0x3F8 (attr 0x22)");
|
||||
static_assert(offsetof(Emitter, maxSeekVoltageIndex) == 0x3FC, "Emitter::maxSeekVoltageIndex @0x3FC (attr 0x23)");
|
||||
static_assert(offsetof(Emitter, seekVoltage) == 0x400, "Emitter::seekVoltage @0x400 (attr 0x24)");
|
||||
static_assert(offsetof(Emitter, firingActive) == 0x418, "Emitter::firingActive @0x418 (attr 0x1D LaserOn)");
|
||||
static_assert(offsetof(Emitter, currentLevel) == 0x414, "Emitter::currentLevel @0x414");
|
||||
static_assert(offsetof(Emitter, beamOrientation) == 0x41C, "Emitter::beamOrientation @0x41C (Quaternion 16B)");
|
||||
static_assert(offsetof(Emitter, beamScale) == 0x42C, "Emitter::beamScale @0x42C");
|
||||
|
||||
@@ -5214,11 +5214,19 @@ void
|
||||
&& getenv("BT_PRESET_TEST"))
|
||||
{
|
||||
static int s_ptFrame = 0;
|
||||
static int s_ptPulses = 0;
|
||||
++s_ptFrame;
|
||||
int t0 = atoi(getenv("BT_PRESET_TEST"));
|
||||
if (t0 < 1) t0 = 600;
|
||||
if (s_ptFrame >= t0 && (s_ptFrame - t0) % 120 == 0)
|
||||
// issue #11: BT_PRESET_HOLD=<n> stops after n pulses so a page can
|
||||
// be HELD indefinitely (steady-state ghost/graph verification --
|
||||
// the 120-frame cycle otherwise phase-locks with BT_SHOT's 90-frame
|
||||
// cadence, so a fixed fraction of shots lands on transition frames).
|
||||
int holdAfter = getenv("BT_PRESET_HOLD") ? atoi(getenv("BT_PRESET_HOLD")) : 0;
|
||||
if (s_ptFrame >= t0 && (s_ptFrame - t0) % 120 == 0
|
||||
&& (holdAfter <= 0 || s_ptPulses < holdAfter))
|
||||
{
|
||||
++s_ptPulses;
|
||||
gBTPresetCycle[0] = 1;
|
||||
gBTPresetCycle[1] = 1;
|
||||
gBTPresetCycle[2] = 1;
|
||||
|
||||
@@ -118,10 +118,15 @@ static_assert((int)PoweredSubsystem::NextAttributeID
|
||||
const MechWeapon::IndexEntry
|
||||
MechWeapon::AttributePointers[]=
|
||||
{
|
||||
// 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).
|
||||
// task #16: the FULL binary table @0x511890 (eleven entries). Every member
|
||||
// offset matches the binary's (layout static_assert-locked); TriggerState
|
||||
// stays the streamed fire-button binding (id 0x13 PINNED).
|
||||
// issue #11: the port-extra 0x1D "OutputVoltage" alias (-> rechargeLevel) is
|
||||
// RETIRED -- the binary MechWeapon table ends at 0x1C, and the alias SHADOWED
|
||||
// the authentic Emitter-level OutputVoltage (currentLevel@0x414, RAW volts --
|
||||
// the SeekVoltageGraph cursor feed) because AttributeIndexSet::Find walks
|
||||
// lowest-id-first. Energy weapons now publish their own table (emitter.cpp
|
||||
// ids 0x1D-0x25); no ballistic-weapon consumer ever bound the alias.
|
||||
//
|
||||
// The PADS fill our chain-vs-binary id gap (now 0x0F..0x11) with valid,
|
||||
// named, never-bound entries (the binary's own ids here are the powered/
|
||||
@@ -142,8 +147,7 @@ const MechWeapon::IndexEntry
|
||||
ATTRIBUTE_ENTRY(MechWeapon, PipExtendedRange, pipExtendedRange), // 0x19 @0x348
|
||||
ATTRIBUTE_ENTRY(MechWeapon, EstimatedReadyTime, estimatedReadyTime),// 0x1A @0x330
|
||||
ATTRIBUTE_ENTRY(MechWeapon, RearFiring, rearFiring), // 0x1B @0x334 (task #68: real semantics)
|
||||
ATTRIBUTE_ENTRY(MechWeapon, WeaponState, weaponAlarm), // 0x1C @0x350
|
||||
ATTRIBUTE_ENTRY(MechWeapon, OutputVoltage, rechargeLevel) // 0x1D (port alias)
|
||||
ATTRIBUTE_ENTRY(MechWeapon, WeaponState, weaponAlarm) // 0x1C @0x350 (binary table END)
|
||||
};
|
||||
|
||||
MechWeapon::AttributeIndexSet&
|
||||
|
||||
@@ -140,13 +140,14 @@ class CockpitHud;
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Attribute Support (gauge data-binding wave)
|
||||
//
|
||||
// The energy/ballistic weapon engineering clusters read OutputVoltage (the
|
||||
// charge/ready level -- animates as the weapon recharges after firing) and
|
||||
// PercentDone (the recharge dial), both onto rechargeLevel (1.0 == ready).
|
||||
// GetAttributeIndex() chains to PoweredSubsystem's so InputVoltage + the
|
||||
// HeatSink temps also resolve; Emitter/PPC/ProjectileWeapon/MissileLauncher
|
||||
// point their DefaultData at MechWeapon::GetAttributeIndex() to inherit it
|
||||
// (they carried an EMPTY default-constructed index -> resolved NOTHING).
|
||||
// The weapon engineering clusters read PercentDone (the recharge dial) onto
|
||||
// rechargeLevel (1.0 == ready). GetAttributeIndex() chains to
|
||||
// PoweredSubsystem's so InputVoltage + the HeatSink temps also resolve;
|
||||
// ProjectileWeapon/MissileLauncher point their DefaultData at
|
||||
// MechWeapon::GetAttributeIndex() to inherit it (they carried an EMPTY
|
||||
// default-constructed index -> resolved NOTHING). Emitter/PPC now chain
|
||||
// their OWN authentic table on top (emitter.cpp, ids 0x1D-0x25: Laser*/
|
||||
// Seek*/OutputVoltage=currentLevel -- issue #11).
|
||||
//
|
||||
public:
|
||||
enum {
|
||||
@@ -203,10 +204,12 @@ class CockpitHud;
|
||||
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
|
||||
WeaponStateAttributeID, // 0x1C @0x350 weaponAlarm (binary table END)
|
||||
// issue #11: the 0x1D "OutputVoltage" PORT-EXTRA alias is RETIRED --
|
||||
// it shadowed the authentic Emitter table's OutputVoltage
|
||||
// (currentLevel@0x414 RAW volts, ids 0x1D-0x25 chain here exactly
|
||||
// as in the binary; Find walks lowest-id-first).
|
||||
NextAttributeID // 0x1D == the binary chain value
|
||||
};
|
||||
private:
|
||||
static const IndexEntry AttributePointers[];
|
||||
|
||||
@@ -40,8 +40,9 @@
|
||||
// ConnectToMover/DisconnectFromMover bodies.
|
||||
// best-effort : MyomersSimulation (@004b8d18) is the per-tick Performance
|
||||
// (only reachable via function pointer; SetPerformance call
|
||||
// site not captured -- see note); GetSpeedReading (@004b8f94,
|
||||
// slot 15) purpose; ToggleSeekVoltage (@004b8a48) not present
|
||||
// site not captured -- see note); [slot 15 @004b8f94 RESOLVED
|
||||
// issue #11: SeekVoltageResponse -- the SeekVoltageGraph
|
||||
// sampler, sqrt not fabs]; ToggleSeekVoltage (@004b8a48) not present
|
||||
// in the decompiled shards; the heat-base field @0x150 and
|
||||
// graphic-state block (this[7..9]) inherit-pattern stores.
|
||||
// excluded : @0x4b7f94..@0x4b881c (gimbal / torso-horizon display, base
|
||||
@@ -315,7 +316,7 @@ int Myomers::CreateStreamedSubsystem(
|
||||
//***************************************************************************
|
||||
//
|
||||
// Core drive computation: returns the locomotion drive available for a given
|
||||
// input voltage. (helper; called by RegisterMaxOutput and GetSpeedReading.)
|
||||
// input voltage. (helper; called by RegisterMaxOutput and SeekVoltageResponse.)
|
||||
//
|
||||
// base = ownerMech[0x34C] * (input_voltage / seekVoltage[recommendedIndex])
|
||||
// ^ Mech base-speed scalar ^ normalise to the recommended gear
|
||||
@@ -372,19 +373,40 @@ void Myomers::RegisterMaxOutput()
|
||||
|
||||
|
||||
//***************************************************************************
|
||||
// Myomers::GetSpeedReading slot 15 @004b8f94
|
||||
// Myomers::SeekVoltageResponse slot 15 @004b8f94
|
||||
//***************************************************************************
|
||||
//
|
||||
// PoweredSubsystem virtual slot 15 (base @004b1780). Best-effort: converts
|
||||
// AvailableOutput(input_voltage) into a normalised gauge reading -- output is
|
||||
// scaled by 3.6 (m/s -> km/h) and divided by 350 (gauge full scale), then run
|
||||
// through FUN_004dd138 (fp magnitude helper / sqrt). Drives a cockpit
|
||||
// speed gauge. (Decompiled body discards the result -> returned via FPU.)
|
||||
// PoweredSubsystem virtual slot 15 (base @004b1780) == the shared subsystem
|
||||
// vtbl+0x3C sampler the SeekVoltageGraph's Execute (@004c6934) plots -- THIS
|
||||
// is what draws the myomer engineering page's POWER curve (issue #11; the
|
||||
// old "drives a cockpit speed gauge" guess and the GetSpeedReading name are
|
||||
// retired). Converts AvailableOutput(input_voltage) into the graph's 0..1
|
||||
// x-response: scaled by 3.6 (m/s -> km/h), normalised to a 350 km/h full
|
||||
// scale, then FUN_004dd138 == SQRT (part_015.c:4026 -- the old "fp magnitude
|
||||
// / fabs" reading was wrong; sqrt is also what Emitter's analog @004bb42c
|
||||
// applies to its power ratio). (Decompiled body discards the result ->
|
||||
// returned via FPU.)
|
||||
//
|
||||
Scalar Myomers::GetSpeedReading(Scalar input_voltage)
|
||||
Scalar Myomers::SeekVoltageResponse(Scalar input_voltage)
|
||||
{
|
||||
Scalar output = AvailableOutput(input_voltage); // @004b8ac0
|
||||
return (Scalar)fabs((double)((output * 3.6f - 0.0f) / 350.0f)); // FUN_004dd138 (fp magnitude)
|
||||
Scalar r = (output * 3.6f - 0.0f) / 350.0f;
|
||||
// (FUN_004dd138 routes a negative operand to the matherr handler; output
|
||||
// >= 0 in practice -- guarded for port safety.)
|
||||
return (r > 0.0f) ? (Scalar)sqrt((double)r) : 0.0f; // FUN_004dd138 (sqrt)
|
||||
}
|
||||
|
||||
//***************************************************************************
|
||||
// issue #11 bridge -- the Myomers leg of the SeekVoltageGraph sample dispatch
|
||||
// (see emitter.cpp BTSeekVoltageSample). Guarded: a non-Myomers subsystem
|
||||
// samples 0 (only emitter/myomer pages own a graph in the shipped config).
|
||||
//***************************************************************************
|
||||
Scalar BTMyomersSeekSample(void *subsystem, Scalar voltage)
|
||||
{
|
||||
Entity *entity = (Entity *)subsystem;
|
||||
if (entity == 0 || !entity->IsDerivedFrom(Myomers::ClassDerivations))
|
||||
return 0.0f;
|
||||
return ((Myomers *)entity)->SeekVoltageResponse(voltage); // @004b8f94
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -245,10 +245,15 @@ class Mech;
|
||||
HandleMessage(int message); // slot 9, @004b8a8c (-> PoweredSubsystem @004b0efc)
|
||||
void
|
||||
ResetToInitialState(Logical powered = True); // slot 10, @004b8aa4 (-> PoweredSubsystem @004b0e6c)
|
||||
// slot 15 (PoweredSubsystem base @004b1780) -- normalised speed/gauge readout
|
||||
// (output km/h scaled to 350): GetSpeedReading @004b8f94
|
||||
// slot 15 (PoweredSubsystem base @004b1780) -- the SeekVoltageGraph's
|
||||
// voltage-response sampler (issue #11: the shared subsystem
|
||||
// vtbl+0x3C slot the graph's Execute @004c6934 calls; Emitter's
|
||||
// analog is @004bb42c): sqrt(AvailableOutput(v)*3.6 / 350)
|
||||
// -- normalised sqrt of the drive speed in km/h against a
|
||||
// 350 km/h full scale. (Renamed from the best-effort
|
||||
// "GetSpeedReading"; FUN_004dd138 is sqrt, not fabs.)
|
||||
Scalar
|
||||
GetSpeedReading(Scalar input_voltage); // slot 15, @004b8f94 (name best-effort)
|
||||
SeekVoltageResponse(Scalar input_voltage); // slot 15, @004b8f94
|
||||
// slot 16 (PoweredSubsystem::IsSourceShorted base @004b0b5c) -- Myomers
|
||||
// requires the source to supply at least the selected seek voltage
|
||||
// AND be in the Ready (==2) state.
|
||||
|
||||
@@ -1449,3 +1449,18 @@ bool BTGetSubsystemAuxScreen(Subsystem *sub, int *screen, int *placement, char *
|
||||
if (label64 != NULL) strcpy(label64, ps->auxScreenLabel); // +0x10C
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// issue #11 bridge -- the SeekVoltageGraph's destroyed test:
|
||||
// *(subsystem+0x40) == 1 in the binary (@004c6934) == MechSubsystem::
|
||||
// simulationState == 1 (Destroyed). Named-member read through the real type
|
||||
// (databinding rule); every subsystem that owns a graph is MechSubsystem-
|
||||
// derived (Emitter/PPC/Myomers), so the PoweredSubsystem gate is sufficient
|
||||
// and a non-match reads "not destroyed".
|
||||
//
|
||||
int BTSubsystemDestroyed(Subsystem *sub)
|
||||
{
|
||||
if (!BTIsPoweredSubsystem(sub))
|
||||
return 0;
|
||||
return (((PoweredSubsystem *)sub)->GetSimulationState() == 1) ? 1 : 0; // +0x40 == 1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user