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
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user