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):
|
||||
// the HUD weapon-charge gauge binds to it, and -- load-bearing -- GaussRifle's
|
||||
// AttributeIndex chains THIS index (GAUSS.CPP), and PPC::DefaultData binds it
|
||||
// via the inherited PPC::AttributeIndex. It MUST be a real defined index
|
||||
// chained to Subsystem::AttributeIndex, not the bare base (a declared-but-
|
||||
// Attribute Support. Emitter publishes the beam charge level (ChargeLevel, at
|
||||
// the authentic 0x1D past the MechWeapon table end): the HUD weapon-charge
|
||||
// gauge binds it, and -- load-bearing -- GaussRifle's AttributeIndex chains
|
||||
// THIS index (GAUSS.CPP), and PPC::DefaultData binds it via the inherited
|
||||
// PPC::AttributeIndex. It MUST be a real defined index (a declared-but-
|
||||
// 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
|
||||
@@ -46,13 +48,13 @@ Emitter::AttributeIndexSet
|
||||
Emitter::AttributeIndex(
|
||||
ELEMENTS(Emitter::AttributePointers),
|
||||
Emitter::AttributePointers,
|
||||
Subsystem::AttributeIndex
|
||||
MechWeapon::AttributeIndex
|
||||
);
|
||||
|
||||
Emitter::SharedData
|
||||
Emitter::DefaultData(
|
||||
Emitter::ClassDerivations,
|
||||
Subsystem::MessageHandlers,
|
||||
MechWeapon::MessageHandlers,
|
||||
Emitter::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
@@ -70,6 +72,16 @@ Emitter::Emitter(
|
||||
|
||||
chargeLevel = 0.0f;
|
||||
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();
|
||||
}
|
||||
@@ -92,14 +104,148 @@ Logical
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// FireWeapon The energy-beam discharge. Not yet reconstructed (the fire path
|
||||
// is exercised in combat, past the current ctor frontier).
|
||||
// FireWeapon The energy-beam discharge (PARTIAL -- binary @004bace8). The
|
||||
// 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
|
||||
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;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Attribute Support
|
||||
// Attribute Support. The energy-weapon family publishes past the MechWeapon
|
||||
// table end (0x1C) -- the binary's emitter IDs run 0x1D+.
|
||||
//
|
||||
public:
|
||||
enum {
|
||||
ChargeLevelAttributeID = Subsystem::NextAttributeID,
|
||||
ChargeLevelAttributeID = MechWeapon::NextAttributeID, // 0x1D
|
||||
NextAttributeID
|
||||
};
|
||||
|
||||
@@ -102,12 +103,31 @@
|
||||
void
|
||||
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
|
||||
//
|
||||
protected:
|
||||
Scalar chargeLevel;
|
||||
Scalar dischargeTime;
|
||||
Scalar dischargeTimer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -48,11 +48,69 @@ MechWeapon::MessageHandlerSet
|
||||
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::DefaultData(
|
||||
MechWeapon::ClassDerivations,
|
||||
MechWeapon::MessageHandlers,
|
||||
Subsystem::AttributeIndex,
|
||||
MechWeapon::AttributeIndex,
|
||||
Subsystem::StateCount
|
||||
);
|
||||
|
||||
@@ -77,6 +135,19 @@ MechWeapon::MechWeapon(
|
||||
damageData.damageType = damageType;
|
||||
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-mounted when its MOUNT SEGMENT's site name carries the 'b' (back)
|
||||
@@ -129,6 +200,47 @@ Logical
|
||||
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
|
||||
MechWeapon::TestInstance() const
|
||||
{
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
# include <damage.hpp>
|
||||
# endif
|
||||
|
||||
# if !defined(ALARM_HPP)
|
||||
# include <alarm.hpp>
|
||||
# endif
|
||||
|
||||
//##################### Forward Class Declarations #######################
|
||||
class Mech;
|
||||
|
||||
@@ -60,6 +64,37 @@
|
||||
static SharedData DefaultData;
|
||||
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
|
||||
//
|
||||
@@ -121,6 +156,32 @@
|
||||
void
|
||||
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
|
||||
//
|
||||
@@ -134,6 +195,19 @@
|
||||
Damage damageData;
|
||||
Logical rearFiring;
|
||||
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
|
||||
|
||||
@@ -18,14 +18,63 @@ rechargeRate / weaponRange / damage / heatCostToFire (+ pip fields).
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
- `FireWeapon` (abstract here; the concrete fire paths in Emitter/PPC/Gauss/
|
||||
MissileLauncher are the combat wave), `SendDamage` (damage/networking),
|
||||
`CreateStreamedSubsystem` (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.
|
||||
- The ballistic fire path (ProjectileWeaponSimulation @004bbd04, missile
|
||||
launch/seeker), `SendDamage` (damage/networking), `CreateStreamedSubsystem`
|
||||
(tool-side model build).
|
||||
- ~~`MechWeapon::MessageHandlers` declared but not defined~~ — **FIXED
|
||||
2026-07-21**: it is now a real defined set (`(0, NULL,
|
||||
Subsystem::MessageHandlers)` — no own handlers yet, inherits the chain), and
|
||||
|
||||
Reference in New Issue
Block a user