KB: land the EXPERIENCE-LEVELS decode from glass-cockpit (Cyd)

Brings Cyd's experience-level / simulation-mode research onto master (the
KB-decode portion of glass-cockpit 4e01e83; the bundled BT410 source
manifest is left for the full glass-cockpit merge).

- context/experience-levels.md (NEW): the egg per-pilot 'experience' field
  (novice/standard/veteran/expert) is the pod's SIMULATION-FIDELITY tier,
  decoded end-to-end. FUN_004c0bc8 reads btMission->experienceLevel(+0xe4)
  and fans it into the +0x25c/+0x260/+0x26c/+0x270/+0x274 flag block:
  +0x25c 'sim live' (novice lockout: jams/searchlight/powersub), +0x260 the
  HEAT-MODEL master switch (veteran+expert; FUN_004ad7d4), +0x274 raw level
  (FUN_004ac9c8 = ==0 novice predicate; the valve/advanced-cockpit lockout).
  4.0->4.10 drift: the viewscreen hunting-aid gate is GONE in 4.10 (HUD reads
  none of these flags), and movement heat is veteran+expert not expert-only.
- btplayer.cpp/.hpp: CORRECTED comments/labels -- the +0x25c..+0x274 block is
  seeded from btMission experienceLevel/advancedDamageOn, NOT the scenario
  role's returnFromDeath; the 'roleClassIndex/showKills' names are scoring-era
  mislabels. Code logic UNCHANGED (comment-only): the ctor still reads
  scenarioRole (STAND-IN, defaults 2 = veteran) pending the wiring task.
- Corrections swept into gauges-hud.md (ROOKIE->novice lockout),
  open-questions.md (player+0x260/0x274 semantics now PINNED),
  pod-hardware.md, subsystems.md, decomp-reference.md; CLAUDE.md router row.

No code-logic change -> no rebuild needed (comment + markdown only). The
runtime wiring (seed from BTMission::ExperienceLevel()) remains TODO.

Co-Authored-By: Cyd <cyd@falloutshelterarcade.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-18 23:20:51 -05:00
co-authored by Cyd Claude Opus 4.8
parent 171c993147
commit 0bcba26213
9 changed files with 216 additions and 54 deletions
+1
View File
@@ -67,6 +67,7 @@ precise than anything you can infer.
| Walking, gait, ground model, collision | `context/locomotion.md` |
| Subsystems, the factory, heat/weapons/power | `context/subsystems.md` |
| Damage zones, targeting, firing, death | `context/combat-damage.md` |
| Experience / simulation modes (novice/standard/veteran/expert), heat-model + novice gates | `context/experience-levels.md` |
| Rendering, LODs, materials, sky, shadows, beams | `context/rendering.md` |
| The death/respawn translocation warp (tsphere vortex) | `context/translocation-warp.md` |
| First-person cockpit canopy (*_cop) + the eyepoint camera | `context/cockpit-view.md` |
+15 -1
View File
@@ -113,7 +113,7 @@ valid for reading the decomp, NOT for our compiled layout (see [[reconstruction-
|---|---|---|
| +0x11c / +0x120 | `damageZoneCount` / `damageZones[]` | inherited Entity; Mech ctor populates the array |
| +0x124 / +0x128 | `subsystemCount` / `subsystemArray[]` | the [[subsystem-roster]] (NOT the segment table) |
| +0x190 | **owning `BTPlayer`** (`GetPlayerLink()`) | set by `FUN_0049f624` (mech↔player bind; also `player->playerVehicle(+0x1fc)=mech`). The valve guard (`FUN_004ac9c8 →player+0x274`) + Myomers gate (`FUN_004ad7d4 →player+0x260`) read the owning player's game-mode flags (NOT the messmgr @0x434). `MECH_OWNING_PLAYER`. |
| +0x190 | **owning `BTPlayer`** (`GetPlayerLink()`) | set by `FUN_0049f624` (mech↔player bind; also `player->playerVehicle(+0x1fc)=mech`). The valve guard (`FUN_004ac9c8 →player+0x274` = novice lockout) + heat-model gate (`FUN_004ad7d4 →player+0x260` = veteran/expert) read the owning player's EXPERIENCE flags — see [[experience-levels]] + the BTPlayer flag block below. `MECH_OWNING_PLAYER`. |
| +0x434 (word 0x10d) | `SubsystemMessageManager` cache | the 0xBD3 damage/explosion hub (factory @10142). ✅ task #7 (`afefaee`): `mech.hpp` names this `messageManager`; the `controlsMapper` mislabel is swept — the real mapper is roster slot 0. |
| +0x260 / +0x26c | `localOrigin` = { Point3D linearPosition; Quaternion angularPosition } | ORIGIN.h:15; position@+0x100 in some subsystem views |
| +0x300 | segment table | skeleton `EntitySegment`s (muzzle sites, joints) |
@@ -128,6 +128,20 @@ valid for reading the decomp, NOT for our compiled layout (see [[reconstruction-
`mech+0x100` (a subsystem-view of localOrigin.linearPosition): `.x@+0x100, .z@+0x108`. [T1]
### BTPlayer (0x294) experience/game-mode flag block (ctor @4c0bc8; see [[experience-levels]]) [T1]
Seeded on the master branch from `btMission(+0x1f8)->experienceLevel(+0xe4)` (egg `experience`,
parse @4d2f3e`BTMission+0xe4`) and `advancedDamageOn(+0xf0)`:
| Offset | Value by level (nov/std/vet/exp) | Verified readers |
|---|---|---|
| +0x25c | 0/1/1/1 — "sim live" (novice lockout) | jam roll @4bbfcc, searchlight ToggleLamp @4b860c, powersub handler @4b0efc, mech-ineffective eval part_012.c:9364 |
| +0x260 | 0/0/1/1 — **heat-model master switch** | `FUN_004ad7d4` → Myomers @4b8d18, Emitter, heat.cpp, mislanch, projweap heat |
| +0x264/+0x268 | = mission `advancedDamageOn` (both) | heat-family sim part_013.c:8757 |
| +0x26c | 0/1/1/0 | none located (supercharge-clamp hypothesis [T4]) |
| +0x270 | 0/1/1/1 | none located |
| +0x274 | raw level 0..3 | `FUN_004ac9c8` = (==0) novice predicate (valve guard), powersub @4b21d0 |
### Binary ATTRIBUTE TABLES (2026-07-16, walked from the image) [T1]
**Row format: 16-byte `{id, namePtr, memberOffset+1, 0}`** (MIND THE ALIGNMENT — an 8-off walk
+118
View File
@@ -0,0 +1,118 @@
---
id: experience-levels
title: "Experience levels / simulation modes — novice, standard, veteran, expert"
status: established
source_sections: "decomp part_013.c:10816-10871 (@4c0bc8), part_014.c:7830-7900 (@4d2f3e), FUN_004ad7d4/FUN_004ac9c8/FUN_004bbfcc; BTMSSN.HPP (survived); Tesla40_BT_manual.pdf pp.7,11-14,21,22,24"
related_topics: [subsystems, combat-damage, gauges-hud, pod-hardware, multiplayer, decomp-reference, open-questions]
key_terms: [master, replicant]
open_questions:
- "player+0x26c (std+vet only) and +0x270 (all but novice) consumers not located — 0x26c hypothesis: myomer supercharge clamp (manual p22) [T4]"
- "does a remote pilot's experience matter cross-pod? flags are computed only on the master branch of the BTPlayer ctor [T4]"
- "movement-heat magnitude veteran vs expert (4.0 manual says expert-only; 4.10 gate is vet+expert) [T4]"
---
# Experience levels / simulation modes
The egg's per-pilot `experience` field is the pod's **simulation-fidelity tier** — the thing the
1995 site operator set per player at sign-up. It is NOT the BAS/MID/ADV control mode (a free
in-cockpit choice, [[locomotion]]/[[pod-hardware]]) and NOT the scenario role (scoring modifiers).
Researched end-to-end 2026-07-18; this file supersedes the older "role display toggles" /
"ROOKIE-role lockout" readings (see Corrections below).
## The data path [T1]
1. **Egg**: pilot page key `experience` ∈ {`novice`,`standard`,`veteran`,`expert`}; required unless
`vehicle=camera`; unknown value = hard abort. Parse @4d2f3e (part_014.c:7830-7900, strings
@51e6f1); `tools/eggmodel.py` knows the value set.
2. **BTMission** (`BTMSSN.HPP` survived, T0): stored at `mission+0xe4` (`experienceLevel`, accessor
`ExperienceLevel()`); the neighboring egg key `advancedDamage``mission+0xf0`
(`advancedDamageOn`) — the separate site-technician splash/collision switch (manual p13).
3. **BTPlayer ctor @4c0bc8** (part_013.c:10816-10871): on the MASTER branch
(`(simulationFlags & 0xc) != 4`; the replicant branch instead swaps in the console-update
Performance) it reads `btMission(+0x1f8)->experienceLevel(+0xe4)` and fans it out:
| egg value | +0x25c | +0x260 | +0x26c | +0x270 | +0x274 (raw) |
|---|---|---|---|---|---|
| novice | 0 | 0 | 0 | 0 | 0 |
| standard | 1 | 0 | 1 | 1 | 1 |
| veteran | 1 | 1 | 1 | 1 | 2 |
| expert | 1 | 1 | 0 | 1 | 3 |
then unconditionally `+0x264 = +0x268 = mission->advancedDamageOn(+0xf0)` (both copies). [T1]
## What each flag gates (binary consumers, all reached via `mech+0x190` → BTPlayer) [T1]
- **+0x25c — "sim live", off only for novice.** Consumers: ballistic jam roll `CheckForJam`
@4bbfcc early-returns NO-JAM when 0 (projweap.cpp calls this `LiveFireEnabled`); Searchlight
`ToggleLamp` @4b860c (searchlight.cpp `ControlsAllowLights`); PoweredSubsystem message handler
@4b0efc (powersub.cpp's "message-manager short-event flag" — same flag, misattributed); the mech
combat-ineffective evaluation (part_012.c:9364: mech state ∈ {3,4} && novice is an extra OR-term
setting `mech+0x414`). The `#if 0` block in surviving PPC.CPP:63-95 (novice PPCs deal no damage)
shows the original intent of the tier.
- **+0x260 — the HEAT-MODEL master switch, ON for veteran+expert.** This is `FUN_004ad7d4`
(`*(*(sub+0xD0)+0x190)+0x260`), the single most-called gate in the subsystem family: Myomers
work→heat (movement heat, MyomersSimulation @4b8d18), Emitter/energy-weapon firing heat, missile
launch heat, the heat.cpp simulation, projectile-weapon heat, and the heat-scaled part of the jam
chance. Standard mode has NO heat consequences; that is authentic, not a port gap.
- **+0x274 — the raw level.** `FUN_004ac9c8` = (level == 0), the NOVICE lockout used as the
condenser-valve guard ([[gauges-hud]] task #13) and by the PoweredSubsystem handler @4b21d0
(not-novice gate). The port's `BTPlayerRoleLocksAdvanced` bridge implements this predicate
(correct behavior, wrong seeding — see Port state).
- **+0x264/+0x268 — advancedDamage copies.** Read in the heat-family sim (part_013.c:8757).
Independent of experience.
- **+0x26c / +0x270 — consumers NOT located** via the mech→player hop. 0x26c (standard+veteran
only) best fits the manual-p22 myomer supercharge clamp (standard locked, expert unlocked) [T4].
## Player-facing behavior (Tesla 4.0 manual) [T1 primary source]
Manual pp. 7, 11-14, 21, 22, 24 (the dedicated Simulation Modes page, manual p2, is MISSING from
the `Tesla40_BT_manual.pdf` scan along with p3):
- **Standard**: armor-only damage — no criticals, no generator outages, no jams, no heat concerns;
MechTech limited to the leg-damage audio warning; viewscreen hunting aids (red hot box + guide
arrow, 60° FOV) on; myomer supercharge locked.
- **Veteran**: critical hits (any hit can go internal; likelihood rises as armor thins; a crit can
kill the mech), generators take damage/go offline (reroute power), ballistic jams (manual
clearing), full MechTech audio + smart buttons; viewscreen hunting aids OFF (radar hot box stays).
- **Expert**: veteran + heat as a core mechanic: movement generates heat (accel/decel worst),
heat slows recharge/top speed, incoming fire causes coolant leaks, ammo-bay fires/cook-off;
myomer supercharge allowed.
- Scoring identical in all modes (p4). Splash/collision damage = a separate technician setting.
## 4.0 → 4.10 drift [T1]
- **The viewscreen hunting-aid gate is GONE in 4.10**: part_014 (HUD/btl4vid TU) reads none of the
player flags — the target hotbox/edge arrows (@4cdf6f) are gated on lock state only, every mode.
Fits the 4.10 arcade-cities positioning.
- **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.
## Port state (as of 2026-07-18) — wiring needed [T3]
The reconstruction met this system piecemeal and stubbed the same gates inconsistently:
- `btplayer.cpp` ctor seeds `roleClassIndex` from `scenarioRole->GetReturnFromDeath()` (default 2)
— the binary seeds it from `btMission->ExperienceLevel()`. Bring-up therefore runs as VETERAN.
Two slot errors in the port's switch vs the binary: the binary's switch never touches +0x264
(that's advancedDamage, set after), and expert's `0` lands on +0x26c, not +0x270.
- `HeatModelActive()` stubbed permissive `return 1` (emitter/heat/mislanch/projweap) while
`Myomers::OwnerAdvancedDamage()` returns `False` — the SAME binary gate (+0x260) answered both
ways: heat accrues from weapons but never from movement, and no experience below veteran can
switch it off.
- `LiveFireEnabled` / `ControlsAllowLights` / powersub's short-event flag = the +0x25c not-novice
flag under three names.
- **The fix**: one accessor family on BTPlayer named members (databinding trap — never raw-read
`player+0x260`), seeded from `BTMission::ExperienceLevel()` + `AdvancedDamageOn()`, with all five
stub sites pointed at it; then an egg `experience` line actually selects the sim tier as shipped.
## Corrections (swept 2026-07-18)
- `btplayer.hpp` "per-role display toggles from returnFromDeath" for 0x25c-0x274 was WRONG — the
source is `btMission+0xe4/+0xf0`, not the role; the `showKills`/`roleClassIndex` names date from
the scoring work and don't match the gate semantics (kept in code for now, comments corrected).
- `gauges-hud.md` "ROOKIE-role lockout" for FUN_004ac9c8 → novice-EXPERIENCE lockout.
- `open-questions.md` "true semantic of player+0x260/0x274 not yet pinned" → pinned here.
## Key Relationships
- Feeds: [[subsystems]] (the heat model + jam/valve gates) · [[combat-damage]] (criticals tier intent) · [[gauges-hud]] (valve guard)
- Sourced from: the egg ([[multiplayer]] egg format) · `BTMission` (BTMSSN.HPP, T0)
- Offsets: [[decomp-reference]] §3 (mech+0x190 row + BTPlayer flag block)
- Manual audit: [[pod-hardware]] §Manual
+5 -4
View File
@@ -150,10 +150,11 @@ moves. Diag: `BT_RADAR_LOG` ([radar] ctor probe + 1 Hz [radar-wedge] rot trace).
## Remaining = DATA FEEDS, not widgets (deferred systems)
**✅ The condenser valve CONTROL is LIVE (task #13, 2026-07-11) [T2]:** `MoveValve` (id 4, the
Condenser handler table @0x50E52C — exactly one entry) registered + guarded by the REAL
FUN_004ac9c8 = `player+0x274 roleClassIndex == 0` (the ROOKIE-role lockout; task #12's
`BTPlayerRoleLocksAdvanced` bridge — the old "game-mode flag / likely off in a basic mission"
hedge is superseded: bring-up role = 2 = UNLOCKED, verified live: press → valveState 1→5 →
flow redistribution 1/6 → 5/10). Desktop: 'C' cycles the selected condenser (BT_VALVE_SLOT).
FUN_004ac9c8 = `player+0x274 == 0` (2026-07-18 correction: +0x274 = the egg EXPERIENCE level,
so this is the NOVICE lockout, not a "ROOKIE role" — see [[experience-levels]]; task #12's
`BTPlayerRoleLocksAdvanced` bridge; bring-up seeding = 2 ≈ veteran = UNLOCKED, verified live:
press → valveState 1→5 → flow redistribution 1/6 → 5/10). Desktop: 'C' cycles the selected
condenser (BT_VALVE_SLOT).
The **MessageBoard** feed needs **StatusMessagePool** (a NULL stub) + the per-player status
queue. `SeekVoltageGraph`'s 4 Seek* attrs are a cluster-child (not config-called). [T2]
+28 -21
View File
@@ -151,7 +151,8 @@ 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,
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
stands in). Also flags a deferred **`Mech::ControlsAllowLights()`** (searchlight.hpp:158 stub).
stands in). Also flags a deferred **`Mech::ControlsAllowLights()`** (searchlight.hpp:158 stub;
2026-07-18: identified as the `player+0x25c` not-novice experience flag — [[experience-levels]]).
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
post-roster loops add to `heatableSubsystems`(0x51155c)/`weaponRoster`(0x511830)/
@@ -187,20 +188,24 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
- **✅ `mech+0x190` IDENTIFIED (2026-07): it is the owning `BTPlayer`** (`Mech::GetPlayerLink()`,
`MECH_OWNING_PLAYER`; ENTITY.h:430). Set by `FUN_0049f624` (the mech↔player bind: `mech+0x190 = player`
AND `player->playerVehicle(+0x1fc) = mech`), which resolves the player from the mission player registry
(`app+0x2c+0x54`, `FUN_0041fd18`) by the pilot key. The valve/Myomers "gates" read the owning player's
fields at `player+0x260` and `player+0x274` (the reconstruction maps this block as `showDamageReceived@0x25c
/ showKills@0x260 / showDamageInflicted@0x264 / roleClassIndex@0x274` in btplayer.hpp — from the SCORING
work, so the names may NOT match the gate semantics; the true meaning + the WRITER of `player+0x260/0x274`
are not yet pinned — part_013.c:4553-4660 is the TorsoSimulation, a different object, not the setter). So
there is NO new subsystem to build. **The wiring (small):** point `MechSubsystem::IsDamaged()` (`FUN_004ac9c8`, valve
guard) at `GetPlayerLink()->`(0x274) and `Myomers::OwnerAdvancedDamage()` (`FUN_004ad7d4`) at
`GetPlayerLink()->`(0x260) — via NAMED members (databinding trap: our BTPlayer layout ≠ binary; do NOT
raw-read `player+0x260`). ⚠ Resolve first: (a) a naming conflict — `FUN_004ad7d4` is labeled BOTH
`HeatModelActive` (heat.hpp) AND `OwnerAdvancedDamage` (myomers.cpp); re-verify which body is which; (b)
the true semantic of `player+0x260/0x274` vs the scoring-derived `showKills`/`roleClassIndex` names. NOTE:
these are MODE flags — in the basic test mission the inert Myomers / dormant valve is likely AUTHENTIC
(advanced damage off); wiring makes them RESPOND when a mission enables the mode, not necessarily change
the basic-mission behavior. The MessageBoard feed is separate (StatusMessagePool, below).
(`app+0x2c+0x54`, `FUN_0041fd18`) by the pilot key.
**✅ FLAG SEMANTICS PINNED (2026-07-18) [T1]: the `player+0x25c..0x274` block = the EXPERIENCE
LEVEL (egg `experience` novice/standard/veteran/expert), written by the BTPlayer ctor @4c0bc8
from `btMission(+0x1f8)->experienceLevel(+0xe4)` — full mapping, consumers, and the 4.0-manual
behavior in [[experience-levels]] (+ [[decomp-reference]] §3 flag table).** Resolved en route: the
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`
reads `player+0x274` = the novice lockout; the btplayer.hpp `showKills`/`roleClassIndex` scoring
names indeed do NOT match the gate semantics (comments corrected in place, member names kept).
**The wiring (small, still open):** ONE accessor family on BTPlayer named members seeded from
`BTMission::ExperienceLevel()`/`AdvancedDamageOn()` (databinding trap: do NOT raw-read
`player+0x260`), then point the five stub sites at it (`HeatModelActive` permissive-1 in
emitter/heat/mislanch/projweap vs `OwnerAdvancedDamage` False in myomers — currently the SAME gate
answered both ways; `LiveFireEnabled`/`ControlsAllowLights`/powersub short-event = the +0x25c
flag under three names; ctor seeding from role->returnFromDeath is a bring-up stand-in that runs
every mission as VETERAN). NOTE: these are MODE flags — an egg with `experience=standard` should
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`
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) →
@@ -352,17 +357,19 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
tables must be function-local statics INSIDE the accessor (cross-TU static-init
order emptied the table -- see [[reconstruction-gotchas]] #9 last bullet); (b)
**FUN_004ac9c8 is NOT IsDamaged** -- raw body: `owner(+0xD0) -> mech+0x190 player
-> roleClassIndex(+0x274) == 0` = the ROOKIE-role lockout for advanced cockpit
systems (port bridge BTPlayerRoleLocksAdvanced, btplayer.cpp; NULL player =
unlocked [T3]; bring-up role defaults to 2 = unlocked). AUDIT TAIL: the other
-> +0x274 == 0` = the NOVICE-experience lockout for advanced cockpit systems
(2026-07-18 correction: +0x274 = the egg experience level, NOT a role class --
see [[experience-levels]]; port bridge BTPlayerRoleLocksAdvanced, btplayer.cpp;
NULL player = unlocked [T3]; bring-up seeding defaults to 2 ≈ veteran = unlocked).
AUDIT TAIL: the other
powersub.cpp sites annotated FUN_004ac9c8 (the coolant-draw gate ~:427,
ForceShortRecovery ~:444, the Generator/PowerWatcher site ~:1085) still call the
heat-family IsDamaged (simulationState != 0) stand-in -- each needs its raw fn
re-checked and swapped to the role bridge where the binary calls 4ac9c8.
- **Myomers authentic coupling** — the structural un-stub is INERT (mover feed + heat-gen no-op).
Real coupling needs the advanced-damage gate (`OwnerAdvancedDamage`/`FUN_004ad7d4` → the owning
**BTPlayer** `mech+0x190`+0x260, NOT 0xBD3 — see the mech+0x190 item above) + `MoverAttach` routing
into the LIVE JointedMover (must be reconciled with the gait cutover first).
Real coupling needs the heat-model gate (`OwnerAdvancedDamage`/`FUN_004ad7d4` → the owning
**BTPlayer** `mech+0x190`+0x260 = veteran/expert experience, NOT 0xBD3 — see [[experience-levels]])
+ `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).
- **✅ DAMAGE ECONOMY — AUTHENTIC (task #8, 2026-07-11) [T1/T2].** The whole economy closes:
+4 -1
View File
@@ -154,7 +154,10 @@ every checked control behavior:
button 8 → ChooseNearestPilot); radar = travel-oriented, 60° vision wedge sweeps with twist,
red hot box + callsign on contacts; **map zoom ± buttons** flank the secondary screen.
- **Experience gates the hunting aids**: viewscreen hot box + hunting-guide arrow appear in
STANDARD sim mode only (not veteran/expert); expert mode adds movement heat.
STANDARD sim mode only (not veteran/expert); expert mode adds movement heat. (2026-07-18 [T1]:
4.0→4.10 drift — in OUR 4.10 binary the HUD reads NO experience flag (hotbox/arrows are
lock-gated in every mode) and movement heat is veteran+expert; full wiring in
[[experience-levels]].)
- Cockpit: 5 MFDs (Coolant UL, Engineering top, Hot Box UR, Weapon ×2) + secondary screen with
side button columns + **EJECT button** beside the joystick + per-mech COOLANT LOOP tables
(weapons+generators per loop 1-6) and stat sheets (tonnage/armor/reservoir liters/heat
+2 -2
View File
@@ -57,8 +57,8 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER
- **✅ DONE (task #7, `afefaee`)** — SubsystemMessageManager 0xBD3 (a
**damage/explosion consolidation hub** cached to `Mech[0x10d]`=0x434; the factory builds the REAL
class and `mech.hpp` names the slot `messageManager` — the old `controlsMapper` mislabel is swept,
the real mapper is roster slot 0. NOT the valve/advanced-damage gate — that's the owning **BTPlayer**
@mech+0x190, see [[combat-damage]]/[[open-questions]]). [T2]
the real mapper is roster slot 0. NOT the valve/heat-model gate — that's the owning **BTPlayer**
@mech+0x190 carrying the EXPERIENCE-level flags, see [[experience-levels]]). [T2]
## The watcher electrical chain (task #57, 2026-07-13 — the torso power gate)
The Watcher branch is POWERED indirectly: a watcher WATCHES another roster subsystem and mirrors
+28 -16
View File
@@ -1303,12 +1303,21 @@ BTPlayer::BTPlayer(
else
{
//
// Master: choose the HUD display toggles from the role class index.
// Master: derive the game-mode flags from the EXPERIENCE level.
//
// TODO(bring-up): scenarioRole is NULL here because the role lookup above
// (btMission->GetRoleRegistry()->Lookup) is still stubbed out. Guard the deref
// so mission load proceeds; default index 2 = show all stats (freeforall).
roleClassIndex = scenarioRole ? scenarioRole->GetReturnFromDeath() : 2; // this[0x9d] <- role+0xe4
// 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.
//
// 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)
{
case 0:
@@ -1327,9 +1336,11 @@ BTPlayer::BTPlayer(
break;
}
// role+0xf0 ("returnDelay") has no field in the WinTesla ScenarioRole;
// initialised to 0 here. CROSS-FAMILY -- see report. BEST-EFFORT.
roleReturnDelay = roleReturnDelay2 = 0.0f; // this[0x99],[0x9a] <- role+0xf0
// CORRECTED (2026-07-18 [T1]): the binary sets 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
}
killCount = 0; // this[0x9f]
@@ -1589,14 +1600,15 @@ void BTPostKillScore(Entity *victim, Scalar damage) // Step 7: KILL (+ MP deat
//
// FUN_004ac9c8 [T1]: `return *(*(*(sub+0xD0) + 0x190) + 0x274) == 0` -- the
// subsystem's owner Mech -> the owning BTPlayer (mech+0x190, GetPlayerLink)
// -> roleClassIndex (+0x274, role resource +0xE4). TRUE = the ROOKIE role
// (class 0) LOCKS the advanced cockpit systems (generator routing, coolant
// valves); nonzero role classes unlock them. The old reconstruction
// mislabeled this fn "Subsystem::IsDamaged" -- with healthy subsystems at
// simulationState==1, that stand-in gated the power-routing handlers OFF
// permanently. A NULL player (the target dummy / unbound solo mech) would AV
// in the binary (every pod mech has a player); the port reads NULL as
// UNLOCKED so dev rigs work [T3].
// -> +0x274 = the raw EXPERIENCE level (egg "experience", mission+0xe4; the
// old "roleClassIndex / role resource" reading was wrong -- see
// context/experience-levels.md). TRUE = NOVICE experience LOCKS the advanced
// cockpit systems (generator routing, coolant valves); standard and above
// unlock them. The old reconstruction mislabeled this fn
// "Subsystem::IsDamaged" -- with healthy subsystems at simulationState==1,
// that stand-in gated the power-routing handlers OFF permanently. A NULL
// player (the target dummy / unbound solo mech) would AV in the binary (every
// pod mech has a player); the port reads NULL as UNLOCKED so dev rigs work [T3].
//
int BTPlayerRoleLocksAdvanced(void *owner_mech)
{
+15 -9
View File
@@ -356,23 +356,29 @@ class DropZone__ReplyMessage;
suppressConsole; // @0x258 skip console notify for this score event
//
// Per-role display toggles, selected from the role's "returnFromDeath"
// class index (scenarioRole resource +0xe4, values 0..3) at @004c0bc8.
// EXPERIENCE-LEVEL game-mode flags (2026-07-18 [T1] — the old "per-role
// display toggles / returnFromDeath" reading was WRONG). The ctor
// @004c0bc8 seeds this block from btMission(+0x1f8)->experienceLevel
// (+0xe4, the egg's per-pilot "experience" novice/standard/veteran/
// expert) and ->advancedDamageOn(+0xf0) — NOT from the scenario role.
// The member NAMES below date from the scoring work and do not match
// the semantics; kept until the wiring task renames them. Binary
// mapping (nov/std/vet/exp) + consumers: context/experience-levels.md.
//
Logical
showDamageReceived; // @0x25c
showDamageReceived; // @0x25c 0/1/1/1 "sim live" (novice lockout: jams, searchlight, powersub @4b0efc)
Logical
showKills; // @0x260
showKills; // @0x260 0/0/1/1 HEAT-MODEL master switch (FUN_004ad7d4)
Logical
showDamageInflicted;// @0x264
showDamageInflicted;// @0x264 = mission advancedDamageOn (copy 1; NOT part of the level switch)
Logical
showScore; // @0x270
showScore; // @0x270 0/1/1/1 consumer not located
Scalar
roleReturnDelay; // @0x268 (role resource +0xf0)
roleReturnDelay; // @0x268 = mission advancedDamageOn (copy 2; int in the binary)
Scalar
roleReturnDelay2; // @0x26c
roleReturnDelay2; // @0x26c 0/1/1/0 consumer not located (supercharge-clamp hypothesis [T4])
int
roleClassIndex; // @0x274 (role resource +0xe4)
roleClassIndex; // @0x274 raw experience level 0..3 (FUN_004ac9c8 = ==0 novice predicate)
friend int BTPlayerRoleLocksAdvanced(void *); // the FUN_004ac9c8 bridge (task #12)
friend void BTPlayerCountObservedDeath(void *); // observed-death tally (MP DEATHS fix)