diff --git a/CLAUDE.md b/CLAUDE.md index d2f2b63..60680ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ > detail is preserved verbatim in **`docs/PROGRESS_LOG.md`** (the old monolithic CLAUDE.md) — the > ultimate fallback if a topic digest is thin. Migration history: `phases/phase-01-context-restructure.md`. -**Project:** Port VWE's 1995 arcade **BattleTech** (BT) pod game (MUNGA engine, Tesla platform +**Project:** Port VWE's arcade **BattleTech** (BT) pod game (MUNGA engine, Tesla platform 4.10) to modern Windows + the pod hardware, by reconstructing the missing BT game logic from the `BTL4OPT.EXE` binary on top of the working WinTesla engine. **Repo of record:** the top-level `CMakeLists.txt` + `README.md` build `btl4.exe`. Layout: @@ -24,13 +24,13 @@ POLISH list (#156 fade, weapon-state presentation). Details + what's-next: ## YOU ARE THE BATTLETECH PORT EXPERT -**You (Claude) are the expert on this port** — the 1995 MUNGA/BT engine, its asset formats, the +**You (Claude) are the expert on this port** — the original MUNGA/BT engine, its asset formats, the decompilation, and the reconstruction state. The `context/*.md` files, `reference/glossary.yaml`, `reference/decomp/` (the raw pseudocode), and the `docs/*.md` ledgers are YOUR knowledge base. **ALWAYS consult the context system before answering a question about the engine, formats, a subsystem, or the reconstruction state — and before writing reconstruction code.** Do not rely on -training data; this is a specific, reverse-engineered 1995 codebase. The knowledge here is more +training data; this is a specific, reverse-engineered 1990s codebase. The knowledge here is more precise than anything you can infer. ### How to answer a question @@ -93,7 +93,7 @@ precise than anything you can infer. | Offsets, ClassIDs, addresses, resource types, env vars | `context/decomp-reference.md` | | What we don't know / deferred systems | `context/open-questions.md` | | Term / acronym definitions | `reference/glossary.yaml` | -| The ORIGINAL 1995 player manual (controls, per-mech stats, coolant loops) | `reference/manual/Tesla40_BT_manual.pdf` (+ alignment audit in `context/pod-hardware.md` §Manual) | +| The ORIGINAL player manual (controls, per-mech stats, coolant loops) | `reference/manual/Tesla40_BT_manual.pdf` (+ alignment audit in `context/pod-hardware.md` §Manual) | | The complete verbatim detail (fallback) | `docs/PROGRESS_LOG.md` (the old 2236-line CLAUDE.md) | | Detailed running ledgers | `docs/RECONCILE.md`, `docs/GAUGE_COMPOSITE.md`, `docs/HARD_PROBLEMS.md`, `docs/SUBSYS_PLAN.md`, `docs/P3_LOCOMOTION.md`, `docs/RESOURCE_AUDIT.md`, `docs/VEHICLE_SUBSYSTEMS.md`, `docs/BGF_FORMAT.md`, `docs/ASSET_PIPELINE.md`, `docs/BT_SOURCE_STATUS.md`, `docs/WAVE_PLAN.md`, `docs/GLASS_COCKPIT.md`, `docs/REVOLVING_DOOR_PLAN.md`, `docs/INPUT_PATH_AUDIT.md`, `docs/RESPAWN_REARM_PLAN.md`, `docs/KD_SCOREBOARD_PLAN.md`, `docs/DIST_LAYOUT_PLAN.md`, `docs/MOUSELOOK_PLAN.md`, `docs/GHOST_MECH_ANALYSIS.md`, `docs/AUDIO_FIDELITY.md`; multi-agent audit ledgers (2026-08-11): `docs/KD_CHAIN_AUDIT_20260811.md`, `docs/MISSION_END_AUDIT_20260811.md`, `docs/RAM_LEAK_AUDIT_20260811.md`, `docs/GEN_WARNING_AUDIT_20260811.md` | @@ -119,7 +119,7 @@ Tag individual claims (not sections) inline: `[T1]`, `[T2]`, … A claim inherit --- ## Conventions (CRITICAL — full detail in `context/reconstruction-gotchas.md`) -- **Never raw-read a compiled object's offsets** (`*(T*)(obj+0xNN)`) — our layout ≠ the 1995 binary. +- **Never raw-read a compiled object's offsets** (`*(T*)(obj+0xNN)`) — our layout ≠ the original binary. Use named members / accessors / a **bridge** fn in a complete-type TU. (databinding trap) - **Never re-declare an engine-base field** — it shadows the base (reads `0xCDCDCDCD`) + mis-offsets. - **`/FORCE` hides unresolved symbols** — an unresolved external → runtime AV near `__ImageBase`, not diff --git a/context/bgf-format.md b/context/bgf-format.md index 829f81f..b3028c9 100644 --- a/context/bgf-format.md +++ b/context/bgf-format.md @@ -101,7 +101,7 @@ One shared 128×64 texmap: `bmap:` → `VIDEO/MAT/BMAP.BMF` → 6 `TEXTURE` reco `LoadBitSliceTexture` merges a 1bpp egg BitMap into it; `FlushBitSliceTexture` (L4VIDEO.cpp:5682, stubbed in 2007) did `dpl_LookupTexture("bmap:bmap1_tex")` + `dpl_TexmapTexels2D(texmap, buf, 128, 64, 4)` — one upload rewrites ALL SIX slices. So the -1995 engine painted the **egg's callsign + ordinal bitmaps** onto these plates every mission; +original engine painted the **egg's callsign + ordinal bitmaps** onto these plates every mission; the baked "PLAYER n" text is only the shipped DEFAULT. Two stacked 128×32 large-name bitmaps per slice = exactly the PNAME v-halves; two stacked 128×32 ordinal bitmaps ("1st|2nd") = exactly the PLACE 2×2 grid. Corollaries: diff --git a/context/build-and-run.md b/context/build-and-run.md index cb27d75..5f63b6a 100644 --- a/context/build-and-run.md +++ b/context/build-and-run.md @@ -86,7 +86,7 @@ run\run.cmd [EGG] # default DEV.EGG; cd's to content\ and runs btl4.exe -eg chain end including a normal quit — it primes players to report quits as crashes; #157 ("did not exit cleanly") was exactly this echo. The arcade-authentic round-end (mission process exits → menu relaunch) itself reads as a crash to first-night players. -- **Why this is a BT411-only hazard [T1].** The 1995 pod shipped ONE folder — `BTL4OPT.EXE` sits +- **Why this is a BT411-only hazard [T1].** The original pod shipped ONE folder — `BTL4OPT.EXE` sits next to `BTL4.RES`/`VIDEO\`/`GAUGE\`/`AUDIO\` (it is still there in `content\`) — and RP411/RP412 keep that shape (`pack-dist.ps1` copies the exe and every asset dir into one dist root). BT411's two-folder split (`build\Release\btl4.exe` + `content\`) is not a design decision: `mkdist.py` @@ -97,7 +97,7 @@ run\run.cmd [EGG] # default DEV.EGG; cd's to content\ and runs btl4.exe -eg authors.** The cwd guard stays either way (it protects the developer tree, where the split is real and permanent). - Interactive: ⚠ under the GLASS profile (`content\bindings.txt`, the default since the cyd - merge) drive is the **1995 throttle-lever scheme**, NOT WASD: **SHIFT** = throttle up (lever + merge) drive is the **original throttle-lever scheme**, NOT WASD: **SHIFT** = throttle up (lever STICKS where you leave it), **CTRL** = throttle down, **ALT** = reverse thrust, **X** = all-stop, **BACKTICK** = view toggle. ("WASD drive" was the pre-glass dev profile — telling a glass user W is the confirmed way to watch a mech stand perfectly still, 2026-07-29.) @@ -183,7 +183,7 @@ stamps `build=4.11. ()`, so an archived PDB matches a tester's log exac - **`tools/`** — btconsole.py, disas2.py, map/res scanners. **`context/`** — this knowledge base. ## Versioning (2026-07-18) -`4.10` = the 1995 arcade release; `4.11` = this win32 reconstruction; dev builds = +`4.10` = the arcade release; `4.11` = this win32 reconstruction; dev builds = **`4.11.` + short hash**, `+` suffix = built from an uncommitted tree (e.g. `4.11.311 (980c9cd+)`). Stamped every build by `tools/btversion.cmake` → `build/btversion.h` (`BT_VERSION_*` macros); shown in the boot banner (day-log head) and diff --git a/context/combat-damage.md b/context/combat-damage.md index f65c824..9d12ef0 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -53,7 +53,7 @@ section). Counting the enum gives `ThermalSightClassID = 0xBD9` and `TurretClass (0xBD6) and Searchlight (0xBD8) but disagrees on MechTech (enum 0xBDD vs CLASSMAP 0xBDC), so the port's `VDATA.h` ordering has drifted from the shipped binary somewhere in this range. CLASSMAP is binary-derived and wins for what the runtime does; the true numeric value of `TurretClassID` in the -1995 binary is **unknown**. Resolve by disassembling the ClassID each ctor stores before relying on it. +original binary is **unknown**. Resolve by disassembling the ClassID each ctor stores before relying on it. ## No AI — BT is PvP-only (verified, not a recovery gap) The mission system is NETWORKED and BT never had NPCs/AI. Every mech is driven by a `BTPlayer` (or a @@ -85,10 +85,10 @@ unlocated** [T4 candidates]: the un-exported stretch `0x4a1674-0x4a2d48` (touche together near entry `0x4a2971`, which is called from `0x494483`), the two unidentified Mech vtable overrides `+0x18=0x4a122c` / `+0x1c=0x4a0c2c` (both big switch functions), and lone sites `0x4877a4` (byte read via the mission entity table `0x5015c8`), `0x4b0097`, `0x45fb14-24`. -**RESOLVED (same dig, deeper): there IS no software pick writer — the 1995 pick was a DPL SCENE +**RESOLVED (same dig, deeper): there IS no software pick writer — the original pick was a DPL SCENE INTERSECTION** [T0 + T1 converging]. Evidence: (a) the WinTesla renderer still carries the result members `dplHitInstance/dplHitDCS/dplHitGeoGroup/dplHitGeometry` + `vehicleReticle` (L4VIDEO.cpp -ctor, all NULL-init); (b) the stubbed 1995-era renderable constructors each took +ctor, all NULL-init); (b) the stubbed original-era renderable constructors each took **`dpl_isect_mode_obj` ("type of intersections to do on this object") + an intersection MASK** — per-renderable intersection configuration against the scene; (c) Auric's account ("the pod's division card cast from the view"); (d) VGL Lynx's LOD warning reads as firsthand knowledge — the @@ -97,7 +97,7 @@ binary or the pseudocode ever writes `rayIntersection/targetEntity/targetDamageZ only reads, initializes (the Mech ctor @0x4a1674 constructs the embedded Reticle), gates (`FUN_004afd10`, the look-state machine, toggles `pickPointingOn` per view with the π rear case), and SUBMITS the reticle to the board for drawing (`FUN_00460a7c` packages +0x37c/+0x390 into dpl). -⇒ **Aimed fire in 1995 had PER-PART precision**: scene ray → struck triangle on the current LOD → +⇒ **Aimed fire in the original had PER-PART precision**: scene ray → struck triangle on the current LOD → the DCS = the struck SEGMENT → its `dzone` (the SKL segment→zone map, `GetSegmentIndex@49db20`) = `targetDamageZone`. The cylinder lottery (STEP 6) was only ever the UNAIMED path. The port's whole-mech box pick + "STEP-6 zone under the boresight" funnels AIMED fire through the unaimed @@ -127,7 +127,7 @@ patch CENTROIDS so the zone walker can exercise per-panel. Bench: [[test-harnes drawn geometry -- the any-object SPHERE fallback and the caller's whole-mech AABB fallback are gone (sphere only for a mesh the reader cannot parse -- none exist, counters clean; AABB only as the pre-tree replicant grace, `BTMechSegmentPick` returns 1/0/-1). They were port stand-ins the -1995 card never had, and their signature regime was a LEVEL boresight over a SHORT mech: the +original card never had, and their signature regime was a LEVEL boresight over a SHORT mech: the blackhawk's mesh tops out below eye-ray height, so rays cleared every triangle while threading the fat cull spheres -- lock ring lit with the reticle above its head (night-12 field report, operator-witnessed on the sweep bench). Instruments: `BT_LOCK_SWEEP` (torso-pan lock envelope, @@ -156,7 +156,7 @@ stale). **Selection is SPECIFICITY-FIRST: among pierced spheres the SMALLEST rad mesh's sphere (r≈4.1 on the MadCat vs shoulders r≈1.0) envelops the mech, so nearest-entry always faces you AND normalized distance rewards the giant sphere (a 0.45u-off-axis ray scores 0.012 vs it). Limbs nest inside the envelope; smallest-pierced picks the most specific part on the aim line -— the per-part semantic the 1995 mesh intersection produced. `mech4.cpp` tries the segment pick +— the per-part semantic the original mesh intersection produced. `mech4.cpp` tries the segment pick per candidate (box `PickRayHit` + zone −1 → the unaimed lottery survives only as fallback: no tree / wrecked / structure occlusion), and the winner's zone rides `MECH_TARGET_SUBIDX` + `targetReticle.targetDamageZone` into `SendDamageMessage` — aimed hits now dispatch a REAL zone. @@ -333,7 +333,7 @@ an enclosing box), no crash. The interactive boresight (`BTGetAimRay`) can't be ## Firing arc + muzzles There is NO firing-arc field in the binary (`Emitter::FireWeapon` fires whenever HasActiveTarget) — -the 1995 game got away with it because it was **cockpit-only** (a beam behind you is off-camera). The +the original game got away with it because it was **cockpit-only** (a beam behind you is off-camera). The port's external camera exposes it, so the arc is gated on the **torso twist range** (`Torso::GetHorizontalReach`, 0 for the twist-disabled Blackhawk) + a base aim tolerance (`BT_FIRE_ARC`, a labeled PORT presentation param). Beams fire from the mech's real gun-port SITE @@ -549,13 +549,13 @@ measuring a PORT bug. The truth [T1, raw disasm @0x4ac7bb + live-verified]: own error strings). Consequences: `MechSubsystem::TakeDamage @0x4ac0bc` (now real, was a stub) accumulates authored per-type damage on subsystems; crits land and destroy subsystems; and **the Myomers un-powered -self-repair `@004b8bb9` was LIVE in 1995 and is live in the port now** — the earlier "dead code in +self-repair `@004b8bb9` was LIVE in the original and is live in the port now** — the earlier "dead code in the original too" note in [[subsystems]] WAVE 6 is superseded. `DistributeCriticalHit`'s direct `damageLevel = 1.0f` pin remains the ammo-explosion path, not the general mechanism. ## ⚠ ZONE SELECTION IS A WEIGHTED LOTTERY — pixel-precise limb damage does not exist (2026-07-29) [T1] Issue #73 ("fired only at the left arm, damage credited elsewhere, no crits") is **largely the -authored 1995 model, not a bug**. The chain (`dmgtable.cpp`, binary-faithful, byte-verified stream +authored original model, not a bug**. The chain (`dmgtable.cpp`, binary-faithful, byte-verified stream format): impact point → `WorldToLocal` → **height layer** (`floor(layerCount·y/heightRef)`) → **pie slice** (`atan2(local.z, local.x)`, optionally rotated by live torso twist — `rotateWithTorso` per layer) → `DamageZonePercentTable::SelectZone()` = **`RandomUnit()` against @@ -616,8 +616,8 @@ btplayer.hpp, static_assert-locked; `scoreAward`=APPLIED tally in all three): `killCount++`: **the #134 panic penalty, live** (bench: `type=2 award=-39.00 kills=0`). - **B @0x4a05d9, not newly killed && tally≠0** → type 0 to the shooter: the **per-hit INFLICTED credit**, and it is LIVE. ⚠ **CORRECTED 2026-08-07** — this entry previously read "the ONLY - registered 0x16 receiver Verify-rejects type 0 → 1995 folded an UNINITIALIZED stack float into - the shooter's score on every non-lethal hit (real 1995 bug — @0x4c0200 … is in NO table entry: + registered 0x16 receiver Verify-rejects type 0 → the original folded an UNINITIALIZED stack float into + the shooter's score on every non-lethal hit (real original-binary bug — @0x4c0200 … is in NO table entry: dead code)". **That was wrong**, and build 787 retired the port's per-hit crediting on the strength of it (the scoring regression players reported on 4.11.817). `BTPlayer` overrides **`Dispatch` — vtable `@00513300` slot 3 = `FUN_004bffa0`** — and splits type 0 off BEFORE base @@ -636,7 +636,7 @@ btplayer.hpp, static_assert-locked; `scoreAward`=APPLIED tally in all three): Reports carry the LOOP-ENTRY zone (msg+0x24, never rewritten mid-loop), the vital-wreck flag, and `inflictingSubsystemID` (msg+0x5c, engine T0 name). -**Score model consequence** [T1, ⚠ REWRITTEN 2026-08-07]: 1995 pod scoring = **per-hit inflicted +**Score model consequence** [T1, ⚠ REWRITTEN 2026-08-07]: original pod scoring = **per-hit inflicted credit + kill awards + received-damage penalties + death costs**. The previous text here claimed "No per-hit inflicted credit … inventions riding the dead @0x4c0200 channel; both retired" — that followed from the dead-code misreading corrected in report B above, and retiring the credit IS the @@ -1235,10 +1235,10 @@ code involved; grammar = the binary ctors @0x49ea48/@0x49e5e4). Results: W1|W2 SEAM. (Corrects the earlier #92 comment that put frontal hits in W6.) * **⚠ PORT FRAME ADAPTER (#124, fixed 2026-08-03; corrected same day by the MUZZLE-ANCHOR probe)**: that +Z-forward (and +X-right, from the W0/W7 - "Right*" names) is the **1995 resolver frame**. OUR engine frame, measured + "Right*" names) is the **original resolver frame**. OUR engine frame, measured with the mech's own asymmetric geometry as the anchor (`BT_ASPECT_TEST`: four world-cardinal self-impacts + one at each weapon's physical muzzle): - **RIGHT = +X, same as 1995; FORWARD = −Z, flipped** — the LEFT missile pod + **RIGHT = +X, same as the original; FORWARD = −Z, flipped** — the LEFT missile pod sits at raw local x=−2.32, the RIGHT at +2.32, and `WorldToLocal` is a clean R(yaw) with no hidden terms (verified at yaws −30°/150°/40°). The frames differ by a **Z-NEGATION ONLY — a reflection** (the first commit's π @@ -1268,7 +1268,7 @@ the per-frame UpdateDeathState and a same-frame direct+splash lethal pair re-ran block (duplicate kill credit + duplicate VehicleDead) — caught by the 4-node bench, fixed by running the once-per-death transition inline at the death tail (1b6d247). Full class write-up: [[reconstruction-gotchas]] §25; stress verification: kd4_bench 9/9/9/9 + kd8_stress 26/26/26/26, -SWALLOWED=0 both. The receiver-side dedup remains a TRIPWIRE only (1995 has none; +0x290 never +SWALLOWED=0 both. The receiver-side dedup remains a TRIPWIRE only (the original has none; +0x290 never written) — if it ever fires again, a new duplicate source exists upstream. ## Key Relationships @@ -1278,7 +1278,7 @@ written) — if it ever fires again, a new duplicate source exists upstream. ## The K/D-score AUTHORITY MODEL -- settled once and for all (2026-08-11, #162) [T1] -The 1995 chain has ONE emitter and NO receiver dedup; every counter is single-owner. +The original chain has ONE emitter and NO receiver dedup; every counter is single-owner. Fixed twice piecemeal (#150 kills, #162 deaths) before the full audit settled it -- do not touch any single limb of this chain again without re-reading this section. @@ -1307,7 +1307,7 @@ part_013.c:10519) -- the #81-era port put it in the VehicleDead arm by misattrib disarming the port's defensive latch (corrected 2026-08-11). **Replication:** the player update record carries currentScore(+0x1c8) + -dropZoneLocation ONLY. killCount/deaths never cross the wire in 1995; each node's +dropZoneLocation ONLY. killCount/deaths never cross the wire in the original; each node's scoreboard columns are locally accumulated (the port's SBMIRROR is a deliberate deviation that makes boards consistent). diff --git a/context/decomp-reference.md b/context/decomp-reference.md index bed77e1..53ec35d 100644 --- a/context/decomp-reference.md +++ b/context/decomp-reference.md @@ -299,7 +299,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a Score@0x4c02e4** (the type-1/2 switch — BT shadows the engine id with its override), **0x17 VehicleDead@0x4c05c4, 0x18 MissionStarting@0x4bfbe8, 0x19 MissionEnding@0x4bfc20, 0x1a ScoreUpdate@0x4c02a8** (= Player::NextMessageID). **@0x4c0200 ("ScoreInflicted", type-0-only, - Verify line 0x18b) is in NO entry — dead code in 1995.** The 1995 type-0 path therefore lands in + Verify line 0x18b) is in NO entry — dead code in the original.** The original type-0 path therefore lands in @0x4c02e4's Verify arm and folds an UNINITIALIZED [ebp-0xc] into currentScore (real bug; port banks 0). @0x4c02e4 internals [T1 raw]: `+0x40==4` MissionEnding gate; registry-find msg+0x34; case 1 = `(ownTonnage/senderTonnage) × role->CalcDamageReceivedScore(basis)` + the @@ -329,7 +329,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a call to engine Player::ScoreMessageHandler @0x42da20 (never dispatched, never on the wire — no matchlog receipt exists; the port's `[deathcost]` DEBUG line is the only witness). **deathPending(+0x290) is NEVER written non-zero anywhere in BTL4OPT.EXE** (three writes, - all zero-stores: ctor @0x4c0c07, respawn helper @0x4c01e5, DropZoneReply tail) — 1995 has NO + all zero-stores: ctor @0x4c0c07, respawn helper @0x4c01e5, DropZoneReply tail) — the original has NO receiver-side death dedup; the sender's one-shot edge is the whole guarantee. The clear pair `{+0x290=0, +0x258=0}` lives in **FUN_004c012c = DropZoneReply**'s tail (part_013.c:10519-23) — NOT the VehicleDead arm (the #81-era port misattribution, corrected 2026-08-11). Latch @@ -342,7 +342,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a tail; gotcha §25). `advancedDamageOn(+0x264)` ← the mission egg's per-pilot `advancedDamage=` key (every stock egg stamps 1 on every page); Player update record replicates ONLY currentScore(+0x1c8) + dropZoneLocation — killCount/deaths never - cross the 1995 wire (the port's #45 record extension mirrors them owner→replicant; benched + cross the original wire (the port's #45 record extension mirrors them owner→replicant; benched convergent at 4 and 8 nodes, kd4_bench/kd8_stress). - **HeatableSubsystem** @0x50E41C: {3, "ToggleCooling"→@004ad6f8}. **Disassembled 2026-07-20** (`tools/disas2.py 0x4ad6f8`): a per-subsystem coolant on/off TOGGLE (NOT a multi-level "cycle @@ -360,7 +360,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a GeneratorA-D — unreconstructed). - **Searchlight** @0x51117C: {3, "ToggleLamp"→**@004b838c**}; **ThermalSight** @0x51120C: {3, "ToggleLamp"→**@004b860c**}. ⚠ **CORRECTED 2026-07-25 (#61)** — these were swapped here (and in - `searchlight.cpp:51`, `hud.cpp:282`), which propagated a false "1995 latent bug" across four + `searchlight.cpp:51`, `hud.cpp:282`), which propagated a false "original latent bug" across four context files; see the WAVE-4 note in [[subsystems]]. The two TUs emit PARALLEL shared-data blocks with identical stride — msg entry, +0x5C attribute entries, +0x84 Performance triple: | TU | msg entry | handler | attributes | Performance | @@ -500,7 +500,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a warning; respawn-with-tripped-gen RTIS (4,0) FIRES it (ghost warning during respawn); crit-death fires enter-Idle one frame late; 4→1 (cooled while switched off) double-fires. The generators' inv+Start is the ONLY inverse-Start fault - trigger in the game (census) — a 1995 authoring slip [T4 intent]. Port machinery + trigger in the game (census) — an original authoring slip [T4 intent]. Port machinery is faithful end-to-end. **DELIBERATE DEVIATION shipped 2026-08-11 (user-approved):** the AudioStateTrigger ctor (AUDWTHR.cpp) flips the GeneratorState (trigState=4, inv=1) record to inv=0 at stream time — warning now fires at the TRIP, silent at @@ -560,7 +560,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a with `scoreAward = −ScenarioRole::specialCaseDeathPenalty` (role+0x20, the 4.10-only key; restored to SCNROLE.h/.cpp — record slot [1] after KillBonus, fail-hard read). Direct call, NOT dispatched — bypasses the BT handlers' type Verifies. NUANCE [T4]: - the 1995 engine adds at Player+0x1c8 while every BT scoreboard reads +0x278 — the + the original engine adds at Player+0x1c8 while every BT scoreboard reads +0x278 — the pod's death cost may never have displayed; our single-cell port shows it. Shipped content authors NO role keys, so the cost is 0 in the field. **CORRECTED 2026-08-07 — that sentence was wrong on both halves.** The role's scoring fields are not authored via diff --git a/context/experience-levels.md b/context/experience-levels.md index edc75b4..470b800 100644 --- a/context/experience-levels.md +++ b/context/experience-levels.md @@ -14,7 +14,7 @@ open_questions: # 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 +original 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). diff --git a/context/gauges-hud.md b/context/gauges-hud.md index 20cfb92..4bfd10d 100644 --- a/context/gauges-hud.md +++ b/context/gauges-hud.md @@ -333,7 +333,7 @@ screen paint over the shared Eng plane, pinning it on the highest screen). Pixel lists SCORING players (flag cleared when the mech links its player, mech.cpp:688, all nodes). Both loops now use `Player::IsScoringPlayer()`; also closes a `pilotIDs[1]` heap overrun the undercount exposed. -2. **Latch race**: the binary's build-once latch assumes 1995 synchronous pod loads; over +2. **Latch race**: the binary's build-once latch assumes the original's synchronous pod loads; over async relay TCP the faster node ticks InterpretControls before the peer's replicants arrive (rig-proven: staggered node logged roster 1 → then peer arrival). [T3 accommodation, demand-latch precedent]: rebuild whenever the scoring CENSUS changes — @@ -641,7 +641,7 @@ and every instrument is now live [T2]:** (class key 0x46) = the engine's **`CameraShipHUDRenderable`**, the camera/broadcast seat's followed-name banner + ranking window, which uses PNAME **and** PLACE1-8 as sorted `[ordinal][callsign]` rows and is **already live** as screen quads (see [[multiplayer]] camera - seat). Its 1995 source is likewise commented out in `L4VIDRND.cpp:2562-2876` — the literal + seat). Its original source is likewise commented out in `L4VIDRND.cpp:2562-2876` — the literal `dpl_*` calls, a Rosetta stone for the binary's node API. - **Simple-X mode** (PrimaryHudOn off, mask 0x20): the minimal reticle — a small green cross (±0.02..0.08 arms) riding the aim translate (ctor @4689-4705 [T1]) — swapped for the full HUD @@ -674,7 +674,7 @@ BITMAPS from the egg, not text — format, renderers, and the operator-console g **The gauge blit addresses SOURCE rows bottom-up, and the vertical strip artwork is authored to match.** `Video16BitBuffered::DrawBitMapOpaque` converts the incoming source rect with `sTop = map_max_y - sTop; sBottom = map_max_y - sBottom` and walks rows upward -(`engine/MUNGA_L4/L4VB16.cpp:3846-3850`, `UP_BITMAP` :2155) [T0]; the 1995 display blit +(`engine/MUNGA_L4/L4VB16.cpp:3846-3850`, `UP_BITMAP` :2155) [T0]; the original display blit @0046bdfc performs the IDENTICAL flip [T1 disasm], and the PCC→BitMap loader stores the PCX top scanline at memory row 0 unflipped (`GRAPH2D.cpp:27-174`). Net: in `OneOfSeveral::Execute` `sy0 = row*frameHeight` selects frames counting from the **BOTTOM** of the image as authored. @@ -780,7 +780,7 @@ Or/Xor ignore the mask entirely and And clears foreign planes -- all audited): entry=0xffffff00 mask=0x3f`, at exactly the artifact positions in the operator's screenshot. FIX: zero the table in the ctor + cycle the in-plane pattern across entries [2^bits..255] (high-index art degrades to its -index-mod-2^bits colour IN-PLANE, can never leak). The 1995 binary ships the +index-mod-2^bits colour IN-PLANE, can never leak). The original binary ships the SAME 64-entry fill and relied on 6-bit art discipline -- garbage is not a preservable behaviour, so the cycle-fill is a guarded PORT deviation. diff --git a/context/glass-cockpit.md b/context/glass-cockpit.md index 53e75a2..cd3ca00 100644 --- a/context/glass-cockpit.md +++ b/context/glass-cockpit.md @@ -387,10 +387,10 @@ It lands on BT's addresses better than it deserves to: `1-4`+`QWER` are the ENTI (Condensers 1-6, flush, balance), `F6`/`F7` the display/control-mode cycles, `F9`-`F12` Generators A-D, `F4` the crouch button. -⚠ **What it costs, deliberately.** A bound key is removed from the authentic 1995 typed-hotkey +⚠ **What it costs, deliberately.** A bound key is removed from the authentic original typed-hotkey channel to stop double-dispatch, and this board binds nearly everything — so `5` (Quad page), `z` (Eng1), `t/y/u/i/o` (pilot select) and `+`/`-` (target zoom) are given up. Unbind a key to get its -1995 meaning back. +original meaning back. ⚠ **New rule: a bindings row WINS over a built-in convenience key** (`PadRIO::KeyHasBinding`). `V` and `J/K/L` are board buttons now, so the view-toggle and preset-cycle polls stand down for @@ -518,7 +518,7 @@ assigned axes excluded), derives invert from the move direction (throttle from F position), auto-adds the hat look cluster, and rewrites bindings.txt between `# >>> BT_JOYCONFIG` markers (rest of the file preserved). Sign conventions: JoystickX + = twist right, JoystickY + = aim up (wizard binds flight-style forward=down via invert), Turn + -= right. Legacy `L4DINPUT.cpp DIJoystick` (L4CONTROLS=DIJOYSTICK, 1995 `Joystick` iface) += right. Legacy `L4DINPUT.cpp DIJoystick` (L4CONTROLS=DIJOYSTICK, legacy `Joystick` iface) left untouched. Verified: grammar accept/reject by line, no-device + XInput-only polling clean 30 s in-mission, zero DI cost with no joy rows. NOT yet verified: real stick axes (no hardware here) — needs a tester with a stick. @@ -590,7 +590,7 @@ copied from them. (right-click = hold latch) — the merged default profile lives in `L4PADBINDINGS.cpp` (auto-writes `content\bindings.txt`; W/S/A/D/Q/E/R/F/X drive, 1-4/Space/Ctrl fire, M/N/H/C = pod buttons 0x18/0x15/0x2C/0x2F, `/V view toggle + J/K/L preset cycles hardcoded in PadRIO). - Bound keys are SUPPRESSED from the typed 1995 channel (`PadRIO::SuppressKey`, the btinput + Bound keys are SUPPRESSED from the typed original channel (`PadRIO::SuppressKey`, the btinput pattern — ended the glass double-dispatch: 'w'=pilot-select, numpad/F-key key-up aliases); unbound keys keep their authentic meanings (5/z presets, t-o pilot select, +/- zoom). In BAS control mode the pod turns with the STICK (Q/E) and pedals are inactive — authentic; @@ -654,7 +654,7 @@ copied from them. streamed Eng-page `.CTL` dispatches subsystem msg **id 3 / 0xb** to whatever subsystem is shown, and a **weapon (Emitter)** registers only PoweredSubsystem 4-8 + MechWeapon 9-10 — so id 3 is a gap below `entryCount`, and `Receiver::MessageHandlerSet::Build`'s `new HandlerEntry[]` left it - as heap garbage → `(this->*garbage)(msg)`. The 1995 binary has the identical non-zeroing + as heap garbage → `(this->*garbage)(msg)`. The original binary has the identical non-zeroing `new[]` and survived only on fresh-heap-zero luck (id 3 on a weapon was always meant to be IGNORED — it's a Condenser/Reservoir action in a different subsystem branch; the uniform Eng-page button template makes buttons for unimplemented actions authentically inert). The new diff --git a/context/locomotion.md b/context/locomotion.md index 0f93e67..922d09a 100644 --- a/context/locomotion.md +++ b/context/locomotion.md @@ -185,7 +185,7 @@ Declared-separate members don't see each other's writes — alias them. Replicants (task #50): travel = engine DeadReckon; leg channel driven joints-only from the replicated worldLinearVelocity -> speedDemand (mech4.cpp REPLICANT MOTION) — detail in [[multiplayer]]. [T2] -## The AUTHENTIC 1995 ground model (default-on, `BT_GROUND_REAL`) +## The AUTHENTIC original ground model (default-on, `BT_GROUND_REAL`) Decoded by the 10-agent `ground-model-decode` workflow (binary `FUN_004a9b5c` = the master perf). **NO GRAVITY anywhere** — accelerations zeroed every frame. Vertical position = an ABSOLUTE per-frame ground SNAP: @@ -462,7 +462,7 @@ trap never fired while the value changed) resets the sim cell between damage eve 1 Hz bench harness it restarted the warning before the 1.8 s voice note; sporadic real-play damage is unaffected. Needs a cdb write-watchpoint dig (candidates: a recon raw +0x2c-family write or a struct copy spanning it). -### The gimp cell is REPLICATED STATE — peers limped for free in 1995 (#82, fixed 2026-07-29) +### The gimp cell is REPLICATED STATE — peers limped for free in the original (#82, fixed 2026-07-29) Field: "peers see a limping mech **skating**" (first-person limp + voice correct; bystanders saw a normal walk sliding at limp speed; a stop/start did not clear it). Root cause chain, all [T1/T2]: - The binary's one `mech+0x40` **is** `Simulation::simulationState`, which diff --git a/context/multiplayer.md b/context/multiplayer.md index 9bf7cf7..b4789ac 100644 --- a/context/multiplayer.md +++ b/context/multiplayer.md @@ -357,7 +357,7 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`. — the relay fans out server-side (kills the mesh's N−1× duplication at the seam where `EntityBroadcastToReplicants` already serialized once). Verified: relay `tcp rx==tx`, one copy per broadcast. -- **UDP channel = the 1995 NETNUB reliable/unreliable split RESTORED (not invented)**: +- **UDP channel = the original NETNUB reliable/unreliable split RESTORED (not invented)**: `Receiver::Message` defaults `messageFlags=ReliableFlag` (RECEIVER.h:195); the ~60Hz update path sets flags=0 (ENTITY.cpp:590); `Mode(UnreliableMode)` at LoadingMission (APP.cpp:704, the 2007 port's `Mode()` was a no-op — now STORES the mode). The routing gate is @@ -397,7 +397,7 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`. SEAT_REQUEST branch (btconsole.py). Verified: 8/8 stub tests (distinct seats, FULL on exhaustion, claim clears reserve, duplicate-HELLO refused) + 2-node localhost e2e with NO BT_SELF on either pod → both seated, full ladder, launch, UDP flowing. -- **WALK-UP CALLSIGN/MECH REQUEST (2026-07-22) [T2]**: the 1995 front-desk conversation, over +- **WALK-UP CALLSIGN/MECH REQUEST (2026-07-22) [T2]**: the original front-desk conversation, over the wire. join.bat/join_lan.bat set `BT_FE_JOIN=1` and launch ZERO-ARG -> the FE menu in JOIN trim (btl4fe.cpp `FeJoinOnly()`: CALLSIGN edit + the 18-mech list + JOIN; screenshot-verified) -> `BTFeLaunchJoinRelay` relaunches with the universal join args + `BT_CALLSIGN`/`BT_MECH` env @@ -438,7 +438,7 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`. default case `Fail()`ed = abort = a SILENT crash (c0000409, no WER record; cdb stack: ucrtbase!abort <- RunMissionMessageHandler <- RoutePacket; 3/3 reproducible in the console-shaped run, and the field report's 464 crash died mid-LOD-load with the same - signature). The 1995 console never hit this state (operator-paced launches). FIX + signature). The original console never hit this state (operator-paced launches). FIX (L4APP.cpp): the default case now DEFERS -- re-posts the message to itself at +1s (the VehicleDead re-post pattern) until the ladder reaches WaitingForLaunch; bounded at 120 deferrals so a wedged load still surfaces the original Fail. `[launch] ... deferred +1s` @@ -513,9 +513,9 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`. is the console pad -- the game socket doesn't exist yet, so the first fix's RelayGameDown hook could never fire (caught by the verification itself). Detection now lives in `HostDisconnectedMessageHandler` case ConsoleHostType (relayMode && !scene-presented -> - `BTRelayRejoinNow`); mesh keeps 1995 console-less behavior. Verified 2x in one run. + `BTRelayRejoinNow`); mesh keeps the original console-less behavior. Verified 2x in one run. 2. **Post-egg pre-mission relay loss**: RelayGameDown -> rejoin (the original fix). ✓ - 3. **Mid-mission relay loss = forever-mission**: the STOP is relay-sent (1995: console owns + 3. **Mid-mission relay loss = forever-mission**: the STOP is relay-sent (original design: console owns the clock), so a pod losing its relay mid-match played alone forever. Now RelayGameDown (scene presented) posts `StopMissionMessage` at +15s -> normal end path -> lobby relaunch -> patient wait. Verified: scene LIVE -> relay killed -> 15s -> clean RunMissions exit. @@ -598,14 +598,14 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`. drives the pod countdown + the final-30s ranking window; the relay now SENDS `Application::StopMissionMessage` (clientID 4, msgID 6, exitCode 0) at length+2s grace, or on the operator's ⏹ END MISSION button (stdin `stop`). Was half-implemented — nothing fired it in - 1995 the console owned the stop. Pod runs the authentic fade → mission-loop exit → clean + the original — the console owned the stop. Pod runs the authentic fade → mission-loop exit → clean process exit. Verified live (40s test mission ended on the clock). > **The console/relay now has its own topic: [[operator-console]]** (`context/operator-console.md`) > — which of the two programs is which, ports, the seat model, the full round/launch lifecycle and > its wedge states, plus liveness. Read that first for anything console-side; the notes below are > the historical record of how it got built. Sysop how-to: `docs/OPERATOR_GUIDE.md`. -- **OPERATOR CONSOLE (2026-07-18)**: `tools/btoperator.py` (PySide6) — the lost 1995 operator +- **OPERATOR CONSOLE (2026-07-18)**: `tools/btoperator.py` (PySide6) — the lost original operator station recreated: mission editor with ALL values validated live from BTL4.RES (`tools/eggmodel.py`: maps=type14∩26, colors/badges/patches from the type-25 vehicletable, vehicles from ModelLists), relay/mesh mode switch, live pod-status lights parsed from the @@ -637,7 +637,7 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`. — the egg's `[mission] length=` (600 = the 10-min pod mission) drives the pod-side countdown (`secondsRemainingInGame`, APP.cpp:652) and the ranking-window display for the final 30s (DIRECTOR.cpp:113), and the full end chain exists (StopMission id 6 → EndingMission → player - fade → Stop) — but NOTHING fired it: in 1995 the CONSOLE sent StopMission at expiry (mission + fade → Stop) — but NOTHING fired it: in the original the CONSOLE sent StopMission at expiry (mission length enforcement is console-side). Now reconstructed: the relay arms the clock at RunMission #2 and sends `Application::StopMissionMessage` (clientID 4, msgID 6, +int exitCode 0) at length+2s grace; the operator app gets an ⏹ END MISSION button (stdin `stop`, like `launch`). @@ -713,7 +713,7 @@ carousel, Phase 2 = true drop-in). The durable engine facts found: existing ROSTER host to OnLine whenever it connects — arrival order/timing is free (this is how slow loaders already work), and the relay's PEER_UP exchange is bidirectional for newcomers (btconsole.py:1202-1209). -- **Late ENTITY sync was never finished in 1995**: entity MakeMessages are broadcast ONCE at +- **Late ENTITY sync was never finished in the original**: entity MakeMessages are broadcast ONCE at creation and never retained. `InterestManager::NotifyOfEntityCreation` (INTEREST.cpp:694-763 → DynamicEntityCreation :839+) sends immediately; the interest-zone bookkeeping is `#if 0 "HACK - Partial implementation"`; and `HostManager::NotifyOfReplacementEntityCreation` @@ -769,7 +769,7 @@ transition, HUD all landed since P6): console egg → mesh → RunningMission on deathPending); without one → engine base @0042db80 (PLAYER.cpp:214: closest non-"win" DropZone → AssignDropZone → 2s re-post probe) → DropZoneReply → CreatePlayerVehicle (a NEW mech; `deathCount = 0`) + viewpoint relink (Verify(viewpointEntity==NULL) is compiled out at - DEBUG_LEVEL 0 — release overwrites, same as the 1995 build). @0049fe0c = roster-wide + DEBUG_LEVEL 0 — release overwrites, same as the original build). @0049fe0c = roster-wide slot-11 DeathShutdown broadcast (the death transition already runs that pass). CORRECTIONS to the earlier scouting: BTL4.RES **type 12 = InternalAudioStream** (the engine's own resource-type table) — `*int.scp` are internal-AUDIO scripts, NOT per-mech intro scripts; @@ -895,7 +895,7 @@ transition, HUD all landed since P6): console egg → mesh → RunningMission on ## Authentic coupled peer motion — DONE (task #50, 2026-07-14) [T1] -Decomp-verified (workflows w0odszxro/wh1h5gnmc, 3 make-or-break claims adversarially CONFIRMED; committed c52a1ad + default-promoted). The 1995 peer per-frame path FUN_004ab430 -> FUN_004ab1c8 (IntegrateMotion) drives motion SINGLE-SOURCE: +Decomp-verified (workflows w0odszxro/wh1h5gnmc, 3 make-or-break claims adversarially CONFIRMED; committed c52a1ad + default-promoted). The original peer per-frame path FUN_004ab430 -> FUN_004ab1c8 (IntegrateMotion) drives motion SINGLE-SOURCE: - LINEAR position = GAIT. IntegrateMotion advances the BODY gait channel FUN_004a5678 (FUN_004a5028 is DEAD CODE, zero call sites), turns its cycleDistance into velocity `{0,0,-cd/dt}`, rotates by heading, integrates into projectedOrigin.linear@0x260 (part_012.c:14994/14997/15008); localOrigin.linear@0x100 is copied VERBATIM from it (FUN_00408440, part_012.c:15131 -- NOT a lerp). updateVelocity.linear is never used for position. - ANGULAR/heading = replicated-velocity slerp (the ONLY thing Mover::DeadReckon-style velocity extrapolation drives): projectedOrigin.angular@0x26c built from updateVelocity.angular@0x2d4 (FUN_004ab188), localOrigin.angular slerped toward it. - 0x260 aliasing: "motionDelta@0x260" and "projectedOrigin@0x260" are LITERALLY THE SAME field (base Mover::projectedOrigin, Origin 0x1c); "worldPose@0x26c" is its quaternion half. Our reconstruction's shadow names hid this and drove the false velocity-vs-gait "contradiction". @@ -913,7 +913,7 @@ The scoring authority model (full map: [[combat-damage]] + `docs/KD_CHAIN_AUDIT_ the VICTIM's master emits everything on its death edge (kill credit to the shooter via replicant-Dispatch reroute, received penalty to itself, one VehicleDead); the killer's master increments `killCount` when the rerouted credit lands; **killCount/deathTally never crossed the -1995 wire** — the port's #45 update-record extension mirrors them one-way owner→replicant +original wire** — the port's #45 update-record extension mirrors them one-way owner→replicant (read guarded replicant-only, mixed-version length guard, 2s heartbeat re-dirty). Bystander nodes learn tallies ONLY through that mirror. Verified: `kd4_bench.sh` (bystander convergence + no-mirror-exceeds-authority, receipt-derived per player) and `kd8_stress.sh` (8-node all-vs-all diff --git a/context/open-questions.md b/context/open-questions.md index c327a9b..97ce143 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -29,7 +29,7 @@ authentic path scoped. (`FUN_0048d4d4` = "flush_material", dpl command opcode 100 — siblings flush_ramp/flush_texture). The card-side shading (how material colour selected/scaled the 256-row material ramps → final texels) is i860 firmware, NOT in BTL4OPT.EXE — so the PERCEPTUAL damage-darkening curve of the - 1995 pods cannot be recovered from this binary. Our port applies the host's linear factor at + original pods cannot be recovered from this binary. Our port applies the host's linear factor at draw (TFACTOR modulate, L4D3D). Field testimony ("a single missile leaves visible armor damage") is arithmetically consistent with the linear curve — one 25-pt hit snaps a 68-90-pt zone by 0.28-0.37 (measured live: amt=25 → rarm 0→0.3247) — but if the card's ramp mapping was @@ -64,7 +64,7 @@ authentic path scoped. relay session — replicated player links (`bmp=1+veh`), director locks the goal (30s timer), camera ship tracks the moving mech (`sees=1`) and CUTS between authored cameras (screenshots). Egg: pilot page `hostType=1` + `vehicle=camera`. **RANKING WINDOW LIVE (same day)**: the stubbed - 1995 overlay reimplemented as screen-space quads in `CameraShipHUDRenderable::Render` — the + original overlay reimplemented as screen-space quads in `CameraShipHUDRenderable::Render` — the followed player's callsign banner (bottom center, the Execute index is ALREADY 0-based) + a rank-ordered row per scoring player ([ordinal][callsign], right of center), visibility = the Director's flash logic (10s on/15s off, solid final 30s), live `playerRank` pointers re-sort @@ -162,7 +162,7 @@ New unaccounted functionality no prior list knew (addresses verified absent from launcher's `ExplosionModelFile`. The MissileThruster parser `FUN_004bf8ec` writes `"SplashRadius"` to +0x50 (size-0x54 record). Burst falloff `baseBurst / dist^exp` floored at 1 (arcade 1.25 / WinTesla `EXPLODE.cpp:209` 1.2f). Only missiles splash (AC tracer is not a Missile). The engine `EXPLODE.cpp` - diff is DONE — the T0 source confirmed the model; the only 1995-vs-WinTesla drift is the exponent. + diff is DONE — the T0 source confirmed the model; the only original-vs-WinTesla drift is the exponent. Port: `BTResolveSplashRadius`/`BTApplySplashDamage` (mech4.cpp), see [[combat-damage]]. **Remaining T3:** the per-player enable sub-gate `missile+0x360 = BTPlayer+0x264` (its writers read as a per-frame toggle, not a config flag; the `showDamageInflicted@0x264` label is a guess) — port treats authored @@ -181,7 +181,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR the port now implements it byte-grounded ([[subsystems]] WAVE 6 correction). LESSON: a "no callers in the decomp" claim is only as good as the export coverage — sweep the raw image for the operand pattern before declaring a data path dead ([[reconstruction-gotchas]]). - Original entry below for the record:** The 1995 manual (p20/p22 + stat sheets) says seek changes TOP SPEED (gear 4 = + Original entry below for the record:** The original manual (p20/p22 + stat sheets) says seek changes TOP SPEED (gear 4 = "Supercharge", expert-only; Loki/Thor print Normal 143 / Super Charged 182 / Gimped 40 kph). The 4.10 binary provably has NO runtime myomer→speed coupling (full data-flow closure [T1]: demand fn @004afd10 all branches, all speedDemand writers, AvailableOutput's 2 callers, the @@ -193,7 +193,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR authorized multiplication in the demand path. [[subsystems]] [[pod-hardware]] - **Environment gravity unwired — the myomer CLIMB-heat term is inert (found 2026-07-31, #85).** `Mover::localEnvironment` (MOVER.h:271) is declared and never populated anywhere in the port; - `GetEnvironment()->gravityConstant` is the named analog of the 1995 Mover's gravity POINTER at + `GetEnvironment()->gravityConstant` is the named analog of the original Mover's gravity POINTER at `+0x250` (`FUN_00421e2c` does `vy -= **(+0x250)` per tick), which the myomer drive-heat integrator uses for its `m·g·|vy|·dt` climb-work term. The bridge (`BTMechMyomerMotionSample`) null-guards it, so climbing currently generates no myomer heat (flat maps: moot; cavern slopes: @@ -239,9 +239,9 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR `SetFogStyle(searchLightOn/OffFogStyle)` with the binary's inverted-cache seed (authentic DARK night start), plus the external spot.bgf beam cone (btfx `brighten` additive veil) on the searchlight site joint, MP-replicated. Historical attribution notes kept below — the - "ORIGINAL 1995 LATENT BUG" premise was ❌ RETRACTED 2026-07-25 (#61).** The arcade swaps fog between `fog=` (lights on) and + "ORIGINAL LATENT BUG" premise was ❌ RETRACTED 2026-07-25 (#61).** The arcade swaps fog between `fog=` (lights on) and `nosearchlightfog=` (off) via `PullFogRenderable` watching the Searchlight's `lightState`. - **The old entry claimed the 1995 binary itself could never light the lamp. That was wrong** — it + **The old entry claimed the original binary itself could never light the lamp. That was wrong** — it compared Searchlight's Performance (@004b841c, reads `requestedOn`@0x1E0) against **ThermalSight's** `ToggleLamp` (@004b860c → `0x1DC`), i.e. two different classes, and so invented a missing "0x1DC→0x1E0 bridge". Searchlight's real handler is **@004b838c** (table @0x51117C) and toggles @@ -251,7 +251,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR carries no novice gate. All T1.] Searchlight's handler set is WIRED and **verified live**: pad `0x14` → `requestedOn 0→1` → `lightState 0→1`; the fog watcher + beam cone landed 2026-08-05 (see the header above), so no - remaining work rides this entry. (NB the 1995 searchlight carries NO novice gate — that + remaining work rides this entry. (NB the original searchlight carries NO novice gate — that lockout is ThermalSight's; raw disasm @004b838c.) - **`HandleMessage` is vtable slot 8/9 in the binary but NON-virtual across the reconstruction -- FILED AS GITEA #65 @@ -305,7 +305,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR - **PlayerStatus (the mission-review panels) reads the compiled player at RAW BINARY OFFSETS -- FILED AS GITEA #63 (2026-07-25, found via #48) [T1 code-read].** btl4gau3.cpp `PlayerStatus::Execute`: `player+0x1FC` (vehicle), `+0x1C8` (score, drawn as a numeric), `+0x1C4` (alive/dead -- a - state flip draws a 160x240 filled box) -- all 1995 offsets, garbage on our compiled layout + state flip draws a 160x240 filled box) -- all original-binary offsets, garbage on our compiled layout (the databinding trap). DORMANT in-game (the review context no longer survives the viewpoint swap -- the #48 teardown fix) but LIVE the moment the mission-review screen actually runs at round end. Bridge all three through btplayer.cpp accessors before enabling the review screen. @@ -702,7 +702,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR - **Subsystem-panel online/offline gate — fields unidentified [T4] (2026-07-12).** The binary's SubsystemCluster draw-state reads `*(subsystem+0x40)==1` and `*(subsystem+0x278)!=4`; neither - 1995 field is identified (weapon simulationState is 0/2/3/4 -- +0x40 isn't it). The port pins + original field is identified (weapon simulationState is 0/2/3/4 -- +0x40 isn't it). The port pins the panel ONLINE (GetDrawState()==1) because the old raw reads were layout NOISE that flapped the state and black-filled the panel over the recharge arc (the frozen-PPC-dial bug; the WeaponCluster repaint-heal also landed). Identify both fields to restore the authentic @@ -948,7 +948,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR - **Collision-death tail fallthrough is inspection-tier [T3]:** the divert now falls to the death tail (VehicleDead + blast) per @0x4a0375, but no live wall-death bench has run; field wall-deaths exercise it nightly (watch for DEAD_NOTIFY on collision deaths). -- **The 1995 type-0 uninit-award bug** (documented in [[decomp-reference]] §BTPlayer table): the +- **The original type-0 uninit-award bug** (documented in [[decomp-reference]] §BTPlayer table): the shipped binary folds an uninitialized stack float into the shooter's score on every non-lethal hit's type-0 report. Era scoreboards looked sane — worth understanding WHY (dispatch path may deterministically leave ~0 at [ebp-0xc]; or replicant-player reroute delivery differs). Port @@ -979,7 +979,7 @@ readout being wrong — it is a real behavioural gap, not a cosmetic one. (`btfx:firesmoke1_scr_tex`) — all effects share the radial-masked grit sheet; decoding FIRE.BSL would give the authored sheet; (c) beams draw before particles, so a beam through thick smoke reads too bright (pass-ordering); (d) `colorWarp/alphaWarp` applied as `t^(1/warp)` [T4 convention]; - (e) emission uses rate×releasePeriod batching [T4 convention vs the exact 1995 emitter]. + (e) emission uses rate×releasePeriod batching [T4 convention vs the exact original emitter]. - Replacement-LOD selection for NON-additive multi-LOD models (needs the board's hot-spot/ reference-point semantics; leads: `s_dplobject.lod_ranges[16]/lod_hot_spot`, 0x2047/0x2048). - Day/night MATERIAL path priority (BMF/IMG) — infrastructure done, gated `BT_MATPRI` (over-applies @@ -1104,7 +1104,7 @@ Trace it live with `BT_AMMO_LOG=1` (+ `BT_PROJ_LOG=1` for per-tick heat/recoil). ## Night-9 playtest intelligence (build 4.11.693, 2026-08-01) **A MEMBER OF THE ORIGINAL TEAM IS NOW TESTING.** "Ronin" (log user `torak`, -machine TOWER_OF_TERROR) worked on the 1995 game and has played the pods +machine TOWER_OF_TERROR) worked on the original game and has played the pods recently, including in Japan. He is a **primary source** — treat his comparisons against the pods as high-value evidence, above reconstruction inference. Two contributions already: diff --git a/context/operator-console.md b/context/operator-console.md index 2f77b2d..d8ce080 100644 --- a/context/operator-console.md +++ b/context/operator-console.md @@ -14,7 +14,7 @@ open_questions: # The Operator Console + Relay The system-operator station: build a mission, run a session, watch pods arrive, launch rounds. -This is the piece the 1995 archive lost and we rebuilt. +This is the piece the original archive lost and we rebuilt. **What survives of the original, and why so little:** the console APPLICATION is absent from every archive (and Route A is closed). What we have is the pod's half of the conversation -- @@ -22,7 +22,7 @@ archive (and Route A is closed). What we have is the pod's half of the conversat defining the Console* messages the game sends TO the console (MechKilled / MechDamaged / ScoreUpdate / TeamScoreUpdate / DeathWithoutHonor / EndMission) -- plus the engine's receive-side handlers (egg / RunMission / StopMission). Its changelog line *"06/03/95 GAH -- Added -corresponding Macintosh message definitions"* implies the 1995 sysop station was a **Macintosh +corresponding Macintosh message definitions"* implies the original sysop station was a **Macintosh application**, a separate codebase that was never in the pod build tree -- which is why no archive has it. `btconsole.py`/`btoperator.py` are therefore a wire-faithful RECREATION built from the surviving parser side (+ BT_NET_PROBE against the real exe), not a port of the lost program. @@ -292,7 +292,7 @@ no UDP fan-out, no control channel). All sends now go through `_send_all_guarded - **Relay (internet)** — `btconsole.py --relay `; pods dial out. The normal mode. - **Mesh (LAN legacy)** — `btconsole.py ...`; the console dials OUT to each pod's - `-net` port. The authentic 1995 cabinet direction. + `-net` port. The authentic original cabinet direction. - **Remote relay / PARKED RELAY** — the GUI drives an already-running relay over the control port instead of spawning a child. **This is a COMPLETE, SUPPORTED deployment mode** (fixed 2026-07-26 — do not re-derive it; see §Parked relay below). The intended production shape: the relay lives diff --git a/context/pod-hardware.md b/context/pod-hardware.md index d40887b..428a55a 100644 --- a/context/pod-hardware.md +++ b/context/pod-hardware.md @@ -63,7 +63,7 @@ Searchlight + Searchlight2 `ToggleLamp` (id 3, table @0x51117C fn @004b838c)** 2026-07-25 (#61)**, previously default-constructed blackholes; verified live (0x14 → `lightState 0→1`, 0x12 → `thermalActive 0→1`). ⚠ The fn addresses were **swapped** in this file before #61 (@004b860c is ThermalSight's) — see [[decomp-reference]] for the table evidence — and -that swap had propagated a false "1995 latent bug" (retracted, [[subsystems]] WAVE 4). +that swap had propagated a false "original latent bug" (retracted, [[subsystems]] WAVE 4). An unhandled message is SILENTLY ignored (Receiver::Receive finds no handler and does nothing — dead buttons produce NO log). 0x10/0x11 = map ZoomIn/Out, 0x15 = CycleDisplayMode, 0x18 = CycleControlMode (#6). Buttons @@ -77,7 +77,7 @@ Full decode: [[gauges-hud]] §preset pages; the complete 72-button census + veri button |axis deflect|rate |keypad pilot|external <0-15>|pckey |action `; `pad