#118: the FLASHING eject invite fires -- generator OUT state + pilot keypad

The last hop lands: a destroyed Generator now reaches its OUT display state
(stateAlarm 4 -- enum renamed from the placeholder "GeneratorRecovered";
[T3 inferred link, both endpoints byte-verified: the eject evaluator's
state==4 dead clause @0049fa1c and the authored alarm streams' conditions
4/5 -> gotoEngineering 0x80 + engEject 0x85]). Wired from the one crit
chokepoint (MechSubsystem::ForceCriticalFailure -> BTGeneratorMarkOut
bridge). Verified live: gen kill -> the quad-select columns FLASH
(RIO 0x37 fast-flash) -- the pod's guided "punch out now" trail.

Plus the authentic panel eject input: CONTROLS.MAP binds the desktop
NUMPAD to the pilot keypad (keypad pilot 0-9) -- while armed, any pilot-
keypad key fires the eject (the binary's only armed binding); un-armed
they feed program entry.

The full 1995 eject experience is now live: alarm -> flashing invite trail
-> lit Panic button -> press (Panic / numpad / Backspace) -> punch-out ->
death without honor -> respawn.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-03 20:11:05 -05:00
co-authored by Claude Fable 5
parent 417008a6d4
commit aa54655980
4 changed files with 55 additions and 4 deletions
+16
View File
@@ -176,3 +176,19 @@ key G action Flush
# healthy mech refuses the button, so a stray press is a no-op.
key Back action Eject
pad LeftThumb action Eject
# PILOT KEYPAD (the pod's MFD program keypad, KeyboardPilot unit). While
# PANIC mode is armed, ANY pilot-keypad key fires the eject (the binary's
# keyboardGroup[KeyboardPilot].Add(0x200000, mech, 0x19) -- its ONLY armed
# eject binding); un-armed, the keys feed program entry. Desktop numpad =
# that keypad.
key NumPad0 keypad pilot 0
key NumPad1 keypad pilot 1
key NumPad2 keypad pilot 2
key NumPad3 keypad pilot 3
key NumPad4 keypad pilot 4
key NumPad5 keypad pilot 5
key NumPad6 keypad pilot 6
key NumPad7 keypad pilot 7
key NumPad8 keypad pilot 8
key NumPad9 keypad pilot 9
+7
View File
@@ -287,6 +287,13 @@ void MechSubsystem::ForceCriticalFailure()
{
((DamageZone *)damageZone)->SetDamageZoneState(1); // zone+0x10
}
// Generator: also reach the OUT display state (stateAlarm 4) so the
// authored gotoEngineering + engEject FLASH invite fires (alarm stream
// conditions 4/5). [T3 inferred link -- see Generator::MarkGeneratorOut.]
{
extern void BTGeneratorMarkOut(::Subsystem *sub); // powersub.cpp
BTGeneratorMarkOut(this);
}
}
// issue #22 (PORT): the fresh-mech respawn repair -- the exact inverse of the
+14 -3
View File
@@ -1305,10 +1305,10 @@ void
{
// The THERMAL BREAKER: output collapses to 0 until the generator cools
// past NormalHeat and re-spins (task #11 diag below shows the duty cycle).
if (stateAlarm.GetLevel() != GeneratorRecovered && getenv("BT_HEAT_LOG"))
if (stateAlarm.GetLevel() != GeneratorOut && getenv("BT_HEAT_LOG"))
DEBUG_STREAM << "[gen] " << GetName() << " TRIPPED (T="
<< currentTemperature << ")" << std::endl;
stateAlarm.SetLevel(GeneratorRecovered); // SetLevel 4
stateAlarm.SetLevel(GeneratorOut); // SetLevel 4
}
switch (stateAlarm.GetLevel()) // this[0x84] @0x210
@@ -1337,7 +1337,7 @@ void
}
break;
case GeneratorRecovered: // 4
case GeneratorOut: // 4
outputVoltage = 0.0f;
if (heatAlarm.GetLevel() == HeatSink::NormalHeat) // this+0x184 == 0
{
@@ -1779,6 +1779,17 @@ int BTSubsystemIsGenerator(::Subsystem *sub)
return (sub != 0 && sub->IsDerivedFrom(*Generator::GetClassDerivations())) ? 1 : 0;
}
// Destruction -> OUT display state (see Generator::MarkGeneratorOut, the [T3
// inferred link]): a destroyed generator must reach stateAlarm 4 or the
// authored gotoEngineering/engEject FLASH invite (alarm conditions 4/5) never
// fires and the eject evaluator's state clause never sees it. Called from
// MechSubsystem::ForceCriticalFailure (the one crit-death chokepoint).
void BTGeneratorMarkOut(::Subsystem *sub)
{
if (sub != 0 && sub->IsDerivedFrom(*Generator::GetClassDerivations()))
((Generator *)sub)->MarkGeneratorOut();
}
//===========================================================================//
// BTGeneratorCountsForEject -- complete-type bridge for the EJECT-permission
// evaluator (Mech::EvaluateEjectPermission @0049fa1c). Binary clause: classID
+18 -1
View File
@@ -379,7 +379,12 @@ class Generator;
GeneratorIdle = 1, // off / no output
GeneratorReady = 2, // producing RatedVoltage
GeneratorShorted = 3, // shorted; shortTimer -> shortRecoveryTime
GeneratorRecovered = 4 // TODO: confirm 4th/5th level names
GeneratorOut = 4 // generator DEAD/OUT: the eject evaluator counts
// stateAlarm==4 as not-live [T1 @0049fa1c], and the
// authored gauge-alarm streams flash gotoEngineering +
// engEject on conditions 4/5 [T1 content dump
// 2026-08-03] -- the "punch out now" invite.
// (Old name "GeneratorRecovered" was a placeholder.)
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -462,6 +467,18 @@ class Generator;
// Source-state queries used by PoweredSubsystem / PowerWatcher.
int
GeneratorStateOf() { return stateAlarm.GetLevel(); } // source+0x210
// DESTRUCTION -> the OUT display state (stateAlarm 4). [T3 inferred
// link, 2026-08-03]: both endpoints are byte-verified -- the eject
// evaluator treats stateAlarm==4 as generator-dead (@0049fa1c) and the
// authored alarm streams flash the gotoEngineering + engEject invite on
// conditions 4/5 -- but the binary's destroyed->SetLevel(4) call site
// sits in unexported code (no FUN_0041bbd8(+0x1fc,4) in the flat
// export). Called from MechSubsystem::ForceCriticalFailure via the
// BTGeneratorMarkOut bridge; replace with the exact site when the
// MechSubsystem::TakeDamage / OnAlarmChanged cluster is reconstructed.
void
MarkGeneratorOut() { stateAlarm.SetLevel(GeneratorOut); }
Scalar
MeasuredVoltage() { return outputVoltage; } // source+0x1dc
Scalar