Experience levels WIRED: the egg field drives the simulation tier (Gitea #2)

BTPlayer's master ctor now seeds the flag block from
btMission->ExperienceLevel() + AdvancedDamageOn() (accessors from the
SURVIVED 1995 BTMSSN.HPP [T0]) instead of the NULL-stubbed scenario
role -- the egg experience knob works for the first time.  Both switch
slot errors fixed vs the binary @4c0bc8; phantom btMission duplicate
member removed (+0x1f8 IS Player::playerMission).

SEVEN gate stubs unified onto the real flags (two found empirically):
HeatModelActive, OwnerAdvancedDamage, LiveFireEnabled,
ControlsAllowLights, powersub @4b0efc, emitter's file-local FUN_004ad7d4
+ FUN_004ac9c8 stubs.  FUN_004ac9c8 family swept (incl. a live raw-offset
databinding trap in MechSubsystem::IsDamaged).  Flag block renamed to its
true semantics (simLive/heatModelOn/...), scoring-era names kept as
comments.  Includes the #5 [aud-tail] diag in emitter.cpp.

ALL 19 shipped eggs say experience=expert -> default behavior UNCHANGED
(per-tier verified: novice/standard = no heat/jams, authentic; veteran/
expert = today's exact behavior).  Real delta: egg advancedDamage=1 now
reaches player+0x264/0x268 (was hardwired 0).  Follow-up flagged: the
glass FE defaults empty experience to veteran (one tier below shipped).

Awaiting human verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-20 14:51:35 -05:00
co-authored by Claude Fable 5
parent a0cec48e3f
commit d7158f1f82
14 changed files with 294 additions and 130 deletions
+24 -13
View File
@@ -449,20 +449,22 @@ void
// @004b0efc -- pass the message to the HeatSink base; if the part is alive,
// the source short flag is set and message 4 arrives, force a short-recovery.
//
extern int BTPlayerExperienceSimLive(void *owner_mech); // btplayer.cpp (player+0x25c; issue #2)
extern int BTPlayerRoleLocksAdvanced(void *owner_mech); // btplayer.cpp (FUN_004ac9c8: player+0x274 == 0)
Logical
PoweredSubsystem::HandleMessage(int message)
{
HeatSink::HandleMessage(message); // FUN_004ad748
// Raw @004b0efc reads *(int*)(*(owner+0x190) + 0x25c) -- the message-manager's
// short-event flag (NOT a Generator field). The manager is null in bring-up
// (BT_GetMessageManager stub), so guard the deref; the real read resumes once
// the SubsystemMessageManager (0xBD3) roster lookup is wired.
SubsystemMessageManager *msgMgr = BT_GetMessageManager((Mech *)owner); // *(this[0x34]+0x190)
if (!IsDamaged() // FUN_004ac9c8 == 0
&& msgMgr != 0
&& *(LWord *)((char *)msgMgr + 0x25c) != 0 // message-manager short-event flag
// CORRECTED (issue #2 [T1]): @004b0efc reads *(int*)(*(owner+0x190)+0x25c)
// -- owner mech+0x190 is the PLAYER LINK, and +0x25c is the BTPlayer's
// "sim live" EXPERIENCE flag (0 only for novice), NOT a message-manager
// field (the old misattribution left this path dead behind the null
// BT_GetMessageManager stub). The leading FUN_004ac9c8==0 term is the
// not-novice predicate (BTPlayerRoleLocksAdvanced), not a damage query.
if (!BTPlayerRoleLocksAdvanced(owner) // FUN_004ac9c8 == 0 (not novice)
&& BTPlayerExperienceSimLive(owner) != 0 // *(player+0x25c) != 0
&& message == 4)
{
ForceShortRecovery(); // FUN_004b11bc(this)
@@ -477,10 +479,15 @@ Logical
void
PoweredSubsystem::ForceShortRecovery() // internal helper for @004b0efc
{
if (IsDamaged()) // FUN_004ac9c8
// CORRECTED (issue #2 [T1]): both FUN_004ac9c8 calls here are the
// not-novice EXPERIENCE predicate on the owning player (each subsystem
// hops sub->owner mech->playerLink; this part and its source share the
// same mech, hence the same player) -- NOT damage-state queries (the old
// HeatSink::IsDamaged stand-in read simulationState).
if (BTPlayerRoleLocksAdvanced(owner)) // FUN_004ac9c8(this)
return;
Generator *source = (Generator *)voltageSource.Resolve(); // FUN_00417ab4(this+0x1d0)
if (source != 0 && !source->IsDamaged())
if (source != 0 && !BTPlayerRoleLocksAdvanced(owner)) // FUN_004ac9c8(source): same mech -> same player
{
source->stateAlarm.SetLevel(Generator::GeneratorShorted); // source+0x1fc -> 3
source->outputVoltage = 0.0f; // source+0x1f8 = 0
@@ -1086,7 +1093,8 @@ Logical
{
if (message == 4)
{
if ((simulationState == 2 || simulationState == 0) && !IsDamaged()) // this[0x10]; FUN_004ac9c8
if ((simulationState == 2 || simulationState == 0) // this[0x10]
&& !BTPlayerRoleLocksAdvanced(owner)) // FUN_004ac9c8 == 0 (not novice; issue #2 -- was the state-based IsDamaged stand-in, which wrongly blocked state 2)
{
stateAlarm.SetLevel(GeneratorShorted); // 3
shortTimer = 0.0f; // this[0x7e]
@@ -1255,7 +1263,10 @@ Logical
Generator *source = (watched != 0) // FUN_00417ab4(watched+0x1d0)
? (Generator *)((PoweredSubsystem *)watched)->ResolveVoltageSource()
: 0;
if (source != 0 && !source->IsDamaged())
// @004b179c's FUN_004ac9c8(source) [T1] = the not-novice EXPERIENCE
// predicate on the source's owner (same mech as ours; issue #2 -- was
// the state-based IsDamaged stand-in).
if (source != 0 && !BTPlayerRoleLocksAdvanced(owner))
{
source->stateAlarm.SetLevel(Generator::GeneratorShorted); // source+0x1fc -> 3
source->outputVoltage = 0.0f; // source+0x1f8 = 0