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:
arcattack
2026-07-19 20:27:39 -05:00
co-authored by Claude Fable 5
parent f1a6ccbaaf
commit 63b168cb92
15 changed files with 594 additions and 109 deletions
+33 -11
View File
@@ -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
}