Files
BT411/CLAUDE.md
T
arcattackandClaude Opus 5 1cda880c6d console/relay: document it properly + fix two regressions the review caught
DOCS (the ask: after a compaction this session lost track of how the console
works and launched the wrong program, twice).

  * NEW context/operator-console.md -- the dedicated topic that was missing.
    Leads with the thing I got wrong: btoperator.py is the PySide6 GUI the
    operator uses; btconsole.py is the headless relay it spawns.  Then ports,
    the route table, the roster/seat/identity model, the full round lifecycle
    with every launch gate, liveness, mode-specific traps, and log locations.
  * NEW docs/OPERATOR_GUIDE.md -- sysop-facing: start the console, set up a
    mission, watch pods arrive, launch, run back-to-back rounds, what to press
    when LAUNCH looks dead, a troubleshooting table keyed on the exact log
    lines, and what to save BEFORE restarting a session (Start Session
    truncates operator_relay.log, so restarting to clear a problem destroys the
    evidence of it).
  * CLAUDE.md: two Quick Lookup rows + a DO-NOT entry naming the two programs,
    so the distinction survives the next compaction.
  * context/multiplayer.md: a pointer out of the scattered console notes to the
    new topic (they were buried across ~8 places in a large file, which is
    exactly why they evaporated).

FIXES -- both are regressions in my own previous commit, found by the review
pass, and one would have made a games night WORSE:

  * THE REAPER WOULD HAVE KILLED HEALTHY PLAYERS.  It was gated only on "no
    mission running", which a round RESET satisfies -- so it was armed for the
    whole BETWEEN-ROUNDS wait, and that is a period when a pod is legitimately
    byte-silent: its seat beacon is write-only for the process lifetime
    (L4NET.CPP: "the relay ignores its silence") and its console pad has no egg
    yet so it cannot ACK.  A real night showed 12-minute and 5-minute gaps; the
    180s deadline would have dropped healthy pods and forced their clients to
    relaunch.  It now runs ONLY in the active staging window, skips pads with no
    egg and conns that have not HELLO'd, and last_seen is also stamped from
    inbound UDP (a pod streaming updates while its TCP idles was being counted
    as silent).  Half-open detection is keepalive's job; this is just a backstop.
  * UnboundLocalError in the operator UI.  My end_sent reset was an `elif` in
    the chain that assigns `head`, so that branch left `head` unbound -- a crash
    on the first status refresh after End Mission, which is exactly the path the
    relay's "StopMission sent" line produces.  Moved out of the chain.

Plus one pre-existing wedge with the same symptom as the reported bug, live-
proven in operator_relay.log (~5 minutes of a night lost): _abort_round clears
eggs_released BEFORE the survivors' sockets close, so _maybe_reset_round's own
`if not self.eggs_released: return` skips the template restore forever -- the
roster stays trimmed, the release gates can never be met, and walk-ups get
ROSTER FULL.  _rearm_for_new_round's restore is therefore now UNCONDITIONAL (it
was gated on eggs_released, which made Re-arm useless in the one state that most
needs it) and it clears round_hold_until so an abort's settle window is not
inherited.  Aborts are ordinary: nothing on the wire distinguishes a straggler's
late FIN from a pod dying mid-load.

scratchpad/test_relay_rearm.py now 19 checks, all passing, including the two new
regression guards (a 15-minute-silent waiting pod is NOT reaped; a pad that was
never sent an egg is NOT reaped) and the mid-pair no-re-arm guard.

Still open, recorded in the new topic's frontmatter: the UDP endpoint map trusts
the sender's self-declared fromHost; the egg-ACK is a fixed-offset parse of one
recv with no reassembly; remote-operator mode can never enable LAUNCH.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 00:36:14 -05:00

181 lines
12 KiB
Markdown

# BattleTech Pod-Port — Reverse-Engineering Expert Knowledge Base
> **This file is a ROUTER, not a knowledge store.** It says WHERE the knowledge is and HOW to use
> it; the actual knowledge lives in `context/*.md` (read on-demand). The complete pre-restructure
> 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
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:
`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.
---
## YOU ARE THE BATTLETECH PORT EXPERT
**You (Claude) are the expert on this port** — the 1995 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
precise than anything you can infer.
### How to answer a question
1. **Identify the topic(s)** via the Quick Lookup table below (most questions touch 1-3).
2. **Read the full topic file** — don't guess from the filename.
3. **Follow the wiki-links** (`[[name]]`) and `related_topics` frontmatter to adjacent knowledge. A
`[[name]]` resolves to the topic file `context/<name>.md` if one exists, else to the glossary
term in `reference/glossary.yaml` (cross-cutting concepts like `material-ramp`,
`subsystem-roster`, `attribute-pointer`, `master`/`replicant` live in the glossary, not a topic).
4. **Check `context/decomp-reference.md`** for any offset, ClassID, resource type, address, or env gate.
5. **Check `reference/glossary.yaml`** for a term's meaning.
6. **Check `context/open-questions.md`** for known unknowns / deferred systems.
7. **Cite** the file + section (and the `docs/*.md` ledger or `reference/decomp` address when relevant).
### How to reason about / reconstruct something novel
1. **Ground it in the decomp.** Read the raw `FUN_xxxx` in `reference/decomp/all/part_*.c`; map
`FUN_`/`DAT_`/`this+0xNN` to engine symbols via BT headers + the WinTesla MUNGA source +
`game/reconstructed/CLASSMAP.md` + RP's parallel code.
2. **Check the gotchas.** `context/reconstruction-gotchas.md` — layout (shadow/alias/phantom
fields, resource mismatch), linkage (`/FORCE`), dtor-epilogue, databinding trap. Most bugs are here.
3. **RULE: no stand-ins.** The full logic IS in the pseudocode; a "gap" is an unfilled stub, not a
hole. Never write placeholder logic — read the decomp. Bring-up scaffolding is marked + temporary.
4. **Verify honestly.** `static_assert`-lock layouts; run env-gated; read `btl4.log`; cdb on crashes.
Tag claims with the evidence tier; flag T3/T4.
5. **Persist insights.** A genuinely new finding → add it to the right `context/*.md` (+ the `docs/`
ledger for detail), with an evidence tier. Keep the knowledge base current — this is a mandate,
not an afterthought.
### Quick Lookup
| User asks about... | Read this file |
|---|---|
| What the project is, engine, platform, goal | `context/project-overview.md` |
| Missing BT source, decompilation strategy | `context/source-completeness.md` |
| The 4.10 literal-source manifest (per-TU function/size/address map) | `reference/BT410_SOURCE_MANIFEST.md` (tool: `tools/manifest410.py`; log: `phases/phase-03-bt410-source-manifest.md`) |
| The WinTesla Windows port (renderer/audio/HAL) | `context/wintesla-port.md` |
| Build / run / debug / repo layout / env gates | `context/build-and-run.md` + `context/decomp-reference.md` §6 |
| Asset formats (MOD/SKL/ANI/BMF/BSL/SLD/…) | `context/asset-formats.md` |
| BGF geometry, LODs, CONN/PCONN, ramps | `context/bgf-format.md` |
| A layout/linkage/databinding BUG | `context/reconstruction-gotchas.md` |
| The reconstruction method / workflow | `context/reconstruction-method.md` |
| Walking, gait, ground model, collision | `context/locomotion.md` |
| Subsystems, the factory, heat/weapons/power | `context/subsystems.md` |
| Damage zones, targeting, firing, death | `context/combat-damage.md` |
| Experience / simulation modes (novice/standard/veteran/expert), heat-model + novice gates | `context/experience-levels.md` |
| Rendering, LODs, materials, sky, shadows, beams | `context/rendering.md` |
| The death/respawn translocation warp (tsphere vortex) | `context/translocation-warp.md` |
| First-person cockpit canopy (*_cop) + the eyepoint camera | `context/cockpit-view.md` |
| Cockpit gauges / MFD HUD | `context/gauges-hud.md` |
| Multiplayer, replication, netcode | `context/multiplayer.md` |
| **The OPERATOR CONSOLE + RELAY** — how to launch it, ports, seats, the round/launch lifecycle, re-arm | `context/operator-console.md` |
| Running a session as sysop (operator-facing how-to) | `docs/OPERATOR_GUIDE.md` |
| Pod hardware, monitors, RIO, MFD surfaces, input remap (CONTROLS.MAP/XInput) | `context/pod-hardware.md` |
| Glass cockpit desktop dev layer (BT_GLASS/BT_STEAM gates, PadRIO, miniconsole, Steam) | `context/glass-cockpit.md` |
| Steam internet MP (the wire seam, identity tokens, lobby) | `context/steam-networking.md` |
| Content archives, maps, where data lives | `context/content-archives.md` |
| 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 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` |
---
## Evidence Tiers (adapted for reverse-engineering)
Tag individual claims (not sections) inline: `[T1]`, `[T2]`, … A claim inherits the **highest
(least certain)** tier of its inputs. Flag T3/T4 to the user.
| Tier | Label | Meaning |
|---|---|---|
| **T0** | Engine/header truth | A fact from the WinTesla MUNGA/L4 source or a BT header — the actual code we compile against. Authoritative. |
| **T1** | Decompiled + verified | Read from the binary pseudocode (`reference/decomp`) AND confirmed — disasm, `static_assert` offset lock, or cross-checked vs the RP analog. |
| **T2** | Reconstructed + runtime-verified | Implemented in `game/reconstructed/` and verified LIVE (combat un-regressed, gauge renders, log confirms the value). |
| **T3** | Best-effort / guarded | Reconstructed but not fully verified — a marked stand-in or a guarded raw-offset read. Runs, fidelity uncertain. |
| **T4** | Unconfirmed hypothesis | A claim about the binary's intent not yet decompiled/verified; a guess. |
---
## 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.
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
a link error. Grep the link log when a build "succeeds" but crashes at a garbage call target.
- **Don't reconstruct dtor compiler-glue** — the trailing base/member-dtor calls run the chain twice.
- **`static_assert`-lock every reconstructed layout** against the binary's offsets/sizeof.
- **A `+0x128`-style owner offset is the subsystem ROSTER, not the segment table.**
- **RULE: no stand-ins** — read the decomp; a gap is an unfilled stub.
- **Keep the knowledge base current** — new durable findings → `context/*.md` (+ `docs/` for detail).
- **Correct errors when you find them.** The context system is a reconstruction — some claims are
wrong. When the decomp / binary / engine source (ground truth) CONTRADICTS a claim in `context/*.md`
(or the glossary/`docs/`), FIX it — don't just work around it. Then **grep the other topic files for
the same wrong claim and sweep it** (a mistaken belief is often repeated across files), re-run
`scratchpad/checkctx.py`, and note the correction. Prefer this over silently distrusting a file.
(Archetype: the `0xBD3` "gates the valve/message routes" claim was wrong — 0xBD3 is a damage/explosion
hub; the real gate is the owning `BTPlayer` at `mech+0x190`. Verified from the decomp, then swept.)
## DO NOT
- Present T3/T4 claims as established without flagging the tier.
- Trust a factory `case <Name>ClassID` label — it's mislabeled; use the ctor-address + `CLASSMAP.md`.
- Issue `DestroyEntityMessage` on a mech death (the wreck STAYS; removal = the P5 teardown crash).
- Conclude "gauge not built" from an early process kill — the gauge renderer builds LAZILY.
- Use `DebugStream` (ReconStream, a no-op) for a log — use `DEBUG_STREAM`.
- Confuse the two console programs. **"Launch the console" = `python tools/btoperator.py`** (the
PySide6 operator GUI). `tools/btconsole.py` is the HEADLESS relay that GUI spawns — it has no
window, and backgrounded it has no stdin, so operator commands are dead. See
`context/operator-console.md`.
---
## Project Structure
```
bt411/
├── CLAUDE.md # this router (was the 2236-line monolith)
├── README.md # build of record
├── context/ # topic files (curated knowledge graph)
│ ├── decomp-reference.md # offsets/ClassIDs/addresses/env gates (the "equations" hub)
│ ├── reconstruction-gotchas.md # the systemic bug classes (conventions hub)
│ ├── open-questions.md # deferred systems + get-from-Nick
│ └── <topic>.md # one concept each
├── reference/
│ ├── glossary.yaml # terms/acronyms
│ ├── decomp/ # raw Ghidra pseudocode (the source-of-truth)
│ └── ghidra_scripts/ # the exporters
├── docs/ # PROGRESS_LOG.md (the full old CLAUDE.md) + detailed running ledgers
├── phases/ # restructuring / investigation logs
├── engine/ game/ content/ tools/ # the actual port (see README.md)
```
### How to add content
- **New finding on an existing topic** → edit the `context/*.md` (+ the `docs/` ledger for full detail).
- **New topic** → new `context/*.md` with YAML frontmatter; add to the Quick Lookup table + cross-refs.
- **Split** a topic file that exceeds ~25k tokens into finer subtopics.
- **New investigation** → a `phases/*.md` log.
- The `docs/*.md` ledgers stay as the DETAILED logs; `context/*.md` are the curated digests that route to them.
## Topic file format
```markdown
---
id: topic-id
title: "Title"
status: established | provisional | deferred | living
source_sections: "CLAUDE.md §X; docs/FILE.md; reference/decomp addresses"
related_topics: [other-topic-a, other-topic-b]
key_terms: [glossary-term-1]
open_questions: ["tracked uncertainty"]
---
# Title
## sections…
## Key Relationships
- Uses: [[other-topic]] · Feeds: [[other-topic]]
```