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
@@ -86,22 +86,41 @@ the `Tesla40_BT_manual.pdf` scan along with p3):
|
|||||||
- **Movement heat is veteran+expert in 4.10** (the +0x260 gate), not expert-only as the 4.0 manual
|
- **Movement heat is veteran+expert in 4.10** (the +0x260 gate), not expert-only as the 4.0 manual
|
||||||
states. Whether a coefficient still differentiates them is open.
|
states. Whether a coefficient still differentiates them is open.
|
||||||
|
|
||||||
## Port state (as of 2026-07-18) — wiring needed [T3]
|
## Port state (as of 2026-07-20) — WIRED (issue #2) [T2]
|
||||||
|
|
||||||
The reconstruction met this system piecemeal and stubbed the same gates inconsistently:
|
The wiring landed (uncommitted during the coordination pause; issue #2):
|
||||||
- `btplayer.cpp` ctor seeds `roleClassIndex` from `scenarioRole->GetReturnFromDeath()` (default 2)
|
- **Seed**: the `BTPlayer` master ctor (`btplayer.cpp` ~1305) now reads
|
||||||
— the binary seeds it from `btMission->ExperienceLevel()`. Bring-up therefore runs as VETERAN.
|
`((BTMission *)playerMission)->ExperienceLevel()` + `->AdvancedDamageOn()`. The binary's +0x1f8
|
||||||
Two slot errors in the port's switch vs the binary: the binary's switch never touches +0x264
|
mission pointer IS the inherited `Player::playerMission` (master: set by the base ctor;
|
||||||
(that's advancedDamage, set after), and expert's `0` lands on +0x26c, not +0x270.
|
replicant: back-filled from `Application::GetCurrentMission()` = app+0xc8) — the old separate
|
||||||
- `HeatModelActive()` stubbed permissive `return 1` (emitter/heat/mislanch/projweap) while
|
`btMission` member was a phantom duplicate, never set on the master branch; removed. The two
|
||||||
`Myomers::OwnerAdvancedDamage()` returns `False` — the SAME binary gate (+0x260) answered both
|
switch slot errors are fixed (binary rows verified against part_013.c:10843-10870). A `[exp]`
|
||||||
ways: heat accrues from weapons but never from movement, and no experience below veteran can
|
sentinel logs the seeded flags per master player.
|
||||||
switch it off.
|
- **Renames** (btplayer.hpp): showDamageReceived→`simLive` (0x25c), showKills→`heatModelOn`
|
||||||
- `LiveFireEnabled` / `ControlsAllowLights` / powersub's short-event flag = the +0x25c not-novice
|
(0x260), showDamageInflicted→`advancedDamageOn` (0x264), roleReturnDelay→`advancedDamageOn2`
|
||||||
flag under three names.
|
(0x268, was mistyped Scalar), roleReturnDelay2→`levelFlag26c` (0x26c), showScore→`levelFlag270`
|
||||||
- **The fix**: one accessor family on BTPlayer named members (databinding trap — never raw-read
|
(0x270), roleClassIndex→`experienceLevel` (0x274).
|
||||||
`player+0x260`), seeded from `BTMission::ExperienceLevel()` + `AdvancedDamageOn()`, with all five
|
- **Bridges** (btplayer.cpp, complete-type TU): `BTPlayerExperienceSimLive` (+0x25c) and
|
||||||
stub sites pointed at it; then an egg `experience` line actually selects the sim tier as shipped.
|
`BTPlayerExperienceHeatModelOn` (+0x260) join `BTPlayerRoleLocksAdvanced` (+0x274==0). All
|
||||||
|
consumers route mech→`GetPlayerLink()`→named member; NULL player reads permissive [T3].
|
||||||
|
- **Gates unified**: `HeatSink::HeatModelActive()` (heat.hpp; also serves mislanch/projweap/
|
||||||
|
heatfamily call sites) + `Myomers::OwnerAdvancedDamage()` (myomers.hpp) → the +0x260 bridge;
|
||||||
|
`ProjectileWeapon::LiveFireEnabled()` (projweap.cpp) + `Searchlight::ControlsAllowLights()`
|
||||||
|
(searchlight.hpp) → the +0x25c bridge; `PoweredSubsystem::HandleMessage` @4b0efc (powersub.cpp)
|
||||||
|
→ not-novice + the +0x25c bridge (the old "message-manager short-event flag" misattribution and
|
||||||
|
its dead `BT_GetMessageManager` guard are gone).
|
||||||
|
- **FUN_004ac9c8 family corrected**: `MechSubsystem::IsDamaged` (mechsub.cpp — was a live
|
||||||
|
raw-offset databinding trap `*(*(*(owner+0xd0)+0x190)+0x274)`), `BT_IsBusLive` (btstubs.cpp —
|
||||||
|
was `return True`), and powersub's `IsDamaged()` call sites @4b11bc/@4b21d0/@4b179c now all
|
||||||
|
route through `BTPlayerRoleLocksAdvanced`. `HeatSink::IsDamaged` (heat.hpp) deliberately keeps
|
||||||
|
the simulationState shadow for the gauge tint (btl4gaug.cpp:172).
|
||||||
|
- **SHIPPED EGGS ARE ALL `expert`** (every pilot page in all 19 content/*.EGG, camera pages
|
||||||
|
included) — so the default felt behavior is unchanged by the wiring (expert and the old
|
||||||
|
hardwired veteran agree on every LOCATED consumer: sim live, heat on, controls unlocked; they
|
||||||
|
differ only in the unlocated +0x26c flag). The glass front-end writes `veteran` when a pilot's
|
||||||
|
experience is empty (btl4fe.cpp:307).
|
||||||
|
- Myomers movement heat remains arithmetically inert (the Owner* motion accessors still return
|
||||||
|
neutral 0s — the gate is wired, the couplings are the reconciled follow-up).
|
||||||
|
|
||||||
## Corrections (swept 2026-07-18)
|
## Corrections (swept 2026-07-18)
|
||||||
|
|
||||||
|
|||||||
+23
-22
@@ -164,8 +164,9 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
|||||||
btl4vid.cpp `MakeMechRenderables` reticle-build/inside pass (== arcade part_014.c:5173, Dynamic,
|
btl4vid.cpp `MakeMechRenderables` reticle-build/inside pass (== arcade part_014.c:5173, Dynamic,
|
||||||
bound per Searchlight `lightState` via a new `LightStatePtr()` accessor); (3) a toggle input (the
|
bound per Searchlight `lightState` via a new `LightStatePtr()` accessor); (3) a toggle input (the
|
||||||
authentic cockpit button-5→`ToggleLamp` dispatch is a controls-family reconstruction; a dev key
|
authentic cockpit button-5→`ToggleLamp` dispatch is a controls-family reconstruction; a dev key
|
||||||
stands in). Also flags a deferred **`Mech::ControlsAllowLights()`** (searchlight.hpp:158 stub;
|
stands in). `ControlsAllowLights()` (searchlight.hpp) is WIRED since issue #2 to the
|
||||||
2026-07-18: identified as the `player+0x25c` not-novice experience flag — [[experience-levels]]).
|
`player+0x25c` not-novice experience flag via the BTPlayerExperienceSimLive bridge
|
||||||
|
([[experience-levels]]); the consumer (ToggleLamp) still needs the cockpit button dispatch.
|
||||||
See [[rendering]] fog section. Verified inert live: BT_FOG_LOG shows zero `SetFogStyle(2/3)`.
|
See [[rendering]] fog section. Verified inert live: BT_FOG_LOG shows zero `SetFogStyle(2/3)`.
|
||||||
- **Factory capability-roster loops 2-4 are STILL DEAD (task #57 discovery).** mech.cpp's
|
- **Factory capability-roster loops 2-4 are STILL DEAD (task #57 discovery).** mech.cpp's
|
||||||
post-roster loops add to `heatableSubsystems`(0x51155c)/`weaponRoster`(0x511830)/
|
post-roster loops add to `heatableSubsystems`(0x51155c)/`weaponRoster`(0x511830)/
|
||||||
@@ -208,17 +209,15 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
|||||||
behavior in [[experience-levels]] (+ [[decomp-reference]] §3 flag table).** Resolved en route: the
|
behavior in [[experience-levels]] (+ [[decomp-reference]] §3 flag table).** Resolved en route: the
|
||||||
old "resolve first" items — `FUN_004ad7d4` (labeled both `HeatModelActive` and `OwnerAdvancedDamage`)
|
old "resolve first" items — `FUN_004ad7d4` (labeled both `HeatModelActive` and `OwnerAdvancedDamage`)
|
||||||
is ONE body reading `player+0x260` = the heat-model master switch (veteran+expert); `FUN_004ac9c8`
|
is ONE body reading `player+0x260` = the heat-model master switch (veteran+expert); `FUN_004ac9c8`
|
||||||
reads `player+0x274` = the novice lockout; the btplayer.hpp `showKills`/`roleClassIndex` scoring
|
reads `player+0x274` = the novice lockout.
|
||||||
names indeed do NOT match the gate semantics (comments corrected in place, member names kept).
|
**✅ THE WIRING LANDED (issue #2, 2026-07-20) [T2]:** the ctor now seeds from
|
||||||
**The wiring (small, still open):** ONE accessor family on BTPlayer named members seeded from
|
`BTMission::ExperienceLevel()`/`AdvancedDamageOn()` via the inherited `Player::playerMission`
|
||||||
`BTMission::ExperienceLevel()`/`AdvancedDamageOn()` (databinding trap: do NOT raw-read
|
(the binary's +0x1f8; the old `btMission` phantom member removed), the members are renamed to
|
||||||
`player+0x260`), then point the five stub sites at it (`HeatModelActive` permissive-1 in
|
the true semantics (`simLive`/`heatModelOn`/`experienceLevel`/…), and all five gate stubs +
|
||||||
emitter/heat/mislanch/projweap vs `OwnerAdvancedDamage` False in myomers — currently the SAME gate
|
the FUN_004ac9c8 family (`MechSubsystem::IsDamaged`, `BT_IsBusLive`, powersub call sites) route
|
||||||
answered both ways; `LiveFireEnabled`/`ControlsAllowLights`/powersub short-event = the +0x25c
|
through the three complete-type bridges (`BTPlayerExperienceSimLive`/`…HeatModelOn`/
|
||||||
flag under three names; ctor seeding from role->returnFromDeath is a bring-up stand-in that runs
|
`BTPlayerRoleLocksAdvanced`). Every shipped egg says `experience=expert`, so default behavior is
|
||||||
every mission as VETERAN). NOTE: these are MODE flags — an egg with `experience=standard` should
|
unchanged. Detail: [[experience-levels]] §Port state.
|
||||||
authentically have inert movement-heat/jams; wiring makes the tier selectable, not the basic
|
|
||||||
mission different. The MessageBoard feed is separate (StatusMessagePool, below).
|
|
||||||
- **✅ Authentic target acquisition RECONSTRUCTED (tasks #36/#39, 2026-07-08)** — the `Reticle`
|
- **✅ Authentic target acquisition RECONSTRUCTED (tasks #36/#39, 2026-07-08)** — the `Reticle`
|
||||||
pick-ray chain is LIVE (see [[combat-damage]] Targeting for the full port map): the crosshair =
|
pick-ray chain is LIVE (see [[combat-damage]] Targeting for the full port map): the crosshair =
|
||||||
**torso boresight** (NO free-aim mouse — the pod stick twisted the torso; you steer to aim) →
|
**torso boresight** (NO free-aim mouse — the pod stick twisted the torso; you steer to aim) →
|
||||||
@@ -373,16 +372,18 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
|||||||
-> +0x274 == 0` = the NOVICE-experience lockout for advanced cockpit systems
|
-> +0x274 == 0` = the NOVICE-experience lockout for advanced cockpit systems
|
||||||
(2026-07-18 correction: +0x274 = the egg experience level, NOT a role class --
|
(2026-07-18 correction: +0x274 = the egg experience level, NOT a role class --
|
||||||
see [[experience-levels]]; port bridge BTPlayerRoleLocksAdvanced, btplayer.cpp;
|
see [[experience-levels]]; port bridge BTPlayerRoleLocksAdvanced, btplayer.cpp;
|
||||||
NULL player = unlocked [T3]; bring-up seeding defaults to 2 ≈ veteran = unlocked).
|
NULL player = unlocked [T3]; seeded from the egg experience since issue #2).
|
||||||
AUDIT TAIL: the other
|
✅ AUDIT TAIL CLOSED (issue #2, 2026-07-20): every powersub.cpp site annotated
|
||||||
powersub.cpp sites annotated FUN_004ac9c8 (the coolant-draw gate ~:427,
|
FUN_004ac9c8 (PoweredSubsystem::HandleMessage @4b0efc, ForceShortRecovery
|
||||||
ForceShortRecovery ~:444, the Generator/PowerWatcher site ~:1085) still call the
|
@4b11bc, Generator::HandleMessage @4b21d0, PowerWatcher::HandleMessage @4b179c)
|
||||||
heat-family IsDamaged (simulationState != 0) stand-in -- each needs its raw fn
|
plus MechSubsystem::IsDamaged and BT_IsBusLive now call the bridge; the
|
||||||
re-checked and swapped to the role bridge where the binary calls 4ac9c8.
|
state-based HeatSink::IsDamaged shadow remains only for the gauge tint.
|
||||||
- **Myomers authentic coupling** — the structural un-stub is INERT (mover feed + heat-gen no-op).
|
- **Myomers authentic coupling** — the structural un-stub is INERT (mover feed + heat-gen no-op).
|
||||||
Real coupling needs the heat-model gate (`OwnerAdvancedDamage`/`FUN_004ad7d4` → the owning
|
The heat-model gate (`OwnerAdvancedDamage`/`FUN_004ad7d4` → the owning **BTPlayer**
|
||||||
**BTPlayer** `mech+0x190`+0x260 = veteran/expert experience, NOT 0xBD3 — see [[experience-levels]])
|
`mech+0x190`+0x260 = veteran/expert experience — see [[experience-levels]]) is WIRED since
|
||||||
+ `MoverAttach` routing into the LIVE JointedMover (must be reconciled with the gait cutover first).
|
issue #2, but the sim stays arithmetically inert (the Owner* motion accessors return neutral
|
||||||
|
0s). Real coupling still needs those accessors + `MoverAttach` routing into the LIVE
|
||||||
|
JointedMover (must be reconciled with the gait cutover first).
|
||||||
- **SeekVoltageGraph** — 4 Seek* attrs unpublished (a cluster-child, not config-called; non-blocking).
|
- **SeekVoltageGraph** — 4 Seek* attrs unpublished (a cluster-child, not config-called; non-blocking).
|
||||||
|
|
||||||
- **✅ DAMAGE ECONOMY — AUTHENTIC (task #8, 2026-07-11) [T1/T2].** The whole economy closes:
|
- **✅ DAMAGE ECONOMY — AUTHENTIC (task #8, 2026-07-11) [T1/T2].** The whole economy closes:
|
||||||
|
|||||||
@@ -1273,13 +1273,14 @@ BTPlayer::BTPlayer(
|
|||||||
// WinTesla analog, so the dead compare is dropped. @005132f4
|
// WinTesla analog, so the dead compare is dropped. @005132f4
|
||||||
|
|
||||||
//
|
//
|
||||||
// On a replicant copy, cache the application's mission/registry pointer
|
// On a replicant copy, back-fill the mission pointer (the base Player ctor
|
||||||
// (application+0xc8 in the binary; no GetMissionRegistry accessor in the
|
// leaves playerMission NULL for replicants; the binary's @0x1f8 slot IS
|
||||||
// WinTesla Application, so read through the documented offset).
|
// 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
|
CString role_key(creation_message->roleName); // make+0x90
|
||||||
(void)role_key;
|
(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)
|
if ((simulationFlags & 0xc) == 4)
|
||||||
{
|
{
|
||||||
@@ -1305,42 +1306,54 @@ BTPlayer::BTPlayer(
|
|||||||
//
|
//
|
||||||
// Master: derive the game-mode flags from the EXPERIENCE level.
|
// Master: derive the game-mode flags from the EXPERIENCE level.
|
||||||
//
|
//
|
||||||
// CORRECTED SOURCE (2026-07-18 [T1]): the binary reads
|
// WIRED (issue #2, 2026-07-20 [T1]): the binary @004c0bc8
|
||||||
// btMission->experienceLevel (mission+0xe4, the egg's per-pilot
|
// (part_013.c:10843-10870) reads btMission->experienceLevel
|
||||||
// "experience" 0..3 = novice/standard/veteran/expert) — NOT the role's
|
// (mission+0xe4, the egg's per-pilot "experience" 0..3 =
|
||||||
// returnFromDeath (the old reading). See context/experience-levels.md.
|
// 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
|
BTMission *bt_mission = (BTMission *)playerMission; // this[0x7e]
|
||||||
// the wiring task points this at BTMission::ExperienceLevel(). Known
|
experienceLevel = bt_mission != 0
|
||||||
// slot drift vs the binary switch: the binary never touches
|
? bt_mission->ExperienceLevel() // mission+0xe4
|
||||||
// showDamageInflicted(0x264) here (that's the advancedDamage copy, set
|
: (int)BTMission::VeteranMode; // [T3] dev-permissive; the binary derefs unguarded (master always has a mission)
|
||||||
// below), and expert's 0 lands on roleReturnDelay2(0x26c), not
|
switch (experienceLevel) // this[0x9d]
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
case 0:
|
case BTMission::NoviceMode: // 0: everything off
|
||||||
showKills = showDamageReceived = showDamageInflicted = showScore = 0;
|
heatModelOn = simLive = levelFlag26c = levelFlag270 = 0;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case BTMission::StandardMode: // 1: live, no heat model
|
||||||
showKills = 0;
|
heatModelOn = 0;
|
||||||
showDamageReceived = showDamageInflicted = showScore = 1;
|
simLive = levelFlag26c = levelFlag270 = 1;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case BTMission::VeteranMode: // 2: everything on
|
||||||
showKills = showDamageReceived = showDamageInflicted = showScore = 1;
|
heatModelOn = simLive = levelFlag26c = levelFlag270 = 1;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case BTMission::ExpertMode: // 3: on, but 0x26c off
|
||||||
showKills = showDamageReceived = showDamageInflicted = 1;
|
heatModelOn = simLive = levelFlag270 = 1;
|
||||||
showScore = 0;
|
levelFlag26c = 0;
|
||||||
break;
|
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
|
// (0x264/0x268) = btMission->advancedDamageOn (mission+0xf0, the egg
|
||||||
// "advancedDamage" technician flag) — an int pair, not role floats.
|
// "advancedDamage" technician flag; an int pair). [T1]
|
||||||
// 0 here = advanced damage OFF, which matches the bring-up eggs. [T3]
|
advancedDamageOn = advancedDamageOn2 = // this[0x99],[0x9a]
|
||||||
roleReturnDelay = roleReturnDelay2 = 0.0f; // this[0x99],[0x9a] <- mission+0xf0 in the binary
|
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]
|
killCount = 0; // this[0x9f]
|
||||||
@@ -1617,7 +1630,52 @@ int BTPlayerRoleLocksAdvanced(void *owner_mech)
|
|||||||
BTPlayer *player = MECH_OWNING_PLAYER(owner_mech);
|
BTPlayer *player = MECH_OWNING_PLAYER(owner_mech);
|
||||||
if (player == 0)
|
if (player == 0)
|
||||||
return 0; // dev-permissive [T3]
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -342,8 +342,12 @@ class DropZone__ReplyMessage;
|
|||||||
// Scalar currentScore; // @0x278 (Player) -- running score
|
// Scalar currentScore; // @0x278 (Player) -- running score
|
||||||
|
|
||||||
// --- BT-specific ---
|
// --- BT-specific ---
|
||||||
Mission
|
// (The binary's @0x1f8 mission pointer IS the inherited
|
||||||
*btMission; // @0x1f8 cached mission / role registry source
|
// Player::playerMission -- the master ctor leaves the base's value and
|
||||||
|
// the replicant branch back-fills it from Application::GetCurrentMission.
|
||||||
|
// The old separate `btMission` member here was a phantom duplicate of
|
||||||
|
// the base field, never set on the master branch; removed in the
|
||||||
|
// issue-#2 experience wiring.)
|
||||||
char
|
char
|
||||||
teamName[64]; // @0x20c copied from MakeMessage (+0x50)
|
teamName[64]; // @0x20c copied from MakeMessage (+0x50)
|
||||||
BTTeam
|
BTTeam
|
||||||
@@ -358,28 +362,30 @@ class DropZone__ReplyMessage;
|
|||||||
//
|
//
|
||||||
// EXPERIENCE-LEVEL game-mode flags (2026-07-18 [T1] — the old "per-role
|
// EXPERIENCE-LEVEL game-mode flags (2026-07-18 [T1] — the old "per-role
|
||||||
// display toggles / returnFromDeath" reading was WRONG). The ctor
|
// display toggles / returnFromDeath" reading was WRONG). The ctor
|
||||||
// @004c0bc8 seeds this block from btMission(+0x1f8)->experienceLevel
|
// @004c0bc8 seeds this block from playerMission(+0x1f8)->experienceLevel
|
||||||
// (+0xe4, the egg's per-pilot "experience" novice/standard/veteran/
|
// (+0xe4, the egg's per-pilot "experience" novice/standard/veteran/
|
||||||
// expert) and ->advancedDamageOn(+0xf0) — NOT from the scenario role.
|
// expert) and ->advancedDamageOn(+0xf0) — NOT from the scenario role.
|
||||||
// The member NAMES below date from the scoring work and do not match
|
// Renamed to the true semantics by the issue-#2 wiring (old scoring-era
|
||||||
// the semantics; kept until the wiring task renames them. Binary
|
// names in the trailing comments). Binary mapping (nov/std/vet/exp) +
|
||||||
// mapping (nov/std/vet/exp) + consumers: context/experience-levels.md.
|
// consumers: context/experience-levels.md.
|
||||||
//
|
//
|
||||||
Logical
|
Logical
|
||||||
showDamageReceived; // @0x25c 0/1/1/1 "sim live" (novice lockout: jams, searchlight, powersub @4b0efc)
|
simLive; // @0x25c 0/1/1/1 "sim live" novice lockout: jams, searchlight, powersub @4b0efc (was showDamageReceived)
|
||||||
Logical
|
Logical
|
||||||
showKills; // @0x260 0/0/1/1 HEAT-MODEL master switch (FUN_004ad7d4)
|
heatModelOn; // @0x260 0/0/1/1 HEAT-MODEL master switch (FUN_004ad7d4) (was showKills)
|
||||||
Logical
|
Logical
|
||||||
showDamageInflicted;// @0x264 = mission advancedDamageOn (copy 1; NOT part of the level switch)
|
advancedDamageOn; // @0x264 = mission advancedDamageOn (copy 1; NOT part of the level switch) (was showDamageInflicted)
|
||||||
Logical
|
Logical
|
||||||
showScore; // @0x270 0/1/1/1 consumer not located
|
levelFlag270; // @0x270 0/1/1/1 consumer not located (was showScore)
|
||||||
Scalar
|
Logical
|
||||||
roleReturnDelay; // @0x268 = mission advancedDamageOn (copy 2; int in the binary)
|
advancedDamageOn2; // @0x268 = mission advancedDamageOn (copy 2; int in the binary) (was roleReturnDelay, mistyped Scalar)
|
||||||
Scalar
|
Logical
|
||||||
roleReturnDelay2; // @0x26c 0/1/1/0 consumer not located (supercharge-clamp hypothesis [T4])
|
levelFlag26c; // @0x26c 0/1/1/0 consumer not located (supercharge-clamp hypothesis [T4]) (was roleReturnDelay2, mistyped Scalar)
|
||||||
int
|
int
|
||||||
roleClassIndex; // @0x274 raw experience level 0..3 (FUN_004ac9c8 = ==0 novice predicate)
|
experienceLevel; // @0x274 raw experience level 0..3 (FUN_004ac9c8 = ==0 novice predicate) (was roleClassIndex)
|
||||||
friend int BTPlayerRoleLocksAdvanced(void *); // the FUN_004ac9c8 bridge (task #12)
|
friend int BTPlayerRoleLocksAdvanced(void *); // the FUN_004ac9c8 bridge (task #12): experienceLevel == 0
|
||||||
|
friend int BTPlayerExperienceSimLive(void *); // +0x25c reader (issue #2): jams / lights / powersub short path
|
||||||
|
friend int BTPlayerExperienceHeatModelOn(void *); // +0x260 reader (issue #2): FUN_004ad7d4 heat-model gate
|
||||||
friend void BTPlayerCountObservedDeath(void *); // observed-death tally (MP DEATHS fix)
|
friend void BTPlayerCountObservedDeath(void *); // observed-death tally (MP DEATHS fix)
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|||||||
@@ -222,11 +222,15 @@ SubsystemMessageManager *BT_GetMessageManager(Mech * /*owner*/)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The running player mech is a live, powered master -> its electrical bus is up.
|
// CORRECTED (issue #2 [T1]): *(*(owner+0x190)+0x274) != 0 is the owning
|
||||||
// (Real impl read *(*(owner+0x190)+0x274); see layout note above.)
|
// BTPlayer's raw EXPERIENCE level being non-zero -- i.e. NOT-NOVICE (the
|
||||||
Logical BT_IsBusLive(Mech * /*owner*/)
|
// Generator::HandleMessage @004b21d0 outer gate), not an electrical-bus
|
||||||
|
// probe. Routed through the complete-type bridge (databinding rule); a NULL
|
||||||
|
// player reads unlocked -> "live", preserving the old permissive dev behavior.
|
||||||
|
Logical BT_IsBusLive(Mech *owner)
|
||||||
{
|
{
|
||||||
return True;
|
extern int BTPlayerRoleLocksAdvanced(void *owner_mech); // btplayer.cpp (FUN_004ac9c8)
|
||||||
|
return BTPlayerRoleLocksAdvanced(owner) ? False : True;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No known-offset myomer back-pointer in the reconstructed Mech to clear.
|
// No known-offset myomer back-pointer in the reconstructed Mech to clear.
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
// FUN_004b9948 MechWeapon::GetMuzzlePoint FUN_004b9864 MechWeapon::ComputeAimOrientation
|
// FUN_004b9948 MechWeapon::GetMuzzlePoint FUN_004b9864 MechWeapon::ComputeAimOrientation
|
||||||
// FUN_004b9728 MechWeapon::DrawWeaponPip FUN_004b9cbc MechWeapon::GetTargetPosition
|
// FUN_004b9728 MechWeapon::DrawWeaponPip FUN_004b9cbc MechWeapon::GetTargetPosition
|
||||||
// FUN_004b0bd0 PoweredSubsystem simulation step FUN_004b0d50 PoweredSubsystem scalar query
|
// 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_00417ab4 follow a SharedData link (voltage source / ammo bin)
|
||||||
// FUN_0049fb54 Mech::IsDestroyed (movementMode 2||9) FUN_0041a1a4 IsDerivedFrom(classDerivations)
|
// FUN_0049fb54 Mech::IsDestroyed (movementMode 2||9) FUN_0041a1a4 IsDerivedFrom(classDerivations)
|
||||||
// FUN_0041bbd8 AlarmIndicator::SetLevel(n) (weaponAlarm @0x350, state @0x364)
|
// FUN_0041bbd8 AlarmIndicator::SetLevel(n) (weaponAlarm @0x350, state @0x364)
|
||||||
@@ -74,6 +74,8 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <hostmgr.hpp> // HostManager::GetEntityPointer -- the update-record target resolve (task #51)
|
#include <hostmgr.hpp> // HostManager::GetEntityPointer -- the update-record target resolve (task #51)
|
||||||
#include <math.h> // sqrt -- the SeekVoltageResponse sampler (issue #11)
|
#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
|
// 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
|
// 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 MakeIdentity(LinearMatrix &m) { m.BuildIdentity(); }
|
||||||
static void CopyColor(void *, const void *) {}
|
static void CopyColor(void *, const void *) {}
|
||||||
|
|
||||||
// engine-internal artifacts the decomp called by address (stand-ins)
|
// (issue #2: the local FUN_004ad7d4 `return 1` / FUN_004ac9c8 `return 0`
|
||||||
static int FUN_004ad7d4(void *) { return 1; } // HeatableSubsystem active?
|
// 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
|
// FUN_0049fb54 == Mech::IsDestroyed (movementMode 2||9; reconstructed as
|
||||||
// Mech::IsDisabled). Was a mislabeled "cockpit/HUD query" no-op stub -- so a
|
// 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
|
// 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
|
// 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
|
// 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.)
|
// 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 int FUN_00421070(void *, void *) { return 0; } // damage source resolve
|
||||||
static void *ResolveSource(void *) { return 0; } // named source registry lookup
|
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
|
// E5: pendingHeat (HeatSink @0x1C8) is the field the heat sim absorbs each frame
|
||||||
// (heatEnergy += pendingHeat -> temperature); the old Emitter-local heatAccumulator
|
// (heatEnergy += pendingHeat -> temperature); the old Emitter-local heatAccumulator
|
||||||
// was a SHADOW at the wrong offset, so firing never reached the heat model.
|
// 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
|
pendingHeat += heatPortion; // inherited HeatSink::pendingHeat @0x1c8
|
||||||
}
|
}
|
||||||
@@ -502,6 +507,26 @@ void
|
|||||||
default:
|
default:
|
||||||
break;
|
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)/..
|
seekRate = (src->MeasuredVoltage() - currentLevel) / dtScale; // 0x45c = (src+0x1dc - 0x414)/..
|
||||||
currentLevel = (seekRate / energyCoefficient) * time_slice + currentLevel; // 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
|
// @004ba838:7544 -- the charging I^2R loss lands in the GENERATOR's
|
||||||
// pendingHeat (src+0x1c8): recharging weapons is what heats generators
|
// pendingHeat (src+0x1c8): recharging weapons is what heats generators
|
||||||
@@ -628,11 +653,11 @@ void
|
|||||||
int
|
int
|
||||||
Emitter::AdvanceSeekVoltage(UpdateRecord *message)
|
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;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -417,9 +417,18 @@ inline int
|
|||||||
void
|
void
|
||||||
HeatSink_Step(Scalar time_slice) { HeatSinkSimulation(time_slice); }
|
HeatSink_Step(Scalar time_slice) { HeatSinkSimulation(time_slice); }
|
||||||
|
|
||||||
// FUN_004ad7d4 -- entity heat-model active flag.
|
// FUN_004ad7d4 -- the heat-model master switch [T1]: the owning
|
||||||
|
// BTPlayer's +0x260 experience flag (mech+0x190 playerLink), ON only
|
||||||
|
// for veteran/expert experience (issue #2 wiring; the old permissive
|
||||||
|
// `return True` stub ran the heat model for every tier). Routed
|
||||||
|
// through the complete-type bridge in btplayer.cpp (databinding rule:
|
||||||
|
// never raw-read player offsets). NULL player reads ON [T3].
|
||||||
Logical
|
Logical
|
||||||
HeatModelActive() { return True; }
|
HeatModelActive()
|
||||||
|
{
|
||||||
|
extern int BTPlayerExperienceHeatModelOn(void *owner_mech); // btplayer.cpp (FUN_004ad7d4)
|
||||||
|
return BTPlayerExperienceHeatModelOn(owner) ? True : False;
|
||||||
|
}
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Subsystem virtual overrides (slots on vtable @0050edc4)
|
// Subsystem virtual overrides (slots on vtable @0050edc4)
|
||||||
|
|||||||
@@ -366,13 +366,21 @@ void
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// @0x4ac9c8 -- IsDamaged: true (dead) when the owning mech's electrical bus is
|
// @0x4ac9c8 -- the NOVICE-experience lockout predicate (issue #2 [T1]):
|
||||||
// down -- *(*(*(owner+0xd0)+0x190)+0x274) == 0.
|
// *(*(*(sub+0xd0)+0x190)+0x274) == 0, i.e. owner mech -> playerLink ->
|
||||||
|
// BTPlayer experienceLevel == 0. NOT a damage/bus query (the old label), and
|
||||||
|
// the old body raw-read the binary offset chain off `owner` (already the
|
||||||
|
// Mech, so the extra +0xd0 hop was doubly wrong) -- the databinding trap.
|
||||||
|
// Routed through the complete-type bridge in btplayer.cpp. NOTE:
|
||||||
|
// HeatSink::IsDamaged (heat.hpp) deliberately shadows this with a
|
||||||
|
// simulationState check for the gauge tint (btl4gaug.cpp); the FUN_004ac9c8
|
||||||
|
// call sites in powersub.cpp call the bridge directly.
|
||||||
//
|
//
|
||||||
Logical
|
Logical
|
||||||
MechSubsystem::IsDamaged()
|
MechSubsystem::IsDamaged()
|
||||||
{
|
{
|
||||||
return (*(int *)(*(int *)(*(int *)((char *)owner + 0xd0) + 0x190) + 0x274) == 0);
|
extern int BTPlayerRoleLocksAdvanced(void *owner_mech); // btplayer.cpp (FUN_004ac9c8)
|
||||||
|
return BTPlayerRoleLocksAdvanced(owner) ? True : False;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ class Damage;
|
|||||||
virtual void
|
virtual void
|
||||||
TakeDamage(Damage &damage); // slot via this+0x24 (see DamageDelta)
|
TakeDamage(Damage &damage); // slot via this+0x24 (see DamageDelta)
|
||||||
Logical
|
Logical
|
||||||
IsDamaged(); // @0x4ac9c8 (bus-state query)
|
IsDamaged(); // @0x4ac9c8 -- NOVICE-experience lockout predicate (player+0x274 == 0), NOT a damage query (issue #2); see mechsub.cpp
|
||||||
|
|
||||||
// Critical-state readouts for ColorMapperCritical (CriticalConnection):
|
// Critical-state readouts for ColorMapperCritical (CriticalConnection):
|
||||||
// the raw simulation state (DefaultState=0 / Destroyed=1 / Exploding=2) and
|
// the raw simulation state (DefaultState=0 / Destroyed=1 / Exploding=2) and
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ void MissileLauncher::FireWeapon()
|
|||||||
// THE FIRING HEAT (task #9): heatCostToFire RAW into the launcher's own
|
// THE FIRING HEAT (task #9): heatCostToFire RAW into the launcher's own
|
||||||
// pendingHeat (pre-scaled 1e7-unit resources: SRM6 = 5e7, LRM15 = 6.5e7)
|
// pendingHeat (pre-scaled 1e7-unit resources: SRM6 = 5e7, LRM15 = 6.5e7)
|
||||||
// -- the binary's add @part_013.c:8744-8747, gated on the heatable check.
|
// -- the binary's add @part_013.c:8744-8747, gated on the heatable check.
|
||||||
if (HeatModelActive()) // FUN_004ad7d4 (heat.hpp; [T3] permissive)
|
if (HeatModelActive()) // FUN_004ad7d4 (heat.hpp -> player+0x260 experience gate; issue #2)
|
||||||
{
|
{
|
||||||
pendingHeat += heatCostToFire; // HeatSink @0x1C8, raw
|
pendingHeat += heatCostToFire; // HeatSink @0x1C8, raw
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,7 +312,17 @@ class Mech;
|
|||||||
Scalar OwnerVelocityY() const { return 0.0f; } // Mech velocity.y
|
Scalar OwnerVelocityY() const { return 0.0f; } // Mech velocity.y
|
||||||
Scalar OwnerMotionGain() const { return 0.0f; } // Mech +0x20C
|
Scalar OwnerMotionGain() const { return 0.0f; } // Mech +0x20C
|
||||||
Scalar OwnerMass() const { return 0.0f; } // *Mech +0x250
|
Scalar OwnerMass() const { return 0.0f; } // *Mech +0x250
|
||||||
Logical OwnerAdvancedDamage() const { return False; } // advanced-damage gate (keeps sim INERT)
|
// FUN_004ad7d4 [T1] -- the SAME player+0x260 heat-model experience gate
|
||||||
|
// as HeatSink::HeatModelActive() (the "advanced damage" name was a
|
||||||
|
// misread; the old `return False` stub kept movement heat off for
|
||||||
|
// every tier). Issue #2: routed through the btplayer.cpp bridge.
|
||||||
|
// (MyomersSimulation stays arithmetically inert until the Owner*
|
||||||
|
// accessors above are wired -- every heat term multiplies a 0.)
|
||||||
|
Logical OwnerAdvancedDamage() const
|
||||||
|
{
|
||||||
|
extern int BTPlayerExperienceHeatModelOn(void *owner_mech); // btplayer.cpp (FUN_004ad7d4)
|
||||||
|
return BTPlayerExperienceHeatModelOn(owner) ? True : False;
|
||||||
|
}
|
||||||
Scalar DamageStructureLevel() const{ return 0.0f; } // DamageZone +0x158
|
Scalar DamageStructureLevel() const{ return 0.0f; } // DamageZone +0x158
|
||||||
|
|
||||||
void MoverAttach(int index, Scalar *feed) { (void)index; (void)feed; } // JointedMover vtable +0x38 (no-op)
|
void MoverAttach(int index, Scalar *feed) { (void)index; (void)feed; } // JointedMover vtable +0x38 (no-op)
|
||||||
|
|||||||
@@ -449,20 +449,22 @@ void
|
|||||||
// @004b0efc -- pass the message to the HeatSink base; if the part is alive,
|
// @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.
|
// 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
|
Logical
|
||||||
PoweredSubsystem::HandleMessage(int message)
|
PoweredSubsystem::HandleMessage(int message)
|
||||||
{
|
{
|
||||||
HeatSink::HandleMessage(message); // FUN_004ad748
|
HeatSink::HandleMessage(message); // FUN_004ad748
|
||||||
|
|
||||||
// Raw @004b0efc reads *(int*)(*(owner+0x190) + 0x25c) -- the message-manager's
|
// CORRECTED (issue #2 [T1]): @004b0efc reads *(int*)(*(owner+0x190)+0x25c)
|
||||||
// short-event flag (NOT a Generator field). The manager is null in bring-up
|
// -- owner mech+0x190 is the PLAYER LINK, and +0x25c is the BTPlayer's
|
||||||
// (BT_GetMessageManager stub), so guard the deref; the real read resumes once
|
// "sim live" EXPERIENCE flag (0 only for novice), NOT a message-manager
|
||||||
// the SubsystemMessageManager (0xBD3) roster lookup is wired.
|
// field (the old misattribution left this path dead behind the null
|
||||||
SubsystemMessageManager *msgMgr = BT_GetMessageManager((Mech *)owner); // *(this[0x34]+0x190)
|
// BT_GetMessageManager stub). The leading FUN_004ac9c8==0 term is the
|
||||||
|
// not-novice predicate (BTPlayerRoleLocksAdvanced), not a damage query.
|
||||||
if (!IsDamaged() // FUN_004ac9c8 == 0
|
if (!BTPlayerRoleLocksAdvanced(owner) // FUN_004ac9c8 == 0 (not novice)
|
||||||
&& msgMgr != 0
|
&& BTPlayerExperienceSimLive(owner) != 0 // *(player+0x25c) != 0
|
||||||
&& *(LWord *)((char *)msgMgr + 0x25c) != 0 // message-manager short-event flag
|
|
||||||
&& message == 4)
|
&& message == 4)
|
||||||
{
|
{
|
||||||
ForceShortRecovery(); // FUN_004b11bc(this)
|
ForceShortRecovery(); // FUN_004b11bc(this)
|
||||||
@@ -477,10 +479,15 @@ Logical
|
|||||||
void
|
void
|
||||||
PoweredSubsystem::ForceShortRecovery() // internal helper for @004b0efc
|
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;
|
return;
|
||||||
Generator *source = (Generator *)voltageSource.Resolve(); // FUN_00417ab4(this+0x1d0)
|
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->stateAlarm.SetLevel(Generator::GeneratorShorted); // source+0x1fc -> 3
|
||||||
source->outputVoltage = 0.0f; // source+0x1f8 = 0
|
source->outputVoltage = 0.0f; // source+0x1f8 = 0
|
||||||
@@ -1086,7 +1093,8 @@ Logical
|
|||||||
{
|
{
|
||||||
if (message == 4)
|
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
|
stateAlarm.SetLevel(GeneratorShorted); // 3
|
||||||
shortTimer = 0.0f; // this[0x7e]
|
shortTimer = 0.0f; // this[0x7e]
|
||||||
@@ -1255,7 +1263,10 @@ Logical
|
|||||||
Generator *source = (watched != 0) // FUN_00417ab4(watched+0x1d0)
|
Generator *source = (watched != 0) // FUN_00417ab4(watched+0x1d0)
|
||||||
? (Generator *)((PoweredSubsystem *)watched)->ResolveVoltageSource()
|
? (Generator *)((PoweredSubsystem *)watched)->ResolveVoltageSource()
|
||||||
: 0;
|
: 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->stateAlarm.SetLevel(Generator::GeneratorShorted); // source+0x1fc -> 3
|
||||||
source->outputVoltage = 0.0f; // source+0x1f8 = 0
|
source->outputVoltage = 0.0f; // source+0x1f8 = 0
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
// FUN_004b9d00 MechWeapon::PrintState (slot 13 base)
|
// FUN_004b9d00 MechWeapon::PrintState (slot 13 base)
|
||||||
// FUN_004b9948 MechWeapon::GetMuzzlePoint FUN_004b9bdc MechWeapon::UpdateTargeting
|
// FUN_004b9948 MechWeapon::GetMuzzlePoint FUN_004b9bdc MechWeapon::UpdateTargeting
|
||||||
// FUN_004b9cbc MechWeapon::GetTargetPosition FUN_004b0d50 voltage helper
|
// FUN_004b9cbc MechWeapon::GetTargetPosition FUN_004b0d50 voltage helper
|
||||||
// FUN_004ad7d4 HeatableSubsystem present? FUN_00417ab4 Connection::Resolve
|
// FUN_004ad7d4 heat-model experience gate (player+0x260; HeatModelActive) FUN_00417ab4 Connection::Resolve
|
||||||
// FUN_0041bbd8 AlarmIndicator::SetLevel(n) FUN_0041db7c Damage init
|
// FUN_0041bbd8 AlarmIndicator::SetLevel(n) FUN_0041db7c Damage init
|
||||||
// FUN_004179d4 / FUN_004179f8 SharedData::Connection ctor/dtor
|
// FUN_004179d4 / FUN_004179f8 SharedData::Connection ctor/dtor
|
||||||
// FUN_004022d0 operator delete FUN_004215b0 resolve subsystem name->index
|
// FUN_004022d0 operator delete FUN_004215b0 resolve subsystem name->index
|
||||||
@@ -227,7 +227,13 @@ Subsystem *
|
|||||||
Logical
|
Logical
|
||||||
ProjectileWeapon::LiveFireEnabled() const
|
ProjectileWeapon::LiveFireEnabled() const
|
||||||
{
|
{
|
||||||
return True;
|
// CheckForJam @004bbfcc's early-out [T1]: the owning BTPlayer's +0x25c
|
||||||
|
// "sim live" experience flag (mech+0x190 playerLink) -- 0 only for NOVICE
|
||||||
|
// experience, whose ballistics never jam. Issue #2: the old permissive
|
||||||
|
// `return True` stub let every tier roll jams; routed through the
|
||||||
|
// complete-type bridge in btplayer.cpp (databinding rule).
|
||||||
|
extern int BTPlayerExperienceSimLive(void *owner_mech); // btplayer.cpp (player+0x25c)
|
||||||
|
return BTPlayerExperienceSimLive(owner) ? True : False;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -507,8 +513,9 @@ void
|
|||||||
// uniform random sample falls under the current jam probability:
|
// uniform random sample falls under the current jam probability:
|
||||||
// p = clamp(0.41 * currentTemperature / failureTemperature,
|
// p = clamp(0.41 * currentTemperature / failureTemperature,
|
||||||
// minJamChance, 1.0)
|
// minJamChance, 1.0)
|
||||||
// Gated on the weapon being heat-active (this+0x184) and the owning Mech's
|
// Gated on the weapon being heat-active (this+0x184) and the owning PLAYER's
|
||||||
// "live fire" flag (mech.controls+0x25c).
|
// "sim live" experience flag (mech+0x190 playerLink -> player+0x25c; novice
|
||||||
|
// never jams -- issue #2).
|
||||||
//
|
//
|
||||||
Logical
|
Logical
|
||||||
ProjectileWeapon::CheckForJam()
|
ProjectileWeapon::CheckForJam()
|
||||||
@@ -772,7 +779,7 @@ void
|
|||||||
// binary adds heatCostToFire RAW to the weapon's own pendingHeat --
|
// binary adds heatCostToFire RAW to the weapon's own pendingHeat --
|
||||||
// projectile resources author PRE-scaled 1e7-unit heat (AFC100 = 6.5e7)
|
// projectile resources author PRE-scaled 1e7-unit heat (AFC100 = 6.5e7)
|
||||||
// -- gated on the heatable check (part_013.c:8744-8747 idiom).
|
// -- gated on the heatable check (part_013.c:8744-8747 idiom).
|
||||||
if (HeatModelActive()) // FUN_004ad7d4 (heat.hpp; [T3] permissive)
|
if (HeatModelActive()) // FUN_004ad7d4 (heat.hpp -> player+0x260 experience gate; issue #2)
|
||||||
{
|
{
|
||||||
pendingHeat += heatCostToFire; // HeatSink @0x1C8, raw
|
pendingHeat += heatCostToFire; // HeatSink @0x1C8, raw
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,14 +148,20 @@
|
|||||||
// SetGraphicsDirty -> engine Simulation::ForceUpdate() (this+0x18)
|
// SetGraphicsDirty -> engine Simulation::ForceUpdate() (this+0x18)
|
||||||
// SetInstanceFlag -> engine Simulation::simulationFlags |= bits (this+0x28)
|
// SetInstanceFlag -> engine Simulation::simulationFlags |= bits (this+0x28)
|
||||||
// GetSegmentFlags() removed (the ctor gate now reads owner->simulationFlags).
|
// GetSegmentFlags() removed (the ctor gate now reads owner->simulationFlags).
|
||||||
// ControlsAllowLights(): no ported base member (the real read is the
|
// ControlsAllowLights() [T1]: the ToggleLamp @004b860c gate is the
|
||||||
// owner controls-mapper lightsEnabled via an unsafe raw owner-offset
|
// owning BTPlayer's +0x25c "sim live" EXPERIENCE flag (mech+0x190
|
||||||
// chain) -> return True for bring-up; the only consumer (ToggleLamp) is
|
// playerLink -> player+0x25c; the old "controls-mapper lightsEnabled"
|
||||||
// dormant. Faithful follow-up = a real Mech::ControlsAllowLights().
|
// reading was wrong) -- 0 only for NOVICE experience. Issue #2:
|
||||||
|
// routed through the complete-type bridge in btplayer.cpp
|
||||||
|
// (databinding rule: never raw-read player offsets).
|
||||||
Logical HostShutDown() const { return simulationState == 1; /* Destroyed */ }
|
Logical HostShutDown() const { return simulationState == 1; /* Destroyed */ }
|
||||||
int WatchedVoltageLevel() const { return watchdogAlarm.GetLevel(); } // @0x198
|
int WatchedVoltageLevel() const { return watchdogAlarm.GetLevel(); } // @0x198
|
||||||
int HeatStateLevel() const { return heatAlarm.GetLevel(); } // @0x140
|
int HeatStateLevel() const { return heatAlarm.GetLevel(); } // @0x140
|
||||||
Logical ControlsAllowLights() const { return True; } // bring-up (see above)
|
Logical ControlsAllowLights() const
|
||||||
|
{
|
||||||
|
extern int BTPlayerExperienceSimLive(void *owner_mech); // btplayer.cpp (player+0x25c)
|
||||||
|
return BTPlayerExperienceSimLive(owner) ? True : False;
|
||||||
|
}
|
||||||
void SetGraphicsDirty() { ForceUpdate(); } // this[0x18] |= 1
|
void SetGraphicsDirty() { ForceUpdate(); } // this[0x18] |= 1
|
||||||
void SetInstanceFlag(int bits) { simulationFlags |= bits; } // this[10] |= bits
|
void SetInstanceFlag(int bits) { simulationFlags |= bits; } // this[10] |= bits
|
||||||
// The "ToggleLamp" command arg is carried at message+0xC (decomp).
|
// The "ToggleLamp" command arg is carried at message+0xC (decomp).
|
||||||
|
|||||||
Reference in New Issue
Block a user