BT410 Phase 5.3.8: weapon fire path -- authentic attribute table + fire state machine
The energy weapons now run a live fire cycle, and the REAL trigger wiring is in place: the 1995 MechWeapon attribute table (binary @0x511890) is published with PINNED IDs, so the streamed per-mech fire-button mappings bind TriggerState (0x13) -> fireImpulse and the controls push writes the trigger into the weapon. - MECHWEAP: attribute enum + table (PercentDone 0x12, TriggerState 0x13, DistanceToTarget 0x14, TargetWithinRange 0x15, WeaponRange 0x16, EstimatedReadyTime 0x1A, RearFiring 0x1B, WeaponState 0x1C). Pads bridge the chain gap 2..0x11 -- LOAD-BEARING: AttributeIndexSet::Build leaves uncovered gap slots as uninitialized garbage, so every ID up to the max must be covered (they shrink to the authentic 0x0F..0x11 when the parent attribute waves land; SENSOR.HPP pins PoweredSubsystem::NextAttributeID at 0x0F). - MECHWEAP: fire-machine members (fireImpulse/previousFireImpulse, rechargeLevel, rangeToTarget, effectiveRange, estimatedReadyTime, recoil, weaponAlarm 0/2/3/4); CheckFireEdge (@004b9608, BIT-PATTERN int compare -- TriggerState carries raw ControlsButton ints whose release value is a float NaN; IEEE compare would latch the detector shut); ComputeOutputVoltage (@004b9c9c recharge dial). - EMITTER: EmitterSimulation (PARTIAL @004baa88) -- Firing/Loaded/Loading FSM on the weapon alarm, viewFireEnable gate at Loaded->Firing, recoil-decay recharge over the authored RechargeRate seconds; FireWeapon (PARTIAL @004bace8) discharge bookkeeping; ChargeLevel re-pinned to the authentic 0x1D; index re-chained to MechWeapon's. Deferred: electrical charge model (TrackSeekVoltage), heat dump, HasActiveTarget gate, beam/damage submission. - BT_FORCE_FIRE dev hook: trigger pulse whenever Loaded (auto-fire cadence). VERIFIED headlessly: forward view -- PPC_1/2 + ERMLaser_1 cycle FIRED->LOADED at the authored 0.6s/1s cadence, rear lasers silent; BT_FORCE_LOOK=3 -- ONLY the rear lasers (ERMLaser_2/3) fire. The view-fire x fire-FSM interlock holds both directions. Zero Fail. (SRM6 ballistic FSM = a later increment.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,13 +27,15 @@ Derivation
|
|||||||
|
|
||||||
//
|
//
|
||||||
//#############################################################################
|
//#############################################################################
|
||||||
// Attribute Support. Emitter publishes the beam charge level (ChargeLevel):
|
// Attribute Support. Emitter publishes the beam charge level (ChargeLevel, at
|
||||||
// the HUD weapon-charge gauge binds to it, and -- load-bearing -- GaussRifle's
|
// the authentic 0x1D past the MechWeapon table end): the HUD weapon-charge
|
||||||
// AttributeIndex chains THIS index (GAUSS.CPP), and PPC::DefaultData binds it
|
// gauge binds it, and -- load-bearing -- GaussRifle's AttributeIndex chains
|
||||||
// via the inherited PPC::AttributeIndex. It MUST be a real defined index
|
// THIS index (GAUSS.CPP), and PPC::DefaultData binds it via the inherited
|
||||||
// chained to Subsystem::AttributeIndex, not the bare base (a declared-but-
|
// PPC::AttributeIndex. It MUST be a real defined index (a declared-but-
|
||||||
// undefined Emitter::AttributeIndex leaves activeAttributeIndex NULL and
|
// undefined Emitter::AttributeIndex leaves activeAttributeIndex NULL and
|
||||||
// faults the first GetAttributePointer on any PPC/Gauss).
|
// faults the first GetAttributePointer on any PPC/Gauss). Chained to the
|
||||||
|
// MechWeapon index so every energy weapon exposes the full weapon table
|
||||||
|
// (TriggerState / PercentDone / ...).
|
||||||
//#############################################################################
|
//#############################################################################
|
||||||
//
|
//
|
||||||
const Emitter::IndexEntry
|
const Emitter::IndexEntry
|
||||||
@@ -46,13 +48,13 @@ Emitter::AttributeIndexSet
|
|||||||
Emitter::AttributeIndex(
|
Emitter::AttributeIndex(
|
||||||
ELEMENTS(Emitter::AttributePointers),
|
ELEMENTS(Emitter::AttributePointers),
|
||||||
Emitter::AttributePointers,
|
Emitter::AttributePointers,
|
||||||
Subsystem::AttributeIndex
|
MechWeapon::AttributeIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
Emitter::SharedData
|
Emitter::SharedData
|
||||||
Emitter::DefaultData(
|
Emitter::DefaultData(
|
||||||
Emitter::ClassDerivations,
|
Emitter::ClassDerivations,
|
||||||
Subsystem::MessageHandlers,
|
MechWeapon::MessageHandlers,
|
||||||
Emitter::AttributeIndex,
|
Emitter::AttributeIndex,
|
||||||
Subsystem::StateCount
|
Subsystem::StateCount
|
||||||
);
|
);
|
||||||
@@ -70,6 +72,16 @@ Emitter::Emitter(
|
|||||||
|
|
||||||
chargeLevel = 0.0f;
|
chargeLevel = 0.0f;
|
||||||
dischargeTime = subsystem_resource->dischargeTime;
|
dischargeTime = subsystem_resource->dischargeTime;
|
||||||
|
dischargeTimer = 0.0f;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Install the beam-weapon fire state machine (a replicant copy is driven by
|
||||||
|
// console updates / ServiceDischarge instead, still staged).
|
||||||
|
//
|
||||||
|
if (owner->GetInstance() != Entity::ReplicantInstance)
|
||||||
|
{
|
||||||
|
SetPerformance(&Emitter::EmitterSimulation);
|
||||||
|
}
|
||||||
|
|
||||||
Check_Fpu();
|
Check_Fpu();
|
||||||
}
|
}
|
||||||
@@ -92,14 +104,148 @@ Logical
|
|||||||
|
|
||||||
//
|
//
|
||||||
//#############################################################################
|
//#############################################################################
|
||||||
// FireWeapon The energy-beam discharge. Not yet reconstructed (the fire path
|
// FireWeapon The energy-beam discharge (PARTIAL -- binary @004bace8). The
|
||||||
// is exercised in combat, past the current ctor frontier).
|
// authentic body: re-arm the beam-on countdown, compute the per-shot damage /
|
||||||
|
// heat from the charge energy (0.5*V^2*EC closed forms), dump the heat into
|
||||||
|
// the inherited thermal accumulator, spend the charge, build the beam
|
||||||
|
// (muzzle -> target) and submit the Damage record at the owner's target.
|
||||||
|
// The energy/heat algebra needs the electrical charge model (TrackSeekVoltage
|
||||||
|
// / seekVoltage / generator -- the powersub wave), and the beam/damage need
|
||||||
|
// the targeting slot + renderer. This partial performs the DISCHARGE
|
||||||
|
// bookkeeping -- countdown re-arm, charge spent, recoil loaded so the recharge
|
||||||
|
// dial animates -- so the fire state machine runs end-to-end.
|
||||||
//#############################################################################
|
//#############################################################################
|
||||||
//
|
//
|
||||||
void
|
void
|
||||||
Emitter::FireWeapon()
|
Emitter::FireWeapon()
|
||||||
{
|
{
|
||||||
Fail("Emitter::FireWeapon -- emitter.cpp not yet reconstructed");
|
Check(this);
|
||||||
|
|
||||||
|
dischargeTimer = dischargeTime;
|
||||||
|
chargeLevel = 0.0f;
|
||||||
|
recoil = rechargeRate; // full recoil -> dial 0, decays in Loading
|
||||||
|
ComputeOutputVoltage();
|
||||||
|
|
||||||
|
if (getenv("BT_MECH_LOG"))
|
||||||
|
{
|
||||||
|
DEBUG_STREAM << "[fire] '" << GetName()
|
||||||
|
<< "' FIRED (discharge=" << dischargeTime
|
||||||
|
<< "s recharge=" << rechargeRate << "s)" << endl << flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// ResetFiringState (binary @004ba9a8) -- the beam has finished: drop back to
|
||||||
|
// Loading so the recharge cycle begins.
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
void
|
||||||
|
Emitter::ResetFiringState()
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
weaponAlarm.SetLevel(LoadingState);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// EmitterSimulation The beam-weapon per-frame fire state machine (binary
|
||||||
|
// @004baa88). The weapon state is carried in the weapon alarm level:
|
||||||
|
// 0 = Firing, 2 = Loaded (ready), 3 = Loading, 4 = the trigger-during-load
|
||||||
|
// blip. PARTIAL: the leading PoweredSubsystem electrical step and the
|
||||||
|
// destroyed / heat-failure / dead-mech hard gates are deferred with the
|
||||||
|
// power / heat / damage waves; the Loading charge uses the authored
|
||||||
|
// RechargeRate seconds directly (recoil decay -> ComputeOutputVoltage dial)
|
||||||
|
// instead of the TrackSeekVoltage generator integration; the Loaded->Firing
|
||||||
|
// gate honors viewFireEnable (the look-view arm) -- the HasActiveTarget gate
|
||||||
|
// joins it with the targeting wave.
|
||||||
|
//
|
||||||
|
// DEV hook BT_FORCE_FIRE=1: pulses the trigger whenever the weapon is Loaded
|
||||||
|
// (press while Loaded, release otherwise), so every armed weapon auto-fires at
|
||||||
|
// its authored recharge cadence -- the headless fire-cycle verification.
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
void
|
||||||
|
Emitter::EmitterSimulation(Scalar time_slice)
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
{
|
||||||
|
static int forceFire = -1;
|
||||||
|
if (forceFire < 0)
|
||||||
|
{
|
||||||
|
forceFire = (getenv("BT_FORCE_FIRE") != NULL) ? 1 : 0;
|
||||||
|
}
|
||||||
|
if (forceFire)
|
||||||
|
{
|
||||||
|
fireImpulse = (GetWeaponState() == LoadedState) ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logical fireEdge = CheckFireEdge();
|
||||||
|
|
||||||
|
switch (GetWeaponState())
|
||||||
|
{
|
||||||
|
case FiringState:
|
||||||
|
//
|
||||||
|
// Count the beam-on timer down; when it expires, drop to Loading.
|
||||||
|
//
|
||||||
|
dischargeTimer -= time_slice;
|
||||||
|
if (dischargeTimer <= 0.0f)
|
||||||
|
{
|
||||||
|
ResetFiringState();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LoadedState:
|
||||||
|
if (fireEdge)
|
||||||
|
{
|
||||||
|
if (viewFireEnable)
|
||||||
|
{
|
||||||
|
weaponAlarm.SetLevel(FiringState);
|
||||||
|
FireWeapon();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
weaponAlarm.SetLevel(TriggerDuringLoadState);
|
||||||
|
weaponAlarm.SetLevel(LoadedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LoadingState:
|
||||||
|
if (fireEdge)
|
||||||
|
{
|
||||||
|
weaponAlarm.SetLevel(TriggerDuringLoadState);
|
||||||
|
weaponAlarm.SetLevel(LoadingState);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Recharge: decay the recoil over the authored RechargeRate seconds;
|
||||||
|
// the dial (rechargeLevel) rises 0 -> 1 with it.
|
||||||
|
//
|
||||||
|
recoil -= time_slice;
|
||||||
|
if (recoil <= 0.0f)
|
||||||
|
{
|
||||||
|
recoil = 0.0f;
|
||||||
|
}
|
||||||
|
ComputeOutputVoltage();
|
||||||
|
if (recoil == 0.0f)
|
||||||
|
{
|
||||||
|
weaponAlarm.SetLevel(LoadedState);
|
||||||
|
if (getenv("BT_MECH_LOG"))
|
||||||
|
{
|
||||||
|
DEBUG_STREAM << "[fire] '" << GetName()
|
||||||
|
<< "' LOADED" << endl << flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Check_Fpu();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -48,11 +48,12 @@
|
|||||||
static SharedData DefaultData;
|
static SharedData DefaultData;
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Attribute Support
|
// Attribute Support. The energy-weapon family publishes past the MechWeapon
|
||||||
|
// table end (0x1C) -- the binary's emitter IDs run 0x1D+.
|
||||||
//
|
//
|
||||||
public:
|
public:
|
||||||
enum {
|
enum {
|
||||||
ChargeLevelAttributeID = Subsystem::NextAttributeID,
|
ChargeLevelAttributeID = MechWeapon::NextAttributeID, // 0x1D
|
||||||
NextAttributeID
|
NextAttributeID
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,12 +103,31 @@
|
|||||||
void
|
void
|
||||||
FireWeapon();
|
FireWeapon();
|
||||||
|
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// Per-frame simulation (the beam-weapon fire state machine).
|
||||||
|
//
|
||||||
|
public:
|
||||||
|
typedef void
|
||||||
|
(Emitter::*Performance)(Scalar time_slice);
|
||||||
|
void
|
||||||
|
SetPerformance(Performance performance)
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
activePerformance = (Simulation::Performance)performance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
EmitterSimulation(Scalar time_slice);
|
||||||
|
void
|
||||||
|
ResetFiringState();
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Local Data
|
// Local Data
|
||||||
//
|
//
|
||||||
protected:
|
protected:
|
||||||
Scalar chargeLevel;
|
Scalar chargeLevel;
|
||||||
Scalar dischargeTime;
|
Scalar dischargeTime;
|
||||||
|
Scalar dischargeTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -48,11 +48,69 @@ MechWeapon::MessageHandlerSet
|
|||||||
Subsystem::MessageHandlers
|
Subsystem::MessageHandlers
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// Attribute Support -- the 1995 binary table (@0x511890), IDs pinned so the
|
||||||
|
// streamed per-mech control mappings resolve: TriggerState (0x13) is what the
|
||||||
|
// authored fire buttons bind (the controls push then writes the trigger state
|
||||||
|
// straight into fireImpulse each frame). The pads bridge our chain gap
|
||||||
|
// 2..0x11 (parents publish nothing yet) -- REQUIRED, because Build leaves
|
||||||
|
// uncovered gap slots uninitialized; they target rechargeLevel, harmless if a
|
||||||
|
// stray mapping ever binds one (BT411 shipped the same). Pip*/WeaponRange
|
||||||
|
// pads-to-rechargeLevel where the member isn't reconstructed yet are noted.
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
#define MECHWEAP_PAD(n, name) \
|
||||||
|
{ (int)MechWeapon::MechWeaponPadFirstAttributeID + n, name, \
|
||||||
|
(Simulation::AttributePointer)&MechWeapon::rechargeLevel }
|
||||||
|
|
||||||
|
const MechWeapon::IndexEntry
|
||||||
|
MechWeapon::AttributePointers[]=
|
||||||
|
{
|
||||||
|
MECHWEAP_PAD( 0, "MechWeaponPad02"),
|
||||||
|
MECHWEAP_PAD( 1, "MechWeaponPad03"),
|
||||||
|
MECHWEAP_PAD( 2, "MechWeaponPad04"),
|
||||||
|
MECHWEAP_PAD( 3, "MechWeaponPad05"),
|
||||||
|
MECHWEAP_PAD( 4, "MechWeaponPad06"),
|
||||||
|
MECHWEAP_PAD( 5, "MechWeaponPad07"),
|
||||||
|
MECHWEAP_PAD( 6, "MechWeaponPad08"),
|
||||||
|
MECHWEAP_PAD( 7, "MechWeaponPad09"),
|
||||||
|
MECHWEAP_PAD( 8, "MechWeaponPad0A"),
|
||||||
|
MECHWEAP_PAD( 9, "MechWeaponPad0B"),
|
||||||
|
MECHWEAP_PAD(10, "MechWeaponPad0C"),
|
||||||
|
MECHWEAP_PAD(11, "MechWeaponPad0D"),
|
||||||
|
MECHWEAP_PAD(12, "MechWeaponPad0E"),
|
||||||
|
MECHWEAP_PAD(13, "MechWeaponPad0F"),
|
||||||
|
MECHWEAP_PAD(14, "MechWeaponPad10"),
|
||||||
|
MECHWEAP_PAD(15, "MechWeaponPad11"),
|
||||||
|
ATTRIBUTE_ENTRY(MechWeapon, PercentDone, rechargeLevel), // 0x12
|
||||||
|
ATTRIBUTE_ENTRY(MechWeapon, TriggerState, fireImpulse), // 0x13
|
||||||
|
ATTRIBUTE_ENTRY(MechWeapon, DistanceToTarget, rangeToTarget), // 0x14
|
||||||
|
ATTRIBUTE_ENTRY(MechWeapon, TargetWithinRange, targetWithinRange), // 0x15
|
||||||
|
ATTRIBUTE_ENTRY(MechWeapon, WeaponRange, effectiveRange), // 0x16
|
||||||
|
{ (int)MechWeapon::PipPositionAttributeID, "PipPosition", // 0x17 (pip
|
||||||
|
(Simulation::AttributePointer)&MechWeapon::rechargeLevel }, // members
|
||||||
|
{ (int)MechWeapon::PipColorAttributeID, "PipColor", // 0x18 not yet
|
||||||
|
(Simulation::AttributePointer)&MechWeapon::rechargeLevel }, // recon-
|
||||||
|
{ (int)MechWeapon::PipExtendedRangeAttributeID, "PipExtendedRange", // 0x19 structed)
|
||||||
|
(Simulation::AttributePointer)&MechWeapon::rechargeLevel },
|
||||||
|
ATTRIBUTE_ENTRY(MechWeapon, EstimatedReadyTime, estimatedReadyTime),// 0x1A
|
||||||
|
ATTRIBUTE_ENTRY(MechWeapon, RearFiring, rearFiring), // 0x1B
|
||||||
|
ATTRIBUTE_ENTRY(MechWeapon, WeaponState, weaponAlarm) // 0x1C
|
||||||
|
};
|
||||||
|
|
||||||
|
MechWeapon::AttributeIndexSet
|
||||||
|
MechWeapon::AttributeIndex(
|
||||||
|
ELEMENTS(MechWeapon::AttributePointers),
|
||||||
|
MechWeapon::AttributePointers,
|
||||||
|
Subsystem::AttributeIndex
|
||||||
|
);
|
||||||
|
|
||||||
MechWeapon::SharedData
|
MechWeapon::SharedData
|
||||||
MechWeapon::DefaultData(
|
MechWeapon::DefaultData(
|
||||||
MechWeapon::ClassDerivations,
|
MechWeapon::ClassDerivations,
|
||||||
MechWeapon::MessageHandlers,
|
MechWeapon::MessageHandlers,
|
||||||
Subsystem::AttributeIndex,
|
MechWeapon::AttributeIndex,
|
||||||
Subsystem::StateCount
|
Subsystem::StateCount
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -77,6 +135,19 @@ MechWeapon::MechWeapon(
|
|||||||
damageData.damageType = damageType;
|
damageData.damageType = damageType;
|
||||||
damageData.damageAmount = damageAmount;
|
damageData.damageAmount = damageAmount;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Fire-machine state: spawn fully charged and Loaded (ready to fire).
|
||||||
|
//
|
||||||
|
fireImpulse = 0.0f;
|
||||||
|
previousFireImpulse = 0.0f;
|
||||||
|
rechargeLevel = 1.0f;
|
||||||
|
rangeToTarget = 0.0f;
|
||||||
|
effectiveRange = weaponRange;
|
||||||
|
estimatedReadyTime = 0;
|
||||||
|
recoil = 0.0f;
|
||||||
|
weaponAlarm.Initialize(WeaponStateCount);
|
||||||
|
weaponAlarm.SetLevel(LoadedState);
|
||||||
|
|
||||||
//
|
//
|
||||||
// REAR-FIRING resolve (the binary ctor tail @004b99a8): a weapon is
|
// REAR-FIRING resolve (the binary ctor tail @004b99a8): a weapon is
|
||||||
// rear-mounted when its MOUNT SEGMENT's site name carries the 'b' (back)
|
// rear-mounted when its MOUNT SEGMENT's site name carries the 'b' (back)
|
||||||
@@ -129,6 +200,47 @@ Logical
|
|||||||
return True;
|
return True;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// CheckFireEdge -- the rising-edge detector on fireImpulse (binary @004b9608).
|
||||||
|
// TriggerState (attr 0x13) carries the raw ControlsButton INT bit-copied by the
|
||||||
|
// streamed direct mapping (+(button+1) press / -(button+1) release), so the
|
||||||
|
// release value reads as a negative NaN in float terms; the binary's x87
|
||||||
|
// compare effectively treated it as "released". Comparing the BIT PATTERNS as
|
||||||
|
// signed ints is sign-correct for the button ints AND for genuine float values
|
||||||
|
// (0.0f = 0x0, positives > 0, negatives/NaNs < 0), reproducing the binary's
|
||||||
|
// behavior exactly.
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
Logical
|
||||||
|
MechWeapon::CheckFireEdge()
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
int current = *(int *)&fireImpulse;
|
||||||
|
int previous = *(int *)&previousFireImpulse;
|
||||||
|
Logical edge = (current > 0 && previous <= 0) ? True : False;
|
||||||
|
previousFireImpulse = fireImpulse;
|
||||||
|
return edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// ComputeOutputVoltage -- the recharge-dial writer (binary @004b9c9c, vtable
|
||||||
|
// slot the Emitter overrides with its own voltage form):
|
||||||
|
// rechargeLevel = (rechargeRate - recoil) / rechargeRate
|
||||||
|
// The Loading tick decays recoil from rechargeRate toward 0, so the dial rises
|
||||||
|
// 0 -> 1 over the authored RechargeRate seconds.
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
void
|
||||||
|
MechWeapon::ComputeOutputVoltage()
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
rechargeLevel = (rechargeRate - recoil) / rechargeRate;
|
||||||
|
}
|
||||||
|
|
||||||
Logical
|
Logical
|
||||||
MechWeapon::TestInstance() const
|
MechWeapon::TestInstance() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,10 @@
|
|||||||
# include <damage.hpp>
|
# include <damage.hpp>
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
|
# if !defined(ALARM_HPP)
|
||||||
|
# include <alarm.hpp>
|
||||||
|
# endif
|
||||||
|
|
||||||
//##################### Forward Class Declarations #######################
|
//##################### Forward Class Declarations #######################
|
||||||
class Mech;
|
class Mech;
|
||||||
|
|
||||||
@@ -60,6 +64,37 @@
|
|||||||
static SharedData DefaultData;
|
static SharedData DefaultData;
|
||||||
static MessageHandlerSet MessageHandlers;
|
static MessageHandlerSet MessageHandlers;
|
||||||
|
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// Attribute Support. The real IDs are PINNED to the 1995 binary numbering
|
||||||
|
// (table @0x511890, ids 0x12..0x1C): the streamed per-mech control mappings
|
||||||
|
// reference them by NUMBER -- TriggerState (0x13) is the fire-button binding.
|
||||||
|
// Our parent chain publishes nothing past Subsystem::NextAttributeID (2)
|
||||||
|
// yet, so pads bridge the gap 2..0x11 (they shrink to the authentic
|
||||||
|
// 0x0F..0x11 once the mechsub/heat/powersub attribute waves publish --
|
||||||
|
// SENSOR.HPP pins PoweredSubsystem::NextAttributeID at 0x0F). The pads are
|
||||||
|
// LOAD-BEARING: AttributeIndexSet::Build leaves unlisted gap slots as
|
||||||
|
// uninitialized garbage, so every ID up to the max must be covered.
|
||||||
|
//
|
||||||
|
public:
|
||||||
|
enum {
|
||||||
|
MechWeaponPadFirstAttributeID = Subsystem::NextAttributeID, // 2
|
||||||
|
PercentDoneAttributeID = 0x12,
|
||||||
|
TriggerStateAttributeID, // 0x13 -- the streamed fire-button binding
|
||||||
|
DistanceToTargetAttributeID, // 0x14
|
||||||
|
TargetWithinRangeAttributeID, // 0x15
|
||||||
|
WeaponRangeAttributeID, // 0x16
|
||||||
|
PipPositionAttributeID, // 0x17
|
||||||
|
PipColorAttributeID, // 0x18
|
||||||
|
PipExtendedRangeAttributeID, // 0x19
|
||||||
|
EstimatedReadyTimeAttributeID, // 0x1A
|
||||||
|
RearFiringAttributeID, // 0x1B
|
||||||
|
WeaponStateAttributeID, // 0x1C (binary table end)
|
||||||
|
NextAttributeID // 0x1D (the Emitter family starts here)
|
||||||
|
};
|
||||||
|
|
||||||
|
static const IndexEntry AttributePointers[];
|
||||||
|
static AttributeIndexSet AttributeIndex;
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Test Class Support
|
// Test Class Support
|
||||||
//
|
//
|
||||||
@@ -121,6 +156,32 @@
|
|||||||
void
|
void
|
||||||
SetViewFireEnable(Logical enable){ Check(this); viewFireEnable = enable; }
|
SetViewFireEnable(Logical enable){ Check(this); viewFireEnable = enable; }
|
||||||
|
|
||||||
|
//
|
||||||
|
// Fire state machine. The weapon state is carried in the weapon alarm
|
||||||
|
// level: 0 = Firing, 2 = Loaded (ready), 3 = Loading, 4 = the
|
||||||
|
// trigger-during-load blip. CheckFireEdge is the rising-edge detector
|
||||||
|
// on fireImpulse (binary @004b9608): TriggerState carries the raw
|
||||||
|
// ControlsButton INT bit-copied by the streamed direct mapping, so the
|
||||||
|
// compare works on the BIT PATTERNS as signed ints (sign-correct for
|
||||||
|
// the button ints AND genuine floats), reproducing the binary's x87
|
||||||
|
// semantics. ComputeOutputVoltage (@004b9c9c) is the recharge-dial
|
||||||
|
// writer.
|
||||||
|
//
|
||||||
|
enum {
|
||||||
|
FiringState = 0,
|
||||||
|
LoadedState = 2,
|
||||||
|
LoadingState = 3,
|
||||||
|
TriggerDuringLoadState = 4,
|
||||||
|
WeaponStateCount = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned
|
||||||
|
GetWeaponState() { Check(this); return weaponAlarm.GetLevel(); }
|
||||||
|
Logical
|
||||||
|
CheckFireEdge();
|
||||||
|
void
|
||||||
|
ComputeOutputVoltage();
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Local Data
|
// Local Data
|
||||||
//
|
//
|
||||||
@@ -134,6 +195,19 @@
|
|||||||
Damage damageData;
|
Damage damageData;
|
||||||
Logical rearFiring;
|
Logical rearFiring;
|
||||||
Logical viewFireEnable;
|
Logical viewFireEnable;
|
||||||
|
//
|
||||||
|
// Fire-machine state (binary @0x31C/0x320/0x324/0x328/0x330/0x350/
|
||||||
|
// 0x3A4/0x3E8): the trigger sample pair, the recharge dial, targeting
|
||||||
|
// ranges, and the weapon-state alarm.
|
||||||
|
//
|
||||||
|
Scalar fireImpulse;
|
||||||
|
Scalar previousFireImpulse;
|
||||||
|
Scalar rechargeLevel;
|
||||||
|
Scalar rangeToTarget;
|
||||||
|
Scalar effectiveRange;
|
||||||
|
int estimatedReadyTime;
|
||||||
|
Scalar recoil;
|
||||||
|
AlarmIndicator weaponAlarm;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -18,14 +18,63 @@ rechargeRate / weaponRange / damage / heatCostToFire (+ pip fields).
|
|||||||
view change (see MECH.NOTES.md increment 7). Verified: the TEST.EGG mech's
|
view change (see MECH.NOTES.md increment 7). Verified: the TEST.EGG mech's
|
||||||
two rear lasers (ERMLaser_2/3) arm only in LOOK-BACK.
|
two rear lasers (ERMLaser_2/3) arm only in LOOK-BACK.
|
||||||
|
|
||||||
|
## Fire path (2026-07-21, Phase 5.3.8)
|
||||||
|
|
||||||
|
- **The authentic attribute table IS published** (binary @0x511890, IDs pinned):
|
||||||
|
PercentDone 0x12 → rechargeLevel, **TriggerState 0x13 → fireImpulse** (the
|
||||||
|
streamed per-mech fire-button mappings bind this ID — the real trigger
|
||||||
|
wiring), DistanceToTarget 0x14, TargetWithinRange 0x15, WeaponRange 0x16 →
|
||||||
|
effectiveRange, EstimatedReadyTime 0x1A, RearFiring 0x1B, WeaponState 0x1C →
|
||||||
|
weaponAlarm. Pads bridge our chain gap 2..0x11 (parents publish nothing yet;
|
||||||
|
they shrink to the authentic 0x0F..0x11 when the mechsub/heat/powersub
|
||||||
|
attribute waves land — SENSOR.HPP pins PoweredSubsystem::NextAttributeID at
|
||||||
|
0x0F). **Pads are LOAD-BEARING**: AttributeIndexSet::Build leaves uncovered
|
||||||
|
gap slots as UNINITIALIZED GARBAGE (`new IndexEntry[entryCount]`, only
|
||||||
|
inherited + listed entries written), so every ID up to the max must be
|
||||||
|
covered or Find() on a gap ID returns a wild pointer. Pip* (0x17-0x19) are
|
||||||
|
pads → rechargeLevel until the pip members are reconstructed.
|
||||||
|
- **Fire state machine members**: fireImpulse/previousFireImpulse (the trigger
|
||||||
|
sample pair), rechargeLevel, rangeToTarget, effectiveRange,
|
||||||
|
estimatedReadyTime, recoil, weaponAlarm (the state carrier: 0 Firing /
|
||||||
|
2 Loaded / 3 Loading / 4 trigger-during-load). Spawn = charged + Loaded.
|
||||||
|
- **CheckFireEdge** (@004b9608): rising-edge on fireImpulse comparing BIT
|
||||||
|
PATTERNS as signed ints — TriggerState carries raw ControlsButton ints
|
||||||
|
(+(button+1) press / −(button+1) release); the release bit-pattern is a
|
||||||
|
negative NaN as float, and an IEEE float compare would latch the detector
|
||||||
|
shut after the first release (BT411 task #8). Int compare reproduces the
|
||||||
|
binary's x87 semantics exactly.
|
||||||
|
- **ComputeOutputVoltage** (@004b9c9c): `rechargeLevel = (rechargeRate −
|
||||||
|
recoil) / rechargeRate` — the recharge dial.
|
||||||
|
- **Emitter::EmitterSimulation** (PARTIAL, @004baa88) installed on every
|
||||||
|
non-replicant energy weapon: Firing → discharge countdown →
|
||||||
|
ResetFiringState(Loading); Loaded + trigger edge → viewFireEnable gate →
|
||||||
|
FireWeapon; Loading → recoil decays over the authored RechargeRate seconds →
|
||||||
|
Loaded. Deferred within it: the leading PoweredSubsystemSimulation electrical
|
||||||
|
step, the destroyed/heat-failure/dead-mech hard gates, the TrackSeekVoltage
|
||||||
|
generator charge integration (the partial uses the authored seconds
|
||||||
|
directly), and the HasActiveTarget gate (targeting wave).
|
||||||
|
- **Emitter::FireWeapon** (PARTIAL, @004bace8): discharge bookkeeping (beam
|
||||||
|
countdown re-arm, charge spent, recoil loaded). Deferred: the 0.5·V²·EC
|
||||||
|
energy/damage/heat closed forms (need the electrical model), the heat dump
|
||||||
|
(heat wave), the beam build + Damage submission (targeting + renderer).
|
||||||
|
- Emitter's index re-chained to MechWeapon's (ChargeLevel at the authentic
|
||||||
|
0x1D past the table end); Emitter::DefaultData now binds
|
||||||
|
MechWeapon::MessageHandlers.
|
||||||
|
- DEV hook `BT_FORCE_FIRE=1`: pulses the trigger whenever a weapon is Loaded →
|
||||||
|
every armed weapon auto-fires at its authored cadence.
|
||||||
|
|
||||||
|
**VERIFIED headlessly**: forward view — PPC_1/2 + ERMLaser_1 cycle
|
||||||
|
FIRED→LOADED→FIRED at the authored 0.6 s discharge / 1 s recharge, rear lasers
|
||||||
|
silent; `BT_FORCE_LOOK=3` (look-behind) — ONLY ERMLaser_2/3 fire. The full
|
||||||
|
view-fire × fire-FSM interlock holds in both directions. Zero Fail. (SRM6s are
|
||||||
|
MissileLaunchers — the ballistic ProjectileWeaponSimulation is a later
|
||||||
|
increment.)
|
||||||
|
|
||||||
## Still staged
|
## Still staged
|
||||||
|
|
||||||
- `FireWeapon` (abstract here; the concrete fire paths in Emitter/PPC/Gauss/
|
- The ballistic fire path (ProjectileWeaponSimulation @004bbd04, missile
|
||||||
MissileLauncher are the combat wave), `SendDamage` (damage/networking),
|
launch/seeker), `SendDamage` (damage/networking), `CreateStreamedSubsystem`
|
||||||
`CreateStreamedSubsystem` (tool-side model build).
|
(tool-side model build).
|
||||||
- The 1995 MechWeapon attribute table (RearFiring @0x1B, EstimatedReadyTime,
|
|
||||||
...) is not yet published — our DefaultData still uses the base
|
|
||||||
Subsystem::AttributeIndex. Comes with the weapon-gauge/console wave.
|
|
||||||
- ~~`MechWeapon::MessageHandlers` declared but not defined~~ — **FIXED
|
- ~~`MechWeapon::MessageHandlers` declared but not defined~~ — **FIXED
|
||||||
2026-07-21**: it is now a real defined set (`(0, NULL,
|
2026-07-21**: it is now a real defined set (`(0, NULL,
|
||||||
Subsystem::MessageHandlers)` — no own handlers yet, inherits the chain), and
|
Subsystem::MessageHandlers)` — no own handlers yet, inherits the chain), and
|
||||||
|
|||||||
Reference in New Issue
Block a user