From 0bcba2621346b54d1c1c42ea6d85a31ca0ed4c3a Mon Sep 17 00:00:00 2001 From: arcattack Date: Sat, 18 Jul 2026 23:20:51 -0500 Subject: [PATCH] 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 Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 1 + context/decomp-reference.md | 16 ++++- context/experience-levels.md | 118 ++++++++++++++++++++++++++++++++ context/gauges-hud.md | 9 +-- context/open-questions.md | 49 +++++++------ context/pod-hardware.md | 5 +- context/subsystems.md | 4 +- game/reconstructed/btplayer.cpp | 44 +++++++----- game/reconstructed/btplayer.hpp | 24 ++++--- 9 files changed, 216 insertions(+), 54 deletions(-) create mode 100644 context/experience-levels.md diff --git a/CLAUDE.md b/CLAUDE.md index b8ae2ea..6831605 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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` | diff --git a/context/decomp-reference.md b/context/decomp-reference.md index d613d78..3da8294 100644 --- a/context/decomp-reference.md +++ b/context/decomp-reference.md @@ -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 diff --git a/context/experience-levels.md b/context/experience-levels.md new file mode 100644 index 0000000..ac0e208 --- /dev/null +++ b/context/experience-levels.md @@ -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 diff --git a/context/gauges-hud.md b/context/gauges-hud.md index f64f9fa..eee5d7e 100644 --- a/context/gauges-hud.md +++ b/context/gauges-hud.md @@ -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] diff --git a/context/open-questions.md b/context/open-questions.md index 094f01b..2baea80 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -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: diff --git a/context/pod-hardware.md b/context/pod-hardware.md index 501cab5..98117d6 100644 --- a/context/pod-hardware.md +++ b/context/pod-hardware.md @@ -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 diff --git a/context/subsystems.md b/context/subsystems.md index b1962fb..ed0abaa 100644 --- a/context/subsystems.md +++ b/context/subsystems.md @@ -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 diff --git a/game/reconstructed/btplayer.cpp b/game/reconstructed/btplayer.cpp index 8a82d46..96b8240 100644 --- a/game/reconstructed/btplayer.cpp +++ b/game/reconstructed/btplayer.cpp @@ -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) { diff --git a/game/reconstructed/btplayer.hpp b/game/reconstructed/btplayer.hpp index 1f2a7d0..548c292 100644 --- a/game/reconstructed/btplayer.hpp +++ b/game/reconstructed/btplayer.hpp @@ -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)