diff --git a/.gitignore b/.gitignore index ad2cfb0..ee93dc9 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,11 @@ scratchpad/night*/*.zip # (deliberate exceptions: git add -f) scratchpad/night*/*notes*.txt scratchpad/night*/lastrun*.txt + +# Bench artifacts in content/ (screenshots, session logs, generated eggs, deployed exe) +# -- receipts stay on disk, out of `git status`. A new AUTHORED egg: git add -f. +content/*.png +content/*.log +content/matchlog_*.txt +content/btl4.exe +content/*.EGG diff --git a/CLAUDE.md b/CLAUDE.md index 681c1c1..c95ad1f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,9 +10,11 @@ `BTL4OPT.EXE` binary on top of the working WinTesla engine. **Repo of record:** the top-level `CMakeLists.txt` + `README.md` build `btl4.exe`. Layout: `engine/ game/ content/ docs/ reference/ tools/ context/`. -**Current front:** `btl4.exe` runs a full single-player loop; the gauge system is complete; the -active work is reconstructing each subsystem's authentic behavior from the binary. Details + -what's-next: `context/project-overview.md`, `context/open-questions.md`, recent git log. +**Current front (2026-08-06):** core gameplay reconstruction is COMPLETE — SP+MP loop, combat/ +damage/scoring, locomotion (gait/gimp/CROUCH), night kit (searchlight), death/respawn, replication +all authentic + benched (builds 4.11.774→801). Active work: the POLISH list + the #60 export +gap census. Details + what's-next: `context/project-overview.md`, `context/open-questions.md`, +recent git log. --- diff --git a/context/build-and-run.md b/context/build-and-run.md index c9c575e..91a1fe7 100644 --- a/context/build-and-run.md +++ b/context/build-and-run.md @@ -182,6 +182,18 @@ default (`-DBT_STEAM=ON` is the documented dev-checkout state). `BT_EXPIRE=ON` ( 14-day tester window; an expire-OFF zip is renamed `-noexpire` and warns. Verify by extracting the zip somewhere clean and booting it with no repo present — that is what catches a missing runtime DLL. +## Build ritual — the stale-link flake (bit 4+ times; MANDATORY for bench work) +MSBuild often does NOT relink `btl4.exe` when only static-lib members changed — the bench then +runs a STALE exe and the session burns hours on phantom results. Ritual for every code-change build: +1. `rm -f build/Release/btl4.exe` FIRST (force the relink); +2. build; a REAL relink prints the 20 known-benign `CreateStreamedSubsystem` LNK2019s (`/FORCE` + baseline — see the top of this file); an up-to-date run prints none; +3. STRING-VERIFY before benching: `python -c "d=open(r'build/Release/btl4.exe','rb').read(); + print(b'' in d)"` — pick a string your change added (NB: release strings + carry LOG TEXT, not function names — check the .map for symbols); +4. use ABSOLUTE paths in every bench command — `cd` leaks between compound commands (the cwd + trap) and has broken builds/appends/copies repeatedly. + ## Key Relationships - Base: [[wintesla-port]] (the engine build recipe). - Verify loop: [[reconstruction-method]]; env gates: [[decomp-reference]] §6. diff --git a/context/combat-damage.md b/context/combat-damage.md index cefec08..10b7b85 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -953,7 +953,9 @@ stub let it fire and corrupt `graphicAlarm`. Now `mech->IsDisabled()` — the la defence-in-depth. Sibling fix: `mechdmg.cpp:451` read the phantom `stance` (perma-0) instead of `MovementMode()`, so the leg-shot-out → `graphicAlarm=9` fall/death branch was entirely DEAD; now live. -## Kill-score damage (task #60) +## Kill-score damage (task #60) — ⚠ SUPERSEDED 2026-08-05 by §THE AUTHENTIC SCORE/DEATH REPORT +## TAIL above (`BTPostKillScore` is RETIRED; the kill basis is now the applied damage TALLY + +## the victim role's killBonus, posted by the victim's own TakeDamage handler). History below. `BTPostKillScore` (btplayer.cpp:1491) feeds the ScoreMessage `damageAmount` into the kill award (`@0x4c02e4` → `(damageAmount + scoreAward) × roleScalar × teamMult × tonnageRatio`, `@0x4c052c`). The port passed a flat `kShotDamage=12` (mech4.cpp:1551), so every kill scored identically regardless diff --git a/context/decomp-reference.md b/context/decomp-reference.md index 468f577..62d049e 100644 --- a/context/decomp-reference.md +++ b/context/decomp-reference.md @@ -176,6 +176,22 @@ rows until sanity fails (`python + struct`, see the session commits `cc2b109`/`2 --- +### Master posture / crouch block (FUN_004a9b5c region, raw disasm 2026-08-05/06) [T1] +- mech+0x3f8 `mapPosture` (0 none / 1 may-duck / 2 holding-squat) — selector @0x4a9f61-0x4aa007: + gates = movementMode normal, simLive (player+0x25c, NOVICE lockout), leg alarm 0/4 → 1, leg + alarm 1 → 2, myomer factor |x|>1e-4 (const @0x4ab16c). +- mech+0x79c `myomerEffectiveness` — MAX over the myomer chain's speedEffect@0x31C, published by + the mapper drive-scale block (mechmppr.cpp:990 == @0x4a9cf2-0x4a9da4); scales speedDemand, + zeroes turnDemand at ~0 (the seek-4 freeze), and gates the crouch. +- DuckRequest consumer @0x4aa011-0x4aa0af: duckState && squatCapable → SetLegAnimation(2 'sqd') + / (3 'squ'), ForceUpdate(8)+(1) (type-3 record ships legState → peers pose it), stability + alarm 0/1, request consumed. Clips: animationClips[2]=sqd(i), [3]=squ(i) (BTL4.RES, all 8 + chassis; loader slots byte-verified vs @0x50d9c8 suffix pool). +- Myomers cluster: Performance wrapper @004b8b9c → drive-heat integrator @004b8be3; + AvailableOutput @004b8ac0 = gear clamp × QUADRATIC heat degrade (+0x114 vs +0x118/+0x11c band) + × (1 − legZoneDamage); speedEffect = output / ownerBaseSpeed(+0x34C) — unclamped (gear-4 + supercharge ≈1.43). + ## 4. Damage delivery - `Entity::TakeDamageMessage(id, size, inflictingEntityID, zone, Damage&)` → `target->Dispatch(&msg)`. @@ -531,6 +547,13 @@ default-ON (`'0'` disables). | Var | Effect | |---|---| | `BT_FORCE_THROTTLE` | auto-walk forward (no key) | +| `BT_BTNTEST=addr,on,off` / `BT_BTNTEST2=...` | scripted screen-button press/release cycles at poll counts (the REAL RIO seam; 2nd cycle for e.g. squat→rise) | +| `BT_DUCK_LOG` | crouch diagnostics: RIO press mode-mask, SetLegAnimation re-arm tracer (+ra), 1 Hz joint probe, posture/squat events | +| `BT_TREE_LOG` | dump the built render-tree topology (segment/joint/parent, SITE rows) at mech build | +| `BT_SPOT_SELF` | bench-only: build the searchlight cone on the OWN-cockpit tree so `BT_CAM=face` can eyeball it solo | +| `BT_CAM=face` | chase camera in FRONT looking back (the old animation view); `BT_CAM_Y/Z` offsets | +| `BT_FOG_LOG` | log SetFogStyle transitions + fog page resolution (searchlight swap forensics) | +| `BT_GIMP_SPEED` | override the gimp-gait demand scale (1.0 = authentic) | | `BT_SPAWN_ENEMY` | spawn a target mech 120u ahead along the spawn facing | | `BT_AUTOFIRE=1` | hold the trigger (headless walk→fire→death harness; supersedes the dead `BT_FORCE_FIRE` — btl4main.cpp:310 set `fireForced` once at startup, but mech4.cpp:1567 unconditionally overwrites it every frame) | | `BT_ASSERT_TO_DEBUGGER` | route CRT asserts to the debugger, not a modal box | diff --git a/context/experience-levels.md b/context/experience-levels.md index b109a15..edc75b4 100644 --- a/context/experience-levels.md +++ b/context/experience-levels.md @@ -42,7 +42,9 @@ then unconditionally `+0x264 = +0x268 = mission->advancedDamageOn(+0xf0)` (both ## 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` +- **+0x25c — "sim live", off only for novice.** Consumers: the CROUCH posture selector + (master-perf @0x4a9f70: novice → mapPosture 0 → squat/rise refused — 2026-08-06, + [[locomotion]] §CROUCH); ballistic jam roll `CheckForJam` @4bbfcc early-returns NO-JAM when 0 (projweap.cpp calls this `LiveFireEnabled`); **ThermalSight** `ToggleLamp` @4b860c (thermalsight.hpp `ControlsAllowLights`) — ⚠ **CORRECTED 2026-07-25 (#61): this was listed as *Searchlight's* gate. It is not. Raw disassembly of Searchlight's real handler diff --git a/context/gauges-hud.md b/context/gauges-hud.md index cb1902b..55d65a7 100644 --- a/context/gauges-hud.md +++ b/context/gauges-hud.md @@ -11,7 +11,7 @@ open_questions: - "SeekVoltageGraph RECONSTRUCTED 2026-07-19 (Gitea #11, was #10 finding A): full widget landed (see §SeekVoltageGraph below) -- ghosts gone steady-state (BT_PRESET_HOLD verification); remaining polish: a 1-frame transition artifact when a BT_SHOT lands on the exact page-switch frame (label BecameActive vs the graph's next rated Execute -- same lag class as the binary; self-heals next frame)" - "Secondary-view cycling RESOLVED 2026-07-19 (Gitea #6): the selector is the DISPLAY mode (CycleDisplayMode -> vtbl+0x4C override @4d1ae4), NOT CycleControlMode; desktop 'N' / pad RightThumb wired; pixel-verified dama->crit->heat" - "Upper-MFD PRESET pages RESOLVED 2026-07-19 (Gitea #9): SetPresetMode table @0051dbf0 re-decoded (little-endian -> ModeMFD bits 0-14), per-MFD pod button banks identified from the .CTL dump, desktop J/K/L cycle wired" - - "Always-active msg-4 records IDENTIFIED 2026-07-20 (glass input audit): 0x2C = Reservoir InjectCoolant (the flush button), 0x2F/0x2E/0x2D/0x2B/0x2A/0x29 = Condenser1-6 MoveValve, 0x1A-0x1D = GeneratorA-D ToggleGeneratorOnOff (@0050fb90, unreconstructed); plus 0x13 = Mech DuckRequest (crouch), 0x28 = Mech BalanceCoolant, 0x12/0x14 = ThermalSight/Searchlight toggles -- see pod-hardware.md + docs/GLASS_COCKPIT.md 2026-07-20" + - "Always-active msg-4 records IDENTIFIED 2026-07-20 (glass input audit): 0x2C = Reservoir InjectCoolant (the flush button), 0x2F/0x2E/0x2D/0x2B/0x2A/0x29 = Condenser1-6 MoveValve, 0x1A-0x1D = GeneratorA-D ToggleGeneratorOnOff (@0050fb90; wired 2026-07-25, powersub.cpp); plus 0x13 = Mech DuckRequest (CROUCH -- COMPLETE 2026-08-06, [[locomotion]]), 0x28 = Mech BalanceCoolant, 0x12/0x14 = ThermalSight/Searchlight toggles (searchlight visuals done 2026-08-05) -- see pod-hardware.md + docs/GLASS_COCKPIT.md; statuses re-swept 2026-08-06" - "MP DEATHS resolved 2026-07-12 (observed-death tally + display clamp); remaining: verify multi-death tallies stay in sync across a long session (GAUGE_COMPOSITE.md)" --- diff --git a/context/multiplayer.md b/context/multiplayer.md index 4d351b9..05df281 100644 --- a/context/multiplayer.md +++ b/context/multiplayer.md @@ -885,6 +885,9 @@ transition, HUD all landed since P6): console egg → mesh → RunningMission on report tail in the victim's TakeDamage handler — BTMechPostCombatReports, replacing BTPostKillScore; same cross-node reroute, see [[combat-damage]] §report tail). Respawn is reconstructed (task #52 — see item 3b; the VehicleDead sender moved to the same tail). + CROUCH and the SEARCHLIGHT replicate with ZERO new wire code (2026-08-05/06): the squat rides + the existing type-3 state record (legState) and the lamp rides subsystem record 0x14 + (lightState) — [[locomotion]] §CROUCH / [[rendering]] §SEARCHLIGHT. ## Key Relationships - Base: [[wintesla-port]] (L4NET). Depends on: [[locomotion]] (update writer), [[combat-damage]] diff --git a/context/open-questions.md b/context/open-questions.md index 6a452fe..433cfd1 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -224,16 +224,22 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR ⚠ This entry was stale in BOTH directions for weeks (`docs/INPUT_PATH_AUDIT.md` flagged it) — most of the list had been reconstructed while the prose still called it dead. Check the code, not the census, before reconstructing anything here. -- **`DuckState` has no CODE consumer, and that is authentic [T1].** `Mech::DuckRequest` sets - `duckState`(attr 0x37, binary `mech+0x398`) to 1 and nothing else: the flag has exactly two - writers in the whole binary (that handler and the mech reset) and ZERO readers. Its consumer is - a DATABINDING — `content/GAUGE/L4GAUGE.CFG` drives a 3-frame `bduck.pcc` widget off `DuckState` - on the map's legend column, verified live (the crouch icon lights grey→orange on a press). So - the button is COMPLETE as a request flag + indicator. What is NOT known: whether the 1995 game - ever consumed it for posture/collision (no SQUAT clip name survives in the decomp or in - `content/`, only `DuckServo01.wav` in AUDIO1.RES). Do not invent a crouch pose to "finish" it. -- **Searchlight-driven fog swap — STILL DEFERRED, but the "ORIGINAL 1995 LATENT BUG" premise is - ❌ RETRACTED (2026-07-25, #61).** The arcade swaps fog between `fog=` (lights on) and +- **❌ RETRACTED (2026-08-06): "`DuckState` has no CODE consumer, and that is authentic [T1]".** + That verdict — and its corollary "no SQUAT clip survives; do not invent a crouch pose" — was + EXPORT-GAP BLINDNESS ([[reconstruction-gotchas]] §20, incident 2), and the [T1] tag was + unearned: "ZERO readers" was true of the *export*, not the binary. The reader is the master-perf + posture/duck block in the un-exported region (@0x4aa011-0x4aa0af, raw disasm), and the squat + clips DID ship — `squ/sqd` + interior `squi/sqdi` for all 8 chassis live in **BTL4.RES** + (the old claim searched decomp strings + loose `content/` files, never the RES TOC; + `DuckServo01.wav` was the tell). CROUCH is now fully reconstructed — request latch → posture + arbiter (novice + myomer + mode gates) → squat/hold/rise, MP-replicated on the type-3 record + ([[locomotion]] §CROUCH). The `bduck.pcc` legend databinding observation stands and still works. +- **✅ Searchlight-driven fog swap — DONE 2026-08-05 (412053d/b75bb4a; [[rendering]] §SEARCHLIGHT).** + The port's `TickSearchlight` (btl4vid) is the `PullFogRenderable` equivalent: LightOn → + `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 `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 compared Searchlight's Performance (@004b841c, reads `requestedOn`@0x1E0) against **ThermalSight's** @@ -243,15 +249,10 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR un-pooled `"ToggleLamp"` strings adjacent to their own class names. Body: @004b838c sits in a Ghidra export gap (#60) but was **recovered by raw disasm** (`scratchpad/dis838c.py`) — it toggles 0x1E0 and carries no novice gate. All T1.] - Searchlight's handler set is now WIRED and **verified live**: pad `0x14` → `requestedOn 0→1` → - `lightState 0→1`. So the sim needs **no repair** and the previous "DECISION: faithful to the buggy - original" is void — a working fog swap is now plain FAITHFUL reconstruction. Remaining work is a - single item: construct `PullFogRenderable` at btl4vid.cpp `MakeMechRenderables` reticle-build/inside - pass (== arcade part_014.c:5173, Dynamic, bound per Searchlight `lightState` via a new - `LightStatePtr()` accessor). `ControlsAllowLights()` is WIRED since issue #2 to the `player+0x25c` - not-novice experience flag via the BTPlayerExperienceSimLive bridge ([[experience-levels]]). - See [[rendering]] fog section. (The pre-#61 "verified inert live: BT_FOG_LOG zero `SetFogStyle(2/3)`" - observation still holds — reason (1), the un-constructed renderable, remains.) + 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 + 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 (2026-07-25, found via #46).** Ten classes declare it (ammobin/heat×2/hud/mechsub/myomers/ diff --git a/context/pod-hardware.md b/context/pod-hardware.md index 9d3b392..768b859 100644 --- a/context/pod-hardware.md +++ b/context/pod-hardware.md @@ -54,9 +54,10 @@ button** (Reservoir InjectCoolant, hold-to-flush — works), **0x2F/0x2E/0x2D/0x the per-condenser VALVE buttons** (MoveValve, Cond1-6 — work), **0x1A-0x1D = Generator A-D ON/OFF** (`ToggleGeneratorOnOff` id 4, binary table @0050fb90 fn @004b1ed0 — ✅ **WIRED**, `powersub.cpp`). Newly decoded from the binary message tables: **0x13 → Mech `DuckRequest` -(0x1a @0049fa00 — the manual's CROUCH button)** — ✅ **WIRED 2026-07-26**, the last handler in -this census; press-only, sets `duckState` and the map legend's `bduck.pcc` widget lights (see -[[open-questions]] for why it has no code consumer) —, **0x28 → Mech `BalanceCoolant` (0x16 +(0x1a @0049fa00 — the manual's CROUCH button)** — ✅ **COMPLETE 2026-08-06**: handler (07-26) + +the master-perf posture CONSUMER + squat/rise clips + MP replication ([[locomotion]] §CROUCH; +glass key F4); the map legend's `bduck.pcc` widget still lights. (The old "no code consumer" +verdict was export-gap blindness — [[reconstruction-gotchas]] §20) —, **0x28 → Mech `BalanceCoolant` (0x16 @0049f728)** ✅ **WIRED 2026-07-21 (#20)**, **0x12 → ThermalSight `ToggleLamp` (id 3, table @0x51120C fn @004b860c)** and **0x14 → Searchlight + Searchlight2 `ToggleLamp` (id 3, table @0x51117C fn @004b838c)** — ✅ **BOTH WIRED 2026-07-25 (#61)**, previously default-constructed blackholes; verified live (0x14 → @@ -231,8 +232,8 @@ every checked control behavior: "Top Speed Gimped 40" also confirms the limp-gait speed cap as an authored spec. The port follows the BINARY (house rule); resurrecting manual-4.0 supercharge would be an opt-in deviation for the operator to decide. -NEW LEADS (manual describes, port lacks input/UI): **CROUCH** (button by the secondary screen; -`Mech::duckState` attr 0x37 + SQUAT clips exist, nothing drives them), ~~EJECT~~ **EJECT WIRED +NEW LEADS (manual describes, port lacks input/UI): ~~CROUCH~~ ✅ **CROUCH COMPLETE 2026-08-06** +([[locomotion]] §CROUCH), ~~EJECT~~ **EJECT WIRED 2026-08-02** (core: `Mech::EjectPilotMessageHandler` id 0x19 @0049f854 + the crippled-mech permission evaluator @0049fa1c; input = binding-engine "Eject" action, Backspace / pad LeftThumb; the punch-out kills via graphicAlarm 10 ≥ 9 — KillBonus authors 0 in ALL shipped diff --git a/context/project-overview.md b/context/project-overview.md index 793ec6a..72beebb 100644 --- a/context/project-overview.md +++ b/context/project-overview.md @@ -39,19 +39,21 @@ sections in `docs/PROGRESS_LOG.md` cite old `C:/git/nick-games/...` paths — tr they map into this repo (reconstructed BT → `game/reconstructed/`, engine → `engine/MUNGA{,_L4}/`, content → `content/`, raw decomp → `reference/decomp/`). [T2] -## Current state (2026-07) -`btl4.exe` boots, renders the world + a skinned mech, and runs a full **drive → animate → target → -fire → damage → destroy → respawn** single-player loop (task #52). **2-node MP is verified -end-to-end** — replication, cross-pod targeting/damage/kill, beam visuals, replicant gait (tasks -#46-#51, [[multiplayer]]). All 8 cockpit canopies are authentic + the horizontal-FOV fix (task #55, -[[cockpit-view]]); the Gyroscope is live byte-exact with hit-bounce (task #56); the **gauge system -is complete** ([[gauges-hud]]). The engine/renderer/HAL are done (WinTesla); AUDIO backend is now REAL -(the repo's OpenAL32.dll + libsndfile-1.dll were both no-op STUBS — replaced with real OpenAL Soft + -an in-tree WAV loader, 2026-07-15) and the 241-sample soundbank is cracked from AUDIO1/2.RES + loading; -the ONLY remaining audio gap is game-triggering (no AudioEntities fire PlayNote yet), so gameplay is -still silent apart from a proof-of-life hook (see [[wintesla-port]] Audio). The active work -is reconstructing each BT subsystem's authentic behavior from the binary. Remaining: pod-LAN -config, Mech-level update records, per-subsystem waves. [T2] +## Current state (2026-08-06) — core gameplay reconstruction COMPLETE; polish phase +Field-tested nightly at 4-8 pods over Steam (builds 4.11.622→801). Authentic + benched: the full +SP+MP loop; per-panel mesh-true targeting/damage (+ crits, cylinder lottery, armour darkening); +the 1995 SCORING model (kill awards + received penalties + panic cost — the id-0x16 report tail); +death (blast/splash, wreck, burial) + respawn (full re-arm audit); locomotion (two-channel gait, +gimp limp, knockdowns, CROUCH F4); the night kit (fog-swap SEARCHLIGHT F5 + beam cone); heat/ +power/myomers (incl. the seek-4 freeze); replication (masters/replicants, kill/death columns, +ghost+skate field detectors); the Steam wire seam + build gate; the operator console/relay +lifecycle; gauges/HUD; audio (real OpenAL backend + the AUDIO_FIDELITY trigger waves — footsteps/ +gait/alarms/impacts live in the field). [T2] +**Remaining (polish + infra):** the #60 export gap census (NEXT — export-gap blindness has caused +4 wrong conclusions, [[reconstruction-gotchas]] §20); VehicleDead killed-by consumption (operator +"X killed by Y"); burning-wreck handler (mech-0x17 label mismatch); the id-0x16 type-0 curiosity; +MechRIOMapper Keypress @004d2514; marker beacon (LoadObject wrapper stub); DIV firmware +intersection routine (deep-cut); deferred ledger items in [[open-questions]]. [T2] ## Key Relationships - Full detail: `docs/PROGRESS_LOG.md`. diff --git a/context/reconstruction-gotchas.md b/context/reconstruction-gotchas.md index 316631a..72e057e 100644 --- a/context/reconstruction-gotchas.md +++ b/context/reconstruction-gotchas.md @@ -278,6 +278,12 @@ Suspect ANY reconstructed per-frame code with narrow equality/window tests or `x state transitions: charge/seek loops, snap comparisons, timers compared with `==`. ## 13. Verification gotchas (don't fool yourself) +- **Capture the viewpoint that can SEE the change.** The crouch "pose does not hold" and the + false "eye does not drop" residual (2026-08-05/06) were BOTH capture errors: cockpit-view + screenshots cannot show your own legs, and canopy-dominant diff crops mask eye-relative motion + (the canopy drops WITH the eye — only the through-window ground shifts). Anchor pixel-diff + crops on a region the effect MUST change, pair them with a state probe (joint values), and + READ one frame with your own eyes before declaring a visual regression. - **Lazy gauge build:** `GaugeRenderer::BuildConfigurationFile` runs LAZILY. A too-early process kill shows `[gskip]=0` / "not built" even though the widget is fine — **wait for the gauge @@ -543,6 +549,30 @@ segment tables) — the same class of latent overflow. 0xFEEEFEEE=freed). 5. For exhaustive multi-function analysis: a read-only Workflow (understand), then implement hands-on. +## 20. Export-gap blindness — absence in the EXPORT is not absence in the BINARY (4 incidents) +The Ghidra export (`reference/decomp/`) has coverage gaps (#60), and BTL4.RES content never +appears in it at all — so "no readers", "no caller", "no such clip/asset", and "unreconstructed" +claims made by grepping the export or the port ALONE are structurally unsound. This class has +produced four wrong conclusions, two of them [T1]-tagged at the time: +1. **2026-07-25 searchlight "1995 latent bug"** — compared Searchlight's Performance against + THERMALSIGHT's handler (wrong class) and invented a missing bridge; retracted (#61). +2. **2026-07-2x `DuckState` "has no CODE consumer, authentic [T1]" + "no SQUAT clip survives"** — + the consumer was the un-exported master-perf posture block (@0x4aa011), and the clips lived in + BTL4.RES (`squ/sqd/squi/sqdi` × 8 chassis); the search covered decomp strings + loose files, + never the RES TOC. Disproven by the CROUCH reconstruction (2026-08-06). +3. **2026-07-31 morning: the myomer drive-scale DELETION** ("the binary has NO dynamic + myomer→speed coupling") — the consumer was un-exported; restored same day by raw capstone. +4. **2026-08-05 "the myomer factor FEEDER (@004b8be3) is unreconstructed"** — it had been fully + reconstructed since 07-31 under NAMED members (`speedEffect`, `AvailableOutput`); the grep + searched raw offsets. Cost: a duplicate multiply + a dead crouch gate until 08-06. +**The rule — before claiming "X does not exist / is not reconstructed":** +- byte-scan `content/BTL4OPT.EXE` for the offset/immediate (disp32 patterns), never just the export; +- grep the port for NAMED members (check the .hpp for the offset's name) — offsets rot after promotion; +- for content claims, walk the **BTL4.RES TOC** (`tools/resscan.py` pattern), not the loose tree; +- check `reference/BT410_SOURCE_MANIFEST.md` + `game/reconstructed/CLASSMAP.md`; +- and tag the claim's tier by the WEAKEST source consulted — an export-only sweep caps at [T4]. +The #60 gap census (dark-region inventory + a fresh Ghidra re-export) is the systemic fix. + ## Key Relationships - Applies to: every topic that reconstructs a class ([[subsystems]], [[combat-damage]], [[gauges-hud]], [[locomotion]]). - Uses: [[decomp-reference]] (offsets/ClassIDs), [[reconstruction-method]] (the loop). diff --git a/context/rendering.md b/context/rendering.md index 0c2e572..02fc991 100644 --- a/context/rendering.md +++ b/context/rendering.md @@ -413,8 +413,15 @@ the VISUALS were the gap, decoded from MakeMechRenderables @004cef28 `case 0xbd8 enables future site-hung effects). Wrecks go dark through the authentic gate (host shutdown forces lightState 0). Cross-node verified: B presses, A logs `[spot] cone SHOWN (seg 20)`. - Benches: `scratchpad/night12/searchfog.sh` (solo cockpit chain + red-fog end-to-end probe), - `spotcone.sh` (2-node cone + replication). OPEN items in [[open-questions]] (cone look pass, - the stubbed LoadObject wrapper + dormant marker case, the subtle authored deltas). + `spotcone.sh` (2-node cone + replication). BEAM MATERIAL decoded 2026-08-06 (b75bb4a) [T1]: + SPOT.BGF is a 7-vert cone ~50u forward and ~35° DOWN (a ground-pool lamp, not an air beam), + verts tinted cyan-white; its material class `btfx:brighten.25` smuggles the ADDITIVE factor + in DIFFUSE.r (0.25) with a warm emissive on the night page — the loader draws brighten* + batches as an additive veil (dest += vertexRGB × factor, blend pass, unlit); tint compose + (vertex cyan vs night emissive warm) [T3], flagged in the L4D3D draw branch. Look pass + ACCEPTED by field eyeball 2026-08-06 (translucent beam reads correctly; solo rig + BT_SPOT_SELF=1 + BT_CAM=face). Still open ([[open-questions]]): the stubbed LoadObject + wrapper + dormant marker case, the subtle authored deltas. ## Key Relationships - Geometry/LOD: [[bgf-format]]. Base: [[wintesla-port]] (L4D3D). Shadow/visual-conform: [[locomotion]]. diff --git a/context/subsystems.md b/context/subsystems.md index b8cf988..aa62e87 100644 --- a/context/subsystems.md +++ b/context/subsystems.md @@ -41,6 +41,9 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER - **WAVE 3** — power bus (Generator/PoweredSubsystem) + Emitter/PPC fire-path (end-to-end fire; heat conducts to the central sink via the linked-sink roster). - **WAVE 4** — standalone readouts: Sensor/Searchlight/ThermalSight/AmmoBin (de-shim, gate fixes). + - ✅ **Searchlight VISUALS complete 2026-08-05** — cockpit fog swap + external spot.bgf beam + cone, both MP-replicated; mountSegment@0x1DC identified (= resource segmentIndex, the cone's + mount joint — was "commandedOn, role unidentified"). Full story: [[rendering]] §SEARCHLIGHT. - ✅ **Searchlight + ThermalSight buttons WIRED 2026-07-25 (#61)** — both classes' handler sets were default-constructed blackholes (systemic cause #1, `docs/INPUT_PATH_AUDIT.md`). Each now chains `PowerWatcher::GetMessageHandlers()` with its own id-3 `ToggleLamp`. **Verified live**: pad `0x14` diff --git a/context/test-harness.md b/context/test-harness.md index 36c83d8..df7e1d4 100644 --- a/context/test-harness.md +++ b/context/test-harness.md @@ -189,6 +189,21 @@ sleep ; kill $relay; bt_kill_ours (one line, ungated) answer questions retroactively; per-frame traces stay gated. +## Bench-script gotchas (each has burned a session) +- **Write benches CLEAN, never sed-derive a chain** — sed-derived copies silently dropped envs + twice (the [replgimp] silence); export envs INSIDE the per-node subshell. +- **Absolute paths everywhere; `cd /c/git/bt411` first** — the cwd trap (heredoc python + `cd + scratchpad/nightN`) has broken later builds/appends with `fatal: pathspec`/`cannot stat`. +- **No apostrophes/backticks through bash heredocs** — write a `.py` file and run it (quoting has + mangled posted tracker comments and killed scripts mid-parse). +- **Drain stale watchdogs before relaunching** — a leftover waiter's `taskkill` has killed a live + bench mid-run. +- **Scripted input goes through the REAL seam**: `BT_BTNTEST`/`BT_BTNTEST2` (RIO queue → mode-mask + drain). In 2-node runs press at poll ≥900 — round-start jitter can eat earlier presses. +- **Capture the viewpoint that can see the change** (gotcha #13 bullet): cockpit view can't show + your own legs; pair screenshots with a state probe and READ a frame before concluding. +- **Build ritual first** — see [[build-and-run]] §Build ritual (stale exe = phantom results). + ## Key Relationships - Uses: [[build-and-run]] (parity, env gates, BT_SHOT capture) · [[experience-levels]] (expert vs novice gating) - Informs: [[reconstruction-method]] (step 4 "verify honestly" — this file is the how) diff --git a/docs/INPUT_PATH_AUDIT.md b/docs/INPUT_PATH_AUDIT.md index 9cf143c..004f1ce 100644 --- a/docs/INPUT_PATH_AUDIT.md +++ b/docs/INPUT_PATH_AUDIT.md @@ -74,7 +74,7 @@ Ranked by impact. **Reverse thrust `0x3F` (key LALT / pad B) is FIXED** and conf | 3 | **pad LT / RT (turn pedals)** — and every pad-only session | Steer the legs | Not a wiring gap: the pedals are live (`L4PADRIO.cpp:710-715` → `L4CTRL.cpp:1438-1446` → `btl4mppr.cpp:1337-1340` → `:1399 pedalsPosition`) but only *Standard/Veteran* read them (`mechmppr.cpp:1034`, `:1061`); the mapper boots in **Basic** (`mechmppr.cpp:361`) — and **the pad has no binding for `0x18` CycleControlMode**. Verified: `content/bindings.txt:74-89` binds only `0x40 0x3F 0x47 0x42 0x44 0x43 0x41`; no `0x18`, no `0x15` | **S** | `BT_MPPR_TRACE=1`, squeeze LT on a pad-only launch: `pedals=` moves, `turn=0`. Add `pad BACK button 0x18`, press it → `[mode] control mode -> 1`, then LT yaws | | 4 | **Fire dropout: releasing one alias of a held fire button** (`1`+`SPACE` on `0x40`; `3`+`LCTRL`+`RCTRL` on `0x47`; pad A/X on the same) | Two keys on one address = two wires to one button | Edge state is per **binding**, not per **address**: `previousKeyHeld[k]` is indexed by binding index and `EmitButton(addr, held)` fires on every per-binding edge (`engine/MUNGA_L4/L4PADRIO.cpp:580-595`; same shape in the pad loop `:665-685`). The release writes `-(addr+1)` (`L4CTRL.cpp:2635-2647`) and `ControlsUpdateManager::Update` then suppresses the redundant press, so fire stays **off** until the held key is released and re-pressed | **M** (shared plumbing — **defer**) | `BT_FIRE_LOG=1`: hold LCTRL (missiles firing), tap `3`, release `3` → fire stops with LCTRL still down | | 5 | **`0x1A`-`0x1D`** Generator A-D ON/OFF (4 radar-rail buttons) | Take a generator off line (heat/power management) | `Generator : HeatSink` (`powersub.hpp:320-322`) and **no `Generator::GetMessageHandlers()` exists** — `powersub.cpp:1014` resolves to HeatSink's set `{id 3 ToggleCooling}`, so streamed `msg 4` (`ctrlmap.log:44-47`) finds nothing. `ToggleGeneratorOnOff` @`004b1ed0` is unreconstructed (0 hits in `game/`) | **M** | Click `0x1A`: today zero log. After: generator output voltage → 0 and its ENG-page bar follows | -| 6 | **`0x13`** CROUCH (manual p8, a full section) | Duck to present a smaller target | Streamed `subsys -1 msg 0x1a` (`ctrlmap.log:40`) resolves to the Mech (`ENTITY.cpp:608-615`), but `Mech::MessageHandlerEntries` has exactly three entries — `TakeDamage`, `PlayerLink`, `BalanceCoolant` (`mech.cpp:463-467`). No id `0x1a`. Assets exist (`Mech::duckState` attr 0x37 `mech.cpp:861`, SQUAT clips, `DuckServo01.wav`) | **M** (reconstruct @`0049fa00`) | Click `0x13` on a stationary mech: expect the squat clip + `duckState 0→1` | +| 6 | **`0x13`** CROUCH (manual p8, a full section) | Duck to present a smaller target | Streamed `subsys -1 msg 0x1a` (`ctrlmap.log:40`) resolves to the Mech (`ENTITY.cpp:608-615`), but `Mech::MessageHandlerEntries` has exactly three entries — `TakeDamage`, `PlayerLink`, `BalanceCoolant` (`mech.cpp:463-467`). No id `0x1a`. Assets exist (`Mech::duckState` attr 0x37 `mech.cpp:861`, SQUAT clips, `DuckServo01.wav`) | ~~M~~ ✅ **COMPLETE** (handler 07-26; consumer + clips + MP 2026-08-06 — [[locomotion]] §CROUCH) | Verified: squat clip plays, holds, rises; peers replicate it | | 7 | **Torso recenter from the keyboard** (`0x42` is pad/mouse only) | Center a twisted torso — the manual's anti-disorientation control (p9) | `0x42` itself is **LIVE** (streamed `subsys 17 attr 14 -> +0x208` = `Torso::centerCommand`, consumed `torso.cpp:636-640`) but `content/bindings.txt:83,86` bind it only on `pad Y`/`DPAD_UP`. The port's keyboard path (`gBTTorsoRecenter`, `mech4.cpp:2875`) is fed from the zeroed `gBTInput` and its consumer sits inside the stood-down bridge (`mechmppr.cpp:854-863`, gate `:655-660`) | **S** | Twist with `E`, press `X`: `currentTwist` does not return to 0. Add `key X button 0x42` (two rows on one VK both fire — `L4PADRIO.cpp:580-629` iterates all bindings) | | 8 | **Any clickable button latches if the mouse-up is lost** (worst on `0x3F` reverse, `0x40` trigger) | A momentary click always releases | Neither WndProc handles `WM_CAPTURECHANGED` or `WM_KILLFOCUS` — grep over `engine/` + `game/` returns **zero** hits. Alt-tab or a system dialog between press and release leaves `gCkPressed` set and `SetScreenButton(a,0)` never runs | **M** — trap: `btl4main.cpp:137` calls `ReleaseCapture()` itself, which re-enters `WM_CAPTURECHANGED`; guard on `gCkPressed != -1` (it is cleared before `ReleaseCapture`) | Click-hold `0x3F`, alt-tab, release outside the window: `BT_PAD_LOG=1` shows no release line and `rev` stays 1 | | 9 | **Every HOTAS / flight stick / rudder** (whole `joydev/joyaxis/joybutton/joyhat` grammar) | Drive the pod channels from a DirectInput device | `content/bindings.txt` is 89 lines and has **zero** joy rows; the joy poll is gated on `joyAxisBindingCount>0 \|\| …` (`L4PADRIO.cpp:775-777`) so `BTJoyInit/BTJoyPoll` are never called. `PadBindingProfile::Load` writes the default only when the file is **absent** (`L4PADBINDINGS.cpp:708-713`), and the file is gitignored (`.gitignore:23`) — so the zip ships **this machine's stale file** | **S** now (ship without it / refresh it) · **M** later (version marker + additive merge) | `content/jstest.log`: `bindings loaded: 40 keys, 10 pad buttons, 5 pad axes, 0 joy axes…`; no `[joy]` line exists in any log in `content/` | diff --git a/docs/RESPAWN_REARM_PLAN.md b/docs/RESPAWN_REARM_PLAN.md index 1ee1aeb..5fd5f87 100644 --- a/docs/RESPAWN_REARM_PLAN.md +++ b/docs/RESPAWN_REARM_PLAN.md @@ -271,3 +271,10 @@ Four operator/field reports were then grounded and dispositioned: Bench: `scratchpad/night11/respawnreset.sh` (BT_VALVE_TEST press -> BT_MP_FORCE_DMG kill -> read `[valve]`/`[respawn]` on A, plume/un-wreck ordering on B). + +## Addendum 2026-08-05 (#45 report tail): the VehicleDead SENDER moved +The death-transition dispatch site (mech4) is retired: the decoded sender is the TakeDamage +handler's death tail (@0x4a07d4-0x4a0890), now BTMechPostVehicleDead (btplayer.cpp) with the +BT 0x38-byte extension {killed-by player, kill zone}. The #55 NULL-playerLink fallback + the +DEAD_NOTIFY forensics moved into it intact; the #81 single-dispatch rule is unchanged. The +flow diagram above predates this -- read BTPostKillScore rows as historical (retired 08-05). diff --git a/reference/glossary.yaml b/reference/glossary.yaml index 4677cd0..94b9be9 100644 --- a/reference/glossary.yaml +++ b/reference/glossary.yaml @@ -431,3 +431,9 @@ RootTranslation: definition: The .ANI [RootTranslation] section — per-keyframe forward root SPEED (.z, units/s). Locomotion integrates movement += dt*rootTrans.z; feet plant by construction (no separate stride constant). related_terms: [ANI, locomotion, SequenceController] related_topics: [locomotion] + +export-gap-blindness: + kind: failure-mode + definition: Concluding "the binary/content lacks X" from the Ghidra EXPORT or the port alone. The export has coverage gaps (#60) and RES-archive content never appears in it, so absence there proves nothing. Four incidents (two falsely [T1]); rule + checklist in reconstruction-gotchas §20 — byte-scan the exe, grep NAMED members, walk the BTL4.RES TOC, consult the manifest. + related_terms: [decomp, RES] + related_topics: [reconstruction-gotchas, source-completeness]