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:
co-authored by
Claude Fable 5
parent
a0cec48e3f
commit
d7158f1f82
@@ -1273,13 +1273,14 @@ BTPlayer::BTPlayer(
|
||||
// WinTesla analog, so the dead compare is dropped. @005132f4
|
||||
|
||||
//
|
||||
// On a replicant copy, cache the application's mission/registry pointer
|
||||
// (application+0xc8 in the binary; no GetMissionRegistry accessor in the
|
||||
// WinTesla Application, so read through the documented offset).
|
||||
// On a replicant copy, back-fill the mission pointer (the base Player ctor
|
||||
// leaves playerMission NULL for replicants; the binary's @0x1f8 slot IS
|
||||
// Player::playerMission -- app+0xc8 == Application::GetCurrentMission()).
|
||||
// The old separate `btMission` phantom member is gone (issue #2).
|
||||
//
|
||||
if ((simulationFlags & 0xc) == 4 && btMission == 0) // this+0x28, this[0x7e]
|
||||
if ((simulationFlags & 0xc) == 4 && playerMission == 0) // this+0x28, this[0x7e]
|
||||
{
|
||||
btMission = (Mission *)*(void **)((char *)application + 0xc8); // app+0xc8
|
||||
playerMission = application->GetCurrentMission(); // app+0xc8
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1291,7 +1292,7 @@ BTPlayer::BTPlayer(
|
||||
//
|
||||
CString role_key(creation_message->roleName); // make+0x90
|
||||
(void)role_key;
|
||||
// scenarioRole = btMission->GetRoleRegistry()->Lookup(&role_key); // this[0x7e]+0x50, this[0x82]
|
||||
// scenarioRole = playerMission->GetRoleRegistry()->Lookup(&role_key); // this[0x7e]+0x50, this[0x82]
|
||||
|
||||
if ((simulationFlags & 0xc) == 4)
|
||||
{
|
||||
@@ -1305,42 +1306,54 @@ BTPlayer::BTPlayer(
|
||||
//
|
||||
// Master: derive the game-mode flags from the EXPERIENCE level.
|
||||
//
|
||||
// CORRECTED SOURCE (2026-07-18 [T1]): the binary reads
|
||||
// btMission->experienceLevel (mission+0xe4, the egg's per-pilot
|
||||
// "experience" 0..3 = novice/standard/veteran/expert) — NOT the role's
|
||||
// returnFromDeath (the old reading). See context/experience-levels.md.
|
||||
// WIRED (issue #2, 2026-07-20 [T1]): the binary @004c0bc8
|
||||
// (part_013.c:10843-10870) reads btMission->experienceLevel
|
||||
// (mission+0xe4, the egg's per-pilot "experience" 0..3 =
|
||||
// novice/standard/veteran/expert) — NOT the role's returnFromDeath (the
|
||||
// old stand-in, which NULL-defaulted every pilot to veteran). The
|
||||
// mission pointer is the inherited Player::playerMission (= the
|
||||
// binary's this+0x1f8), set by the base ctor on the master branch; the
|
||||
// concrete mission is a BTL4Mission (is-a BTMission). Binary rows for
|
||||
// (0x25c,0x260,0x26c,0x270): nov 0,0,0,0 / std 1,0,1,1 / vet 1,1,1,1 /
|
||||
// exp 1,1,0,1. Note the two old slot errors, now fixed: the binary
|
||||
// switch never touches advancedDamageOn(0x264) (set below), and
|
||||
// expert's 0 lands on levelFlag26c(0x26c), not levelFlag270(0x270).
|
||||
// See context/experience-levels.md.
|
||||
//
|
||||
// STAND-IN [T3]: still seeded from the role (default 2 ≈ VETERAN) until
|
||||
// the wiring task points this at BTMission::ExperienceLevel(). Known
|
||||
// slot drift vs the binary switch: the binary never touches
|
||||
// showDamageInflicted(0x264) here (that's the advancedDamage copy, set
|
||||
// below), and expert's 0 lands on roleReturnDelay2(0x26c), not
|
||||
// showScore(0x270) — binary rows: nov 0,0,0,0 / std 1,0,1,1 /
|
||||
// vet 1,1,1,1 / exp 1,1,0,1 for (0x25c,0x260,0x26c,0x270).
|
||||
roleClassIndex = scenarioRole ? scenarioRole->GetReturnFromDeath() : 2; // this[0x9d] <- mission+0xe4 in the binary
|
||||
switch (roleClassIndex)
|
||||
BTMission *bt_mission = (BTMission *)playerMission; // this[0x7e]
|
||||
experienceLevel = bt_mission != 0
|
||||
? bt_mission->ExperienceLevel() // mission+0xe4
|
||||
: (int)BTMission::VeteranMode; // [T3] dev-permissive; the binary derefs unguarded (master always has a mission)
|
||||
switch (experienceLevel) // this[0x9d]
|
||||
{
|
||||
case 0:
|
||||
showKills = showDamageReceived = showDamageInflicted = showScore = 0;
|
||||
case BTMission::NoviceMode: // 0: everything off
|
||||
heatModelOn = simLive = levelFlag26c = levelFlag270 = 0;
|
||||
break;
|
||||
case 1:
|
||||
showKills = 0;
|
||||
showDamageReceived = showDamageInflicted = showScore = 1;
|
||||
case BTMission::StandardMode: // 1: live, no heat model
|
||||
heatModelOn = 0;
|
||||
simLive = levelFlag26c = levelFlag270 = 1;
|
||||
break;
|
||||
case 2:
|
||||
showKills = showDamageReceived = showDamageInflicted = showScore = 1;
|
||||
case BTMission::VeteranMode: // 2: everything on
|
||||
heatModelOn = simLive = levelFlag26c = levelFlag270 = 1;
|
||||
break;
|
||||
case 3:
|
||||
showKills = showDamageReceived = showDamageInflicted = 1;
|
||||
showScore = 0;
|
||||
case BTMission::ExpertMode: // 3: on, but 0x26c off
|
||||
heatModelOn = simLive = levelFlag270 = 1;
|
||||
levelFlag26c = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// CORRECTED (2026-07-18 [T1]): the binary sets this[0x99]/[0x9a]
|
||||
// The binary then unconditionally copies this[0x99]/[0x9a]
|
||||
// (0x264/0x268) = btMission->advancedDamageOn (mission+0xf0, the egg
|
||||
// "advancedDamage" technician flag) — an int pair, not role floats.
|
||||
// 0 here = advanced damage OFF, which matches the bring-up eggs. [T3]
|
||||
roleReturnDelay = roleReturnDelay2 = 0.0f; // this[0x99],[0x9a] <- mission+0xf0 in the binary
|
||||
// "advancedDamage" technician flag; an int pair). [T1]
|
||||
advancedDamageOn = advancedDamageOn2 = // this[0x99],[0x9a]
|
||||
bt_mission != 0 ? bt_mission->AdvancedDamageOn() : False;
|
||||
|
||||
// Verification sentinel (issue #2): one line per master player.
|
||||
DEBUG_STREAM << "[exp] master player experience=" << experienceLevel
|
||||
<< " simLive=" << (int)simLive
|
||||
<< " heatModelOn=" << (int)heatModelOn
|
||||
<< " advDamage=" << (int)advancedDamageOn
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
|
||||
killCount = 0; // this[0x9f]
|
||||
@@ -1617,7 +1630,52 @@ int BTPlayerRoleLocksAdvanced(void *owner_mech)
|
||||
BTPlayer *player = MECH_OWNING_PLAYER(owner_mech);
|
||||
if (player == 0)
|
||||
return 0; // dev-permissive [T3]
|
||||
return (player->roleClassIndex == 0) ? 1 : 0; // player+0x274 == 0
|
||||
return (player->experienceLevel == 0) ? 1 : 0; // player+0x274 == 0
|
||||
}
|
||||
|
||||
|
||||
//#############################################################################
|
||||
// BTPlayerExperienceSimLive -- complete-type bridge (issue #2)
|
||||
//
|
||||
// The player+0x25c "sim live" flag [T1]: 0 only for NOVICE experience. Binary
|
||||
// consumers reached through the owner mech's playerLink (mech+0x190):
|
||||
// * CheckForJam @004bbfcc (projweap.cpp LiveFireEnabled) -- no jam rolls;
|
||||
// * Searchlight::ToggleLamp @004b860c (searchlight.hpp ControlsAllowLights);
|
||||
// * PoweredSubsystem::HandleMessage @004b0efc short-event path (powersub.cpp).
|
||||
// A NULL player (target dummy / unbound dev mech) reads as LIVE so dev rigs
|
||||
// keep today's behavior [T3]; the binary derefs unguarded.
|
||||
//
|
||||
int BTPlayerExperienceSimLive(void *owner_mech)
|
||||
{
|
||||
if (owner_mech == 0)
|
||||
return 1; // dev-permissive [T3]
|
||||
BTPlayer *player = MECH_OWNING_PLAYER(owner_mech);
|
||||
if (player == 0)
|
||||
return 1; // dev-permissive [T3]
|
||||
return player->simLive ? 1 : 0; // player+0x25c
|
||||
}
|
||||
|
||||
|
||||
//#############################################################################
|
||||
// BTPlayerExperienceHeatModelOn -- complete-type bridge (issue #2)
|
||||
//
|
||||
// FUN_004ad7d4 [T1]: `return *(*(*(sub+0xD0)+0x190)+0x260)` -- the HEAT-MODEL
|
||||
// master switch, ON only for VETERAN and EXPERT experience. The single
|
||||
// most-called gate in the subsystem family: HeatSink::HeatModelActive()
|
||||
// (heat.hpp -- weapon-fire heat, the heat.cpp simulation, coolant), missile
|
||||
// launch heat (mislanch.cpp), projectile firing heat (projweap.cpp), and
|
||||
// Myomers::OwnerAdvancedDamage() (movement heat, myomers.hpp). Standard mode
|
||||
// has NO heat consequences -- authentic, not a port gap. A NULL player reads
|
||||
// as ON (today's permissive stub behavior) so dev rigs are unchanged [T3].
|
||||
//
|
||||
int BTPlayerExperienceHeatModelOn(void *owner_mech)
|
||||
{
|
||||
if (owner_mech == 0)
|
||||
return 1; // dev-permissive [T3]
|
||||
BTPlayer *player = MECH_OWNING_PLAYER(owner_mech);
|
||||
if (player == 0)
|
||||
return 1; // dev-permissive [T3]
|
||||
return player->heatModelOn ? 1 : 0; // player+0x260
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user