BT410 Phase 5.3.22: the cockpit weapon-button column -- generator panel LIVE

The full per-receiver message chain, contiguous ids 3..0xb: PoweredSubsystem
4-8 (SelectGeneratorA-D + ToggleGeneratorMode, binary table @0x50F4EC)
chained on HeatSink's ToggleCooling(3); MechWeapon 9/10 (config buttons,
staged latch bodies); Emitter 0xb ToggleSeekVoltage (@0x511DB8).

SelectGenerator = release the old tap -> roster walk by generatorNumber ->
AttachToVoltageSource -> Connected; mode toggle cycles Manual->Auto->
(detach)->Manual; the seek dial steps the authored voltage ladder with wrap
(a step up re-enters Loading, charge persists).  All novice-locked.
Generator::UntapVoltageSource + PoweredSubsystem::DetachFromVoltageSource
added.  Dev harness: BT_PRESS_GEN=1..4 / BT_PRESS_SEEK.

Verified: PPC_1 retaps GeneratorB (tapped); seek index 2->3, target 9900V
(authored fraction x rated); novice presses INERT; missile fight 14/14 and
smoke green, zero faults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-24 14:36:32 -05:00
co-authored by Claude Fable 5
parent 1a01aa6f43
commit bfff7fe874
8 changed files with 1000 additions and 560 deletions
File diff suppressed because it is too large Load Diff
+15
View File
@@ -54,6 +54,21 @@
public:
static Derivation ClassDerivations;
static SharedData DefaultData;
static const HandlerEntry MessageHandlerEntries[];
static MessageHandlerSet MessageHandlers;
//
// The energy-weapon seek-voltage button (binary table @0x511DB8,
// id 0xb, chained after MechWeapon's 9/10; the ammo-weapon branch
// binds 0xb to EjectAmmo in ITS OWN table instead).
//
enum {
ToggleSeekVoltageMessageID = MechWeapon::NextMessageID, // 0xb
NextMessageID
};
void
ToggleSeekVoltageMessageHandler(ReceiverDataMessageOf<int> *message);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute Support. The energy-weapon family publishes past the MechWeapon
+45
View File
@@ -951,6 +951,51 @@ void
}
}
}
//
// BT_PRESS_GEN=1..4: SelectGenerator<N> at the first Emitter
// (the generator panel re-tap); BT_PRESS_SEEK: ToggleSeekVoltage
// at the first Emitter (the seek dial).
//
{
const char *press_gen = getenv("BT_PRESS_GEN");
const char *press_seek = getenv("BT_PRESS_SEEK");
if (press_gen != NULL || press_seek != NULL)
{
for (int s = 2; s < subsystemCount; ++s)
{
Subsystem *sub = subsystemArray[s];
if (sub != NULL
&& sub->IsDerivedFrom(Emitter::ClassDerivations))
{
if (press_gen != NULL)
{
int n = atoi(press_gen);
if (n >= 1 && n <= 4)
{
ReceiverDataMessageOf<int> press(
PoweredSubsystem::
SelectGeneratorAMessageID
+ (n - 1),
sizeof(ReceiverDataMessageOf<int>),
1
);
sub->Dispatch(&press);
}
}
if (press_seek != NULL)
{
ReceiverDataMessageOf<int> press(
Emitter::ToggleSeekVoltageMessageID,
sizeof(ReceiverDataMessageOf<int>),
1
);
sub->Dispatch(&press);
}
break;
}
}
}
}
}
}
+51 -9
View File
@@ -38,19 +38,26 @@ Derivation
//
//#############################################################################
// Message handlers. MechWeapon publishes no handlers of its own yet (the
// weapon fire/damage handler wave), but the set MUST be a real defined object:
// the surviving CODE PPC.CPP binds the inherited name (`PPC::MessageHandlers`)
// into PPC::DefaultData, and a declared-but-undefined static links as a
// zero-filled common block -- the same silent-NULL trap as the
// Emitter::AttributeIndex crash (see MECHWEAP.NOTES.md).
// Message handlers (binary ids 9/10 @004b9550/@004b95b8) -- the weapon-group
// config buttons, chained onto the PoweredSubsystem generator panel (4-8),
// which chains HeatSink (3): a weapon's dispatch reaches the whole cockpit
// column. (The set MUST be a real defined object: the surviving CODE
// PPC.CPP binds the inherited name into PPC::DefaultData, and a
// declared-but-undefined static links as a zero-filled common block.)
//#############################################################################
//
const MechWeapon::HandlerEntry
MechWeapon::MessageHandlerEntries[] =
{
MESSAGE_ENTRY(MechWeapon, ConfigureMappables), // id 9 @004b9550
MESSAGE_ENTRY(MechWeapon, ChooseButton) // id 10 @004b95b8
};
MechWeapon::MessageHandlerSet
MechWeapon::MessageHandlers(
0,
NULL,
Subsystem::MessageHandlers
ELEMENTS(MechWeapon::MessageHandlerEntries),
MechWeapon::MessageHandlerEntries,
PoweredSubsystem::MessageHandlers
);
//
@@ -196,6 +203,41 @@ MechWeapon::MechWeapon(
Check_Fpu();
}
//
//#############################################################################
// The weapon-group config buttons (ids 9/10). STAGED: the binary bodies
// (@004b9550/@004b95b8) drive the group-assign flow with the HUD config
// page; until that page exists the press is latched (configureActivePress)
// and logged -- the dispatch path itself is the live, authentic part.
//#############################################################################
//
void
MechWeapon::ConfigureMappablesMessageHandler(
ReceiverDataMessageOf<int> *message)
{
Check(this);
configureActivePress = message->dataContents;
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[btn] '" << GetName()
<< "' ConfigureMappables press=" << message->dataContents
<< " (staged latch)" << endl << flush;
}
}
void
MechWeapon::ChooseButtonMessageHandler(
ReceiverDataMessageOf<int> *message)
{
Check(this);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[btn] '" << GetName()
<< "' ChooseButton press=" << message->dataContents
<< " (staged)" << endl << flush;
}
}
MechWeapon::~MechWeapon()
{
}
+16
View File
@@ -68,8 +68,24 @@
public:
static Derivation ClassDerivations;
static SharedData DefaultData;
static const HandlerEntry MessageHandlerEntries[];
static MessageHandlerSet MessageHandlers;
//
// The weapon-group config buttons (binary ids 9/10, chained after
// the PoweredSubsystem generator panel 4-8).
//
enum {
ConfigureMappablesMessageID = PoweredSubsystem::NextMessageID, // 9
ChooseButtonMessageID, // 10
NextMessageID // 11
};
void
ConfigureMappablesMessageHandler(ReceiverDataMessageOf<int> *message);
void
ChooseButtonMessageHandler(ReceiverDataMessageOf<int> *message);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute Support. The real IDs are PINNED to the 1995 binary numbering
// (table @0x511890, ids 0x12..0x1C): the streamed per-mech control mappings
@@ -160,3 +160,22 @@ with burstCount=missileCount (burstCount feeds only gyro bounce + splash
falloff — never the zone formula). MISLANCH now dispatches a single
SendDamage per salvo (verified 1:1 FIRED:[dmg]). The cluster SPLASH component
arrives with the Missile entity wave (proximity-fuse detonation).
## 5.3.22 — the cockpit weapon-button column
The full per-receiver message chain is registered (contiguous, no pads):
HeatSink 3 ToggleCooling → PoweredSubsystem 4-8 (SelectGeneratorA-D @004b099c+,
ToggleGeneratorMode @004b0abc — binary table @0x50F4EC) → MechWeapon 9/10
(ConfigureMappables/ChooseButton @004b9550/@004b95b8 — STAGED press-latch
bodies, the group-config flow needs the HUD page) → Emitter 0xb
ToggleSeekVoltage (@004ba478, table @0x511DB8; the ammo branch's 0xb
EjectAmmo is deferred — its absence is safe, no gap slot below a registered
max). SelectGenerator = detach old tap → FindGeneratorByNumber roster walk →
AttachToVoltageSource → modeAlarm Connected; ToggleGeneratorMode cycles
Manual→AutoConnect→(detach)→Manual (the auto-hunt itself = a
PoweredSubsystemSimulation brick). ToggleSeekVoltage steps the seek ladder
with wrap; a step UP re-enters Loading (charge persists and climbs).
All novice-locked. Dev: BT_PRESS_GEN=1..4 / BT_PRESS_SEEK (~6s dispatch at
the first Emitter). Verified: PPC_1 → GeneratorB (tapped); seek 2→3 target
9900V; novice presses inert; fight/smoke green. Static-init order is safe
under the authentic .MAK lib order (heat→powersub→mechweap→emitter).
+191 -1
View File
@@ -30,10 +30,35 @@ Derivation
"PoweredSubsystem"
);
//
//#############################################################################
// The cockpit generator buttons (binary handler table @0x50F4EC, ids 4-8),
// chained onto the HeatSink set (id 3 ToggleCooling) so every powered
// subsystem's dispatch reaches the Eng-page Coolant button too. Static-init
// order is safe under the authentic .MAK lib order (heat precedes powersub).
//#############################################################################
//
const PoweredSubsystem::HandlerEntry
PoweredSubsystem::MessageHandlerEntries[] =
{
MESSAGE_ENTRY(PoweredSubsystem, SelectGeneratorA), // id 4 @004b099c
MESSAGE_ENTRY(PoweredSubsystem, SelectGeneratorB), // id 5 @004b09e4
MESSAGE_ENTRY(PoweredSubsystem, SelectGeneratorC), // id 6 @004b0a2c
MESSAGE_ENTRY(PoweredSubsystem, SelectGeneratorD), // id 7 @004b0a74
MESSAGE_ENTRY(PoweredSubsystem, ToggleGeneratorMode) // id 8 @004b0abc
};
PoweredSubsystem::MessageHandlerSet
PoweredSubsystem::MessageHandlers(
ELEMENTS(PoweredSubsystem::MessageHandlerEntries),
PoweredSubsystem::MessageHandlerEntries,
HeatSink::MessageHandlers
);
PoweredSubsystem::SharedData
PoweredSubsystem::DefaultData(
PoweredSubsystem::ClassDerivations,
Subsystem::MessageHandlers,
PoweredSubsystem::MessageHandlers,
Subsystem::AttributeIndex,
Subsystem::StateCount
);
@@ -179,6 +204,171 @@ int
return 0;
}
//
//#############################################################################
// DetachFromVoltageSource (binary @004b0e30): release the tap on the current
// source and clear the connection.
//#############################################################################
//
void
PoweredSubsystem::DetachFromVoltageSource()
{
Check(this);
Generator *source = (Generator *)voltageSource.Resolve();
if (source != NULL)
{
source->UntapVoltageSource();
voltageSource.Clear();
}
}
//
//#############################################################################
// FindGeneratorByNumber (binary @004b0b18): walk the owner's roster for the
// Generator whose authored generatorNumber matches (1=A .. 4=D).
//#############################################################################
//
Subsystem*
PoweredSubsystem::FindGeneratorByNumber(int generator_number)
{
Check(this);
for (int slot = 2; slot < owner->GetSubsystemCount(); ++slot)
{
Subsystem *sub = owner->GetSubsystem(slot);
if (sub != NULL
&& sub->IsDerivedFrom(Generator::ClassDerivations)
&& ((Generator *)sub)->GetGeneratorNumber() == generator_number)
{
return sub;
}
}
return NULL;
}
//
//#############################################################################
// SelectGenerator -- the manual re-tap: release the current source, tap
// generator N, drop the connect mode back to Connected (a manual selection
// ends any auto-hunt). The binary dereferences the find unguarded (authored
// mechs always carry A-D); we skip loud instead.
//#############################################################################
//
void
PoweredSubsystem::SelectGenerator(int generator_number)
{
Check(this);
Subsystem *generator = FindGeneratorByNumber(generator_number);
if (generator == NULL)
{
if (getenv("BT_MECH_LOG") || getenv("BT_POWER_LOG"))
{
DEBUG_STREAM << "[gensel] '" << GetName()
<< "' -> generator " << generator_number
<< " NOT FOUND" << endl << flush;
}
return;
}
DetachFromVoltageSource();
int tap = AttachToVoltageSource(generator);
modeAlarm.SetLevel(Connected);
if (getenv("BT_MECH_LOG") || getenv("BT_POWER_LOG"))
{
DEBUG_STREAM << "[gensel] '" << GetName()
<< "' -> '" << generator->GetName() << "'"
<< ((tap >= 0) ? " (tapped)" : " (REFUSED: no spare tap)")
<< endl << flush;
}
}
//
//#############################################################################
// The cockpit button handlers (ids 4-8). Press-only (dataContents > 0),
// novice-locked -- a novice cockpit's generator panel is inert.
//#############################################################################
//
void
PoweredSubsystem::SelectGeneratorAMessageHandler(
ReceiverDataMessageOf<int> *message)
{
Check(this);
if (!NoviceLockout() && message->dataContents > 0)
{
SelectGenerator(1);
}
}
void
PoweredSubsystem::SelectGeneratorBMessageHandler(
ReceiverDataMessageOf<int> *message)
{
Check(this);
if (!NoviceLockout() && message->dataContents > 0)
{
SelectGenerator(2);
}
}
void
PoweredSubsystem::SelectGeneratorCMessageHandler(
ReceiverDataMessageOf<int> *message)
{
Check(this);
if (!NoviceLockout() && message->dataContents > 0)
{
SelectGenerator(3);
}
}
void
PoweredSubsystem::SelectGeneratorDMessageHandler(
ReceiverDataMessageOf<int> *message)
{
Check(this);
if (!NoviceLockout() && message->dataContents > 0)
{
SelectGenerator(4);
}
}
//
//#############################################################################
// ToggleGeneratorMode (id 8, binary @004b0abc): cycle Manual -> AutoConnect
// -> (detach +) Manual. The auto-hunt itself (a shorted/dead source makes
// the subsystem walk for a live generator) lives in
// PoweredSubsystemSimulation -- a later brick.
//#############################################################################
//
void
PoweredSubsystem::ToggleGeneratorModeMessageHandler(
ReceiverDataMessageOf<int> *message)
{
Check(this);
if (NoviceLockout() || message->dataContents <= 0)
{
return;
}
if ((unsigned)modeAlarm.GetLevel() < (unsigned)AutoConnect)
{
modeAlarm.SetLevel(AutoConnect);
}
else if (modeAlarm.GetLevel() == AutoConnect)
{
DetachFromVoltageSource();
modeAlarm.SetLevel(ManualConnect);
}
if (getenv("BT_MECH_LOG") || getenv("BT_POWER_LOG"))
{
DEBUG_STREAM << "[gensel] '" << GetName()
<< "' mode -> " << modeAlarm.GetLevel() << endl << flush;
}
}
//
//#############################################################################
// ChargeTimeScale -- the heat/firepower feedback (binary @004b0d50):
+49
View File
@@ -86,6 +86,46 @@
// Power support
//
public:
//
// The cockpit generator-select buttons (binary handler table
// @0x50F4EC, ids 4-8, chained onto the HeatSink set so id 3
// ToggleCooling stays reachable through every powered subsystem).
//
enum {
SelectGeneratorAMessageID = HeatSink::NextMessageID, // 4
SelectGeneratorBMessageID, // 5
SelectGeneratorCMessageID, // 6
SelectGeneratorDMessageID, // 7
ToggleGeneratorModeMessageID, // 8
NextMessageID // 9
};
static const HandlerEntry MessageHandlerEntries[];
static MessageHandlerSet MessageHandlers;
void
SelectGeneratorAMessageHandler(ReceiverDataMessageOf<int> *message);
void
SelectGeneratorBMessageHandler(ReceiverDataMessageOf<int> *message);
void
SelectGeneratorCMessageHandler(ReceiverDataMessageOf<int> *message);
void
SelectGeneratorDMessageHandler(ReceiverDataMessageOf<int> *message);
void
ToggleGeneratorModeMessageHandler(ReceiverDataMessageOf<int> *message);
//
// The manual generator re-tap (binary @004b0b18/@004b0dd8 family):
// find generator N in the owner's roster, release the current tap,
// tap the new source, drop the connect mode to Connected.
//
void
SelectGenerator(int generator_number);
Subsystem*
FindGeneratorByNumber(int generator_number);
void
DetachFromVoltageSource();
Subsystem*
ResolveVoltageSource() { return voltageSource.Resolve(); }
int
@@ -309,6 +349,15 @@
++currentTapCount;
return 0;
}
void
UntapVoltageSource()
{
Check(this);
if (currentTapCount > 0)
{
--currentTapCount;
}
}
typedef void
(Generator::*Performance)(Scalar time_slice);