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
+33 -8
View File
@@ -49,7 +49,7 @@
// FUN_004b9948 MechWeapon::GetMuzzlePoint FUN_004b9864 MechWeapon::ComputeAimOrientation
// FUN_004b9728 MechWeapon::DrawWeaponPip FUN_004b9cbc MechWeapon::GetTargetPosition
// FUN_004b0bd0 PoweredSubsystem simulation step FUN_004b0d50 PoweredSubsystem scalar query
// FUN_004ad7d4 HeatableSubsystem "is online/active" FUN_004ac9c8 HeatableSubsystem heat-state query
// FUN_004ad7d4 heat-model experience gate (player+0x260; HeatModelActive) FUN_004ac9c8 novice lockout (player+0x274==0; MechSubsystem::IsDamaged)
// FUN_00417ab4 follow a SharedData link (voltage source / ammo bin)
// FUN_0049fb54 Mech::IsDestroyed (movementMode 2||9) FUN_0041a1a4 IsDerivedFrom(classDerivations)
// FUN_0041bbd8 AlarmIndicator::SetLevel(n) (weaponAlarm @0x350, state @0x364)
@@ -74,6 +74,8 @@
#endif
#include <hostmgr.hpp> // HostManager::GetEntityPointer -- the update-record target resolve (task #51)
#include <math.h> // sqrt -- the SeekVoltageResponse sampler (issue #11)
#include <map> // [aud-tail] per-weapon previous-state tracking (Gitea #5, instrumentation only)
#include <ctime> // [aud-tail] clock() ms timestamps (matches L4AUDLVL.cpp)
// E8 (bring-up): the player fire input. The controls mapper that would normally write
// the weapon's fireImpulse is bypassed (mech4.cpp), so EmitterSimulation reads this
@@ -108,8 +110,12 @@ static void Normalize(EulerAngles &) {}
static void MakeIdentity(LinearMatrix &m) { m.BuildIdentity(); }
static void CopyColor(void *, const void *) {}
// engine-internal artifacts the decomp called by address (stand-ins)
static int FUN_004ad7d4(void *) { return 1; } // HeatableSubsystem active?
// (issue #2: the local FUN_004ad7d4 `return 1` / FUN_004ac9c8 `return 0`
// stand-ins are RETIRED -- FUN_004ad7d4 is the player+0x260 HEAT-MODEL
// experience gate (HeatSink::HeatModelActive(), heat.hpp -> the btplayer.cpp
// bridge) and FUN_004ac9c8 is the player+0x274==0 NOVICE lockout
// (MechSubsystem::IsDamaged() -> the same bridge family); call sites now use
// the real members. See context/experience-levels.md.)
// FUN_0049fb54 == Mech::IsDestroyed (movementMode 2||9; reconstructed as
// Mech::IsDisabled). Was a mislabeled "cockpit/HUD query" no-op stub -- so a
// live beam never CUT when its target mech died (@004ba8d0/@004baa20 check
@@ -131,7 +137,6 @@ static int BTMechDestroyed(Entity *e)
// TrackSeekVoltage never charged. The E7 one-frame force-charge that covered
// for it made every emitter recycle in ~0.3s instead of the authored 2-6s
// RechargeRate -- the actual root cause of the task-#10 heat runaway.)
static int FUN_004ac9c8(void *) { return 0; } // heat-state query
static int FUN_00421070(void *, void *) { return 0; } // damage source resolve
static void *ResolveSource(void *) { return 0; } // named source registry lookup
@@ -281,7 +286,7 @@ void
// E5: pendingHeat (HeatSink @0x1C8) is the field the heat sim absorbs each frame
// (heatEnergy += pendingHeat -> temperature); the old Emitter-local heatAccumulator
// was a SHADOW at the wrong offset, so firing never reached the heat model.
if (FUN_004ad7d4(this)) // HeatableSubsystem active
if (HeatModelActive()) // FUN_004ad7d4 -- player+0x260 heat-model experience gate (issue #2)
{
pendingHeat += heatPortion; // inherited HeatSink::pendingHeat @0x1c8
}
@@ -502,6 +507,26 @@ void
default:
break;
}
// [aud-tail] (Gitea #5 verdict harness, additive): timestamped WeaponState
// transitions. Leave-0 (Firing) == the beam-visual end (firingActive is
// cleared on the same path); 3->2 (Loading->Loaded) is where the authored
// charge-loop stop watcher fires StopNote and the release fade begins.
// Correlate against the [aud-tail] logs in L4AUDLVL.cpp (same CRT clock()
// ms). BT_AUD_TAIL=1 gates; instrumentation only, no behavior change.
if (getenv("BT_AUD_TAIL"))
{
static std::map<const void *, int> s_prevState;
int st = (int)weaponAlarm.GetState();
std::map<const void *, int>::iterator it = s_prevState.find(this);
if (it == s_prevState.end() || it->second != st)
{
DEBUG_STREAM << "[aud-tail] t=" << (long)clock() << "ms " << GetName()
<< " state " << ((it == s_prevState.end()) ? -1 : it->second)
<< " -> " << st << "\n" << std::flush;
s_prevState[this] = st;
}
}
}
@@ -530,7 +555,7 @@ void
seekRate = (src->MeasuredVoltage() - currentLevel) / dtScale; // 0x45c = (src+0x1dc - 0x414)/..
currentLevel = (seekRate / energyCoefficient) * time_slice + currentLevel; // 0x414
if (FUN_004ad7d4(this)) // heatable/online
if (HeatModelActive()) // FUN_004ad7d4 -- player+0x260 heat-model experience gate (issue #2)
{
// @004ba838:7544 -- the charging I^2R loss lands in the GENERATOR's
// pendingHeat (src+0x1c8): recharging weapons is what heats generators
@@ -628,11 +653,11 @@ void
int
Emitter::AdvanceSeekVoltage(UpdateRecord *message)
{
if (FUN_004ac9c8(this)) // heat-state set -> skip
if (MechSubsystem::IsDamaged()) // FUN_004ac9c8 -- NOVICE lockout (seek-voltage cycling is an advanced cockpit control; issue #2)
{
return /*non-zero status*/ 1;
}
if (!FUN_004ad7d4(this) || message->payloadCount <= 0) // message+0xc
if (!HeatModelActive() || message->payloadCount <= 0) // FUN_004ad7d4 -- heat-model gate; message+0xc
{
return 0;
}