diff --git a/context/_ROUTER-DRAFT.md b/context/_ROUTER-DRAFT.md new file mode 100644 index 0000000..17873c6 --- /dev/null +++ b/context/_ROUTER-DRAFT.md @@ -0,0 +1,153 @@ +# BattleTech Pod-Port — Reverse-Engineering Expert Knowledge Base + +> ⚠️ **DRAFT of the proposed slim `CLAUDE.md`.** This is the router that would REPLACE the +> 2236-line `CLAUDE.md`, moving its deep knowledge into `context/*.md`. It is a draft for review +> — the live `CLAUDE.md` is unchanged until this is approved + the remaining topic files are +> migrated. See `phases/phase-01-context-restructure.md` for the migration plan + status. + +**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/`. + +--- + +## 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 `[[wiki-links]]`** and `related_topics` frontmatter to adjacent topics. +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 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` | +| Rendering, LODs, materials, sky, shadows, beams | `context/rendering.md` | +| Cockpit gauges / MFD HUD | `context/gauges-hud.md` | +| Multiplayer, replication, netcode | `context/multiplayer.md` | +| Pod hardware, monitors, RIO, MFD surfaces | `context/pod-hardware.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` | +| Detailed running ledgers | `docs/RECONCILE.md`, `docs/GAUGE_COMPOSITE.md`, `docs/HARD_PROBLEMS.md`, `docs/SUBSYS_PLAN.md`, `docs/P3_LOCOMOTION.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). + +## DO NOT +- Present T3/T4 claims as established without flagging the tier. +- Trust a factory `case 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`. + +--- + +## 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 +│ └── .md # one concept each +├── reference/ +│ ├── glossary.yaml # terms/acronyms +│ ├── decomp/ # raw Ghidra pseudocode (the source-of-truth) +│ └── ghidra_scripts/ # the exporters +├── docs/ # detailed running ledgers (RECONCILE, GAUGE_COMPOSITE, HARD_PROBLEMS, …) +├── 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]] +``` diff --git a/context/bgf-format.md b/context/bgf-format.md new file mode 100644 index 0000000..98c4300 --- /dev/null +++ b/context/bgf-format.md @@ -0,0 +1,64 @@ +--- +id: bgf-format +title: "BGF Model Format (DIV-BIZ2 geometry) + the render gotchas" +status: established +source_sections: "docs/BGF_FORMAT.md (full spec); CLAUDE.md §5, §10 render-fidelity notes" +related_topics: [asset-formats, rendering, decomp-reference] +key_terms: [BGF, DIV-BIZ2, PMESH, CONN, PCONN, LOD, material-ramp, SV_SPECIAL] +open_questions: + - "Replacement-LOD selection for NON-additive multi-LOD models (hot-spot/reference semantics)" +--- + +# BGF Model Format + +`.BGF` = **`DIV-BIZ2`** little-endian nested Tag-Length-Value geometry. Full byte-level parsing +spec (verified against all 1275 content `.BGF` files) lives in **`docs/BGF_FORMAT.md`** — this +topic captures the structure + the hard-won gotchas that cost real debugging. Loader of record: +`engine/MUNGA_L4/bgfload.cpp` (game-callable via `LoadBgfFile`); standalone reference reader +`DivLoader/VGCDivLoader.cpp`. Tag names: `dsys/PFBIZTAG.H`. + +## Structure +`OBJECT → LOD → PATCH (geogroup) → PMESH`. Interleaved float32 verts; **QUAD faces (must +triangulate `[a b c][a c d]`)**; materials referenced by `library:material` name (resolved +against the map's `.BMF` libs). Damage/articulation are **name-driven via `SV_SPECIAL` (0x2037) +`dz_*` tokens**, not stored geometry. [T1] + +## Indices — the CONN/PCONN story (two corrections) +A PMESH carries faces in `CONNECTION_LIST` (0x0047) and/or `PCONN_LIST`: +- `CONNECTION_LIST` is a **FLAT TRIANGLE LIST** (3 indices/face) — NOT "one polygon, fan-triangulate" + (the original doc was wrong; fan-triangulating turned buttec's 115 tris into 341 fan-garbage). + Corpus-verified: ALL 3488 CONN chunks have n%3==0. [T2] +- **CONN and PCONN COEXIST** — a pmesh can carry quads/hexes in PCONN PLUS loose triangles in + CONN. **370 of 841 GEO models** had mixed pmeshes silently dropping their CONN triangles until + both were processed always (the "turret base missing panels" bug). `ppf=6` hexagons exist. [T2] + +## LODs — the √3 decode +Each LOD chunk carries a `0x2046` header = **[near..far) viewing band**. The board selected the +LOD whose band contains the camera distance. +- **√3 range decode:** every stored band value is the authored euclidean distance ÷ √3 (92% of + the corpus is exactly nice-number/√3; hand-conversion typos prove it). Multiply parsed bands by + `√3` (`kLodRangeScale=1.7320508`). [T2] +- **Metric = the instance-transformed OBJECT ORIGIN distance**, ONE shared `d` for all LODs of an + object (a bounding-sphere-SURFACE metric desyncs sibling bands — wrong). [T2] +- **ADDITIVE_LODS** (object-level `SV_SPECIAL` token, 135 pod BGFs — all arena structures): at eye + distance d, EVERY LOD with `d < OutDist` draws (near detail ADDS onto coarser massing). Non-additive + keep first-LOD-only (the 2007 engine's own behavior). [T2] +- **PUNCH** (patch-level `SV_SPECIAL`): cutout geogroups — black texels = HOLES (alpha-test in the + opaque pass). Coplanar shells inside one LOD z-fight → depth-bias by submission ordinal. [T2] + +## Shading model (from libDPL DPLTYPES.H) +Shading is selected PER-GEOMETRY by vertex type: +- **No-normal geometry** (XYZ_UV / XYZ_RGBA_UV: mesas/ground/sky/buildings/mech, WHITE baked + verts) is UNLIT, colorized by the material's 2-endpoint **RAMP** (see [[material-ramp]]). +- **Normal-bearing geometry** (XYZ_N_UV: ~150 vehicle/missile files) is LIT by the map light. +- **Applying the ramp to lit geometry = the "dusty white blobs" bug** — gate on `hasNormals`. [T2] + +## Skeleton assembly +A mech is not one BGF — it's a `.SKL` skeleton (a DCS tree of joints) whose each joint's +`Object=part.bgf` is loaded + posed, then animated by `.ANI` clips. See [[asset-formats]] / +[[locomotion]] for skeleton + animation. [T1] + +## Key Relationships +- Detailed spec: `docs/BGF_FORMAT.md`. +- Consumed by: [[rendering]] (the D3D9 draw path), [[locomotion]] (skeleton parts). +- Related: [[asset-formats]] (BMF/BSL/SKL/ANI), [[decomp-reference]]. diff --git a/context/decomp-reference.md b/context/decomp-reference.md new file mode 100644 index 0000000..543abef --- /dev/null +++ b/context/decomp-reference.md @@ -0,0 +1,169 @@ +--- +id: decomp-reference +title: "Decomp Reference — resource types, ClassIDs, offsets, addresses, env gates" +status: established +source_sections: "CLAUDE.md §10c; game/reconstructed/CLASSMAP.md; reference/decomp/all/part_*.c" +related_topics: [subsystems, combat-damage, reconstruction-gotchas, build-and-run, gauges-hud] +key_terms: [ClassID, resource-type, subsystem-roster, BTL4OPT, factory] +open_questions: + - "0xBCE weapon family fully mapped? (GaussRifle : Emitter, FireWeapon is a no-op in this build)" +--- + +# Decomp Reference + +The quantitative hub for the reconstruction: resource-type enum, ClassID map, mech struct +offsets, key `FUN_` addresses, and env gates. Cite a section here rather than re-deriving. +Everything is `[T1]` (decompiled + byte-verified) unless tagged otherwise. The living +source-of-truth is `reference/decomp/all/part_*.c`; the class map is +`game/reconstructed/CLASSMAP.md`. + +--- + +## 1. Resource lookup + +`ResourceFile::FindResourceDescription(name, type, index=-1)` (`FUN_00406ff8` = find-by-type-and-name; +`FUN_00407064` = `SearchList(resourceID, type)`). Lookup is **by NAME or by ID**. `Mech::ResolveAnimationClip(prefix,suffix)` builds a name and calls it. + +⚠ A clip_id from `ResolveAnimationClip` is a **DIRECT resource ID** → `FindResourceDescription(clip_id)`, +NOT `SearchList(clip_id, 16)` (which treats the arg as a LIST → walks garbage → AV). [T1] + +### Resource type enum +| Type | Value | Holds | +|---|---|---| +| SkeletonStream | — | `.SKL` skeletons | +| DamageZoneStream | **0x14** | damage-zone records (Mech ctor reads count + array) | +| ModelList | **18** | model/mech names (`bhk1`, `madcat`, …) | +| AnimationResourceType | **16 (0x10)** | animation clips (lowercase: `blhrrl`=904, `blhrrr`=905, `blhwwli`=910, `blhrrli`=916) | +| damage-zone segment list | 0x14 | per-zone segment list | +| critical-subsystem segment | 0x1e | crit subsystem segments | + +Animation clip names in `BTL4.RES` are **lowercase**. [T1] + +--- + +## 2. ClassID map (subsystem factory) + +Base 3000 = **0xBB8**. The factory switches on the resource ClassID; the `case ClassID` +LABELS are **systematically mislabeled** — trust the `// FUN_004xxxxx` ctor-address comment +reconciled via `CLASSMAP.md`, not the case label. [T1] + +| ClassID | Real class | ctor @ | Notes | +|---|---|---|---| +| 0xBB9 | **Mech** | @004a2d48 (`Mech::Make`) | sizeof 0x854 (binary); reconstructed 0x638 | +| 0xBBD | **Condenser** | @4ae568 | valve; `case`="?" | +| 0xBBE | **AggregateHeatSink** (heat-sink BANK) | @4ae8d0 | GUID 0x50e590, sizeof 0x1E4; `case`=SensorClassID | +| 0xBC0 | **Reservoir** | @4af408 | `case`=Condenser | +| 0xBC1 | **Generator** | @4b… | power bus | +| 0xBC2 | **PoweredSubsystem** | @004b0f74 | sizeof 0x31C | +| 0xBC3 | **Sensor** | — | `case`=MyomersClassID | +| 0xBC4 | **Gyroscope** | @004b3778 | DEFERRED (NaN integrator; reverted to stub) | +| 0xBC5 | **Torso** | @004b6b0c | sizeof 0x280; `case`=SinkSource | +| 0xBC6 | **Myomers** | @4b8fec | `case`=Actuator; INERT un-stub | +| 0xBC8 | **Emitter** | @004b1d18 | sizeof 0x478; energy weapon | +| 0xBCB | **AmmoBin** | — | `case`=JumpJet; sizeof 0x22C | +| 0xBCD | **ProjectileWeapon** | @4bc3fc | sizeof 0x448 (`case`≠ this — see VDATA.h) | +| 0xBCE | **GaussRifle** | @4bdcb4 | : Emitter; FireWeapon is a **no-op** in this build | +| 0xBD0 | **MissileLauncher** | @4bcff0 | sizeof 0x44C | +| 0xBD3 | **SubsystemMessageManager** | — | DEFERRED (WAVE 8; the valve/message control hub @mech+0x190) | +| 0xBD4 | **PPC** (: Emitter) | — | same FireWeapon as Emitter; `factory calls it GaussRifle` | +| 0xBD6 | **HUD** | — | alloc 0x2a4; `case`=MechDisplay-ish | +| 0xBD8 | **Searchlight** | — | `case`=LegSubsystem | +| 0xBDC | **MechTech** | — | alloc 0x104 | +| 0xBDE | **ThermalSight** | — | `case`=MechDisplay | + +Identify a class at runtime with `sub->GetClassID()` (== `sub+4`). [T1] + +### Class hierarchy (the two branches that share only MechSubsystem) +- **Heat leaf:** `PoweredSubsystem : HeatSink : HeatableSubsystem : MechSubsystem` (Emitter/PPC/Sensor/Myomers/weapons chain here). [T1] +- **Watcher branch:** `Torso/Gyroscope/Searchlight/ThermalSight/HUD : PowerWatcher : HeatWatcher : MechSubsystem`; `AmmoBin : HeatWatcher`. Uses a 0xC connection + 0x54 alarm (NOT the 4/8-byte heat-leaf types). [T1] + +--- + +## 3. Mech struct offsets (`Mech*`, binary layout) + +Reconstructed access MUST use named members/accessors — these raw offsets are the BINARY's, +valid for reading the decomp, NOT for our compiled layout (see [[reconstruction-gotchas]]). [T1] + +| Offset | Field | Notes | +|---|---|---| +| +0x11c / +0x120 | `damageZoneCount` / `damageZones[]` | inherited Entity; Mech ctor populates the array | +| +0x124 / +0x128 | `subsystemCount` / `subsystemArray[]` | the [[subsystem-roster]] (NOT the segment table) | +| +0x190 | `messageManager` | the 0xBD3 SubsystemMessageManager (damage guard / valve+message route source) | +| +0x260 / +0x26c | `localOrigin` = { Point3D linearPosition; Quaternion angularPosition } | ORIGIN.h:15; position@+0x100 in some subsystem views | +| +0x300 | segment table | skeleton `EntitySegment`s (muzzle sites, joints) | +| +0x37c | target world `Point3D` | aim source | +| +0x388 | target `Entity*` | HasActiveTarget() gate | +| +0x38c | targeted sub-zone (int) | -1 = whole | +| +0x42c | `shadowJointNode` (Joint*) | ShadowJointName @model+0xB4 | +| +0x438 | torso subsystem cache | HUD/mapper read it | +| +0x5b4 | HUD subsystem cache | (factory case 0xBD6 writes param_1[0x16d]) | +| +0x650 | `deathAnimationLatched` / crashed flag | | +| +0x7cc | `damageableSubsystems` chain | the condenser chain (GUID 0x50e4fc) RecomputeCondenserValves iterates | + +`mech+0x100` (a subsystem-view of localOrigin.linearPosition): `.x@+0x100, .z@+0x108`. [T1] + +--- + +## 4. Damage delivery + +- `Entity::TakeDamageMessage(id, size, inflictingEntityID, zone, Damage&)` → `target->Dispatch(&msg)`. +- Base `Entity::TakeDamageMessageHandler` **IGNORES zone==-1** — send a VALID zone index (the Mech cylinder-lookup override for unaimed hits is not reconstructed). [T1] +- `class Damage { damageType(enum Collision/Ballistic/Explosive/Laser/Energy), damageAmount, damageForce, surfaceNormal, impactPoint, burstCount }`. [T1] +- Weapon effect id: **"explode" = 13** (`Explosion::Make`). [T2] +- `DestroyEntityMessage(id,size)` removes an entity — but a killed mech STAYS (a WRECK); death = a STATE transition (`SetGraphicState(DestroyedGraphicState)`), NOT removal. Issuing removal-on-death is the P5 teardown bug (do not). [T2] + +--- + +## 5. Weapon fire-rate constants (recovered) + +From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed as x87 float10). [T1] +- `DischargeTime` = beam-on time. ER-medium laser (BLH) = **0.2 s**. +- `RechargeRate` = recharge time in **seconds exactly** (the two ln5 factors cancel): ER-S 1.0 / ER-M 2.0 / ER-L 4.0 / PPC 5.0. +- One BLH ER-M cycle = 0.2 + 2.0 = **2.2 s**; ~12 lasers staggered → aggregate ≈ 5/s. +- `WeaponRange` 500 (ER laser), `DamageAmount` 3.5, `PipColor` 1.0 0.0 0.0 (RED). +- Charge constants: `_DAT_004bb3c4`=1.0, `_DAT_004bb3b8`=1e-4, `_DAT_004bb3b4`=0.5; RatedVoltage (GENRATOR.SUB)=1e4; fire threshold `seekVoltage[rec]=0.8*RatedVoltage`. + +--- + +## 6. Env-var gates (runtime; default OFF unless noted) + +The authentic stack is DEFAULT-ON (`BTEnvOn`, mechrecon.hpp): `BT_GAIT_CUTOVER`, `BT_GAIT_SM`, +`BT_COLLISION`, `BT_REAL_CONTROLS` default ON — set `=0` to fall back. ⚠ Gates check +`getenv(name) != NULL`, so `BT_X=""` (empty) still ENABLES; use `Remove-Item Env:\BT_X`. + +| Var | Effect | +|---|---| +| `BT_FORCE_THROTTLE` | auto-walk forward (no key) | +| `BT_SPAWN_ENEMY` | spawn a target mech 120u ahead along the spawn facing | +| `BT_FORCE_FIRE` | auto-fire on cooldown | +| `BT_ASSERT_TO_DEBUGGER` | route CRT asserts to the debugger, not a modal box | +| `BT_HEAPCHECK` | whole-heap validation on every alloc/free (O(n²) at mission load — SLOW) | +| `BT_PROBE_BGF=` | boot-time BGF loader probe, exits after | +| `BT_LOG=` | per-instance log file (in content/) | +| `BT_GAUGE_ATTR_LOG` | `[attr] OK/NULL` per config binding | +| `BT_GAUGE_SKIP_LOG` | `[gskip] ` per unregistered gauge widget the parse skips | +| `BT_VALVE_LOG` | condenser valve flow distribution | +| `BT_SECTOR_LOG` | SectorDisplay Make/Execute | +| `BT_NET_TRACE` | `[net-tx]/[net-rx]/[net-upd]` MP tracing | +| `BT_DEV_GAUGES` | render the 6 pod MFD surfaces in a separate dev window | + +Full render/locomotion gates (BT_RAMP, BT_MATPRI, BT_CULL, BT_SHADOW_*, BT_LODSEL, BT_ADDLOD, +BT_PUNCH, …) are catalogued in [[rendering]]. + +--- + +## 7. Tools + +- `tools/disas2.py [len]` — capstone disassembly of BTL4OPT.EXE at a VA (recovers x87 math + Ghidra drops; folds known call targets + float constants). THE tool for any `FUN_` the + assert-anchored exporter didn't capture (e.g. `@0x4ae464` MoveValve, `@0x4ca07c` Execute). +- `tools/disas2.py`-style PE reads: `scratchpad/rdva.py` (4-byte VA read), `rdtbyte.py` (80-bit float10). +- `reference/ghidra_scripts/ExportBTSource.java` — the headless assert-anchored decompiler. +- `reference/ghidra_scripts/DecompVSS.java` — headless address-list decompiler (any skipped fn). +- `tools/btconsole.py` — the MP console emulator. `tools/mapscan.py`/`resscan.py` — content scanners. + +--- + +## Key Relationships +- Feeds: [[subsystems]] (factory + ClassIDs), [[combat-damage]] (damage delivery), [[gauges-hud]] (attribute binding). +- Verified against: [[reconstruction-method]] (the decomp loop), [[reconstruction-gotchas]] (why raw offsets fail). diff --git a/context/gauges-hud.md b/context/gauges-hud.md new file mode 100644 index 0000000..bd9d8cd --- /dev/null +++ b/context/gauges-hud.md @@ -0,0 +1,86 @@ +--- +id: gauges-hud +title: "Cockpit Gauges / MFD HUD system" +status: established +source_sections: "docs/GAUGE_COMPOSITE.md (full map); CLAUDE.md §8" +related_topics: [reconstruction-gotchas, decomp-reference, subsystems, pod-hardware] +key_terms: [gauge, methodDescription, attribute-pointer, GaugeRenderer, MFD, PCC] +open_questions: + - "Valve/message CONTROL routes need the 0xBD3 SubsystemMessageManager (WAVE 8)" + - "Status-message queue (StatusMessagePool) is a NULL stub -> MessageBoard is empty" +--- + +# Cockpit Gauges / MFD HUD + +The pod's cockpit instruments: the secondary MFD (radar/heat/comm), the 5 mono MFDs, and the +engineering screens. Full map + per-widget history in **`docs/GAUGE_COMPOSITE.md`**. **The gauge +system is COMPLETE** at the registration+binding level: all 50 config attribute bindings resolve +(0 NULL) and every config gauge primitive is registered + built (0 parse-skips). [T2] + +## Architecture (three layers) +1. **The config** `content/GAUGE/l4gauge.cfg` — a text file the engine `GaugeInterpreter` parses + (`BuildConfigurationFile → Initialize → GetProcedureBody → ParsePrimitive`). Each mech's gauge + tree is built from the label `GetGameModel()+"Init"` (e.g. `bhk1Init`), which invokes shared + blocks (`MechInit`, `Secondary1`) that `configure` the ports and call gauge primitives. [T1] +2. **Widgets** — each `keyword(...)` in the config resolves to a `MethodDescription` in + `BTL4MethodDescription[]` (btl4grnd.cpp). An UNREGISTERED keyword is **parse-skipped** (never + built). Base primitives (`numeric`/`digitalClock`/`rankAndScore`/`vertBar`-base/`segmentArc`) + are ENGINE (L4GAUGE.cpp); BT-specific ones (`vertBar`/`map`/`pilotList`/`GeneratorCluster`/ + `sectorDisplay`/`prepEngr`/`messageBoard`/`vehicleSubSystems`/the ColorMapper family) are + reconstructed + registered. [T2] +3. **Data binding** — gauges bind to game state by NAME via the engine [[attribute-pointer]] + system: the config's `Subsystem/Attribute` (e.g. `HeatSink/CurrentTemperature`) resolves + through `ParseAttribute → FindSubsystem (stricmp GetName()) → GetAttributePointer`. A class + PUBLISHES an attribute via a `AttributeID` enum + `static AttributePointers[]` + (`ATTRIBUTE_ENTRY`) + `GetAttributeIndex()` chained to its parent. ⚠ DENSE-TABLE HAZARD (see + [[reconstruction-gotchas]] §11). [T2] + +## Reconstructing a widget — the recipe +`MethodDescription Class::methodDescription = { "keyword", Class::Make, { ParameterDescription +rows } };` + a line in `BTL4MethodDescription[]` before `&BTL4ChainToPrevious`. The static +`Make(int port, Vector2DOf pos, Entity*, GaugeRenderer*)` reads `methodDescription.parameterList[]`, +allocs (`operator new(binSize)` or plain `new`), placement-news the ctor. Then ctor/dtor/ +TestInstance/BecameActive/Execute. **Gotchas that bite EVERY widget:** +- Container-Execute must override (§10 gotchas) — else `Gauge::Execute` aborts. +- `/FORCE` trap — a prose-only slot AVs on first call. +- Databinding trap — never raw-read owner offsets; use a bridge (`BTGetSubsystemAuxScreen`, …). +- ReconStream no-op — use `DEBUG_STREAM` for logs, not `DebugStream`. +- Lazy build — wait for the gauge window before concluding "not built" (`BT_GAUGE_SKIP_LOG`). [T2] + +## The completed widgets (this project's gauge wave) +- **Attributes published:** HeatSink table (Degradation/Failure/NormalizedPressure/CoolantMassLeakRate/ + ValveSetting/…), PoweredSubsystem (InputVoltage), MechWeapon (OutputVoltage), Mech (LinearSpeed, + radar Position/Quaternion), Sensor (RadarPercent), **AggregateHeatSink (AmbientTemperature=300 — + the LAST NULL)**. [T2] +- **Widgets reconstructed + registered:** ColorMapper family (cmHeat/cmCrit/cmArmor/multiArmor), + headingPointer, vertBar (VertTwoPartBar), segmentArcRatio, oneOfSeveralPixInt, map (radar), + PlayerStatus, vehicleSubSystems (the engineering cluster panels), LeakGauge, vertNormalSlider, + **pilotList** (Comm KILLS/DEATHS), **GeneratorCluster**, **SectorDisplay** (radar SECTOR X/Z + read-out — live), **PrepEngrScreen** (12 engineering-screen label overlays), **MessageBoard** + (comm ticker — deferred-empty). [T2] +- **Condenser valve gauge:** ValveSetting→coolantFlowScale reads the authentic **1/N** + (`RecomputeCondenserValves`, FUN_0049f788, was a no-op stub). [T2] + +## Dev composite (off-pod) +`BT_DEV_GAUGES` renders the 6 pod [[MFD]] surfaces in a separate 960×384 window (bit-plane masks +over one shared `SVGA16` pixelBuffer; `SVGA16::DrawDevSurface`). On the POD they come from +`SETENV.BAT`/`L4GAUGE` on the real multi-adapter hardware (`FindBestAdapterIndices`/`BuildWindows`, +intact). The `overlay` port (SectorDisplay lives there) shares the `sec` physical surface via a +different bit-plane (0x00C0). [T2] + +## Authentically-static (do NOT "fix") +`currentTemperature ~77` (huge thermalMass ~1.39e6 → a 3.5-heat shot rounds to zero); +Degradation/Failure temps are fixed markers; AmbientTemperature 300; CoolantMassLeakRate 0 on a +pristine mech (damage-gated); cmArmor/cmCrit all-green (undamaged solo player — BT is PvP-only); +RANK 1 solo; MessageBoard empty (no status messages exist in bring-up). The Heat MFD is +authentically NEAR-STATIC, not a live heat-accumulation display. [T2] + +## Remaining = DATA FEEDS, not widgets (deferred systems) +The valve/message CONTROL routes need the **0xBD3 SubsystemMessageManager** (WAVE 8); the +status-message queue needs **StatusMessagePool** (a NULL stub); `SeekVoltageGraph`'s 4 Seek* +attrs are a cluster-child (not config-called). See [[open-questions]]. [T2] + +## Key Relationships +- Full history: `docs/GAUGE_COMPOSITE.md`. +- Uses: [[attribute-pointer]] + [[reconstruction-gotchas]]; reads [[subsystems]] state. +- Renders on: [[pod-hardware]] MFD surfaces. diff --git a/context/open-questions.md b/context/open-questions.md new file mode 100644 index 0000000..9fe5535 --- /dev/null +++ b/context/open-questions.md @@ -0,0 +1,71 @@ +--- +id: open-questions +title: "Open Questions — deferred systems, unknowns, get-from-Nick" +status: living +source_sections: "CLAUDE.md §9; docs/HARD_PROBLEMS.md; per-subsystem deferral notes" +related_topics: [source-completeness, subsystems, multiplayer, combat-damage, locomotion] +--- + +# Open Questions + +Known unknowns + deliberately-deferred systems. For each: what's known, what's missing, what +would resolve it, status. Deferred ≠ broken — most are marked-and-guarded stand-ins with the +authentic path scoped. + +## From Nick (external — gating for some paths) +- **[GATING] The BT game SOURCE CODE** — the missing implementation `.cpp` (mech, subsystems, + mapper, HUD, app; see [[source-completeness]]). Without it, Route A (recompile original) is + impossible; we reconstruct from the binary instead. Likely on a backup/dev drive. Status: OPEN. +- Confirmation the PodPC image is the complete content master. (Low priority — the runtime + `BTL4.RES` already has 8 maps + the full mech anim set.) Status: OPEN. +- Pod specifics for Phase 8: the 7-monitor driver setup, the RIO cockpit I/O protocol, current + Win10+wrapper pod config. Status: OPEN. + +## Deferred subsystems / feeds (authentic path scoped, marked in code) +- **0xBD3 SubsystemMessageManager (WAVE 8)** — the per-mech message/damage hub @mech+0x190. Gates: + the condenser valve CONTROL route (`MoveValve` @0x4ae464 is reconstructed but dormant — no + caller + its guard reads the messmgr); the MessageBoard source; the Myomers advanced-damage + gate; gauge control-mode cycling. What resolves it: reconstruct 0xBD3 + wire the message routes. +- **StatusMessagePool (NULL stub, btstubs.cpp:62)** — the per-player status-message queue. + BTPlayer+0x1dc is never populated → MessageBoard is empty (authentic for bring-up). What + resolves it: wire the pool + decode `AddStatusMessage @0042e580` + the `Player__StatusMessage` + record ({messageId@+0xc, nameEntity@+0x10}). +- **Gyroscope (0xBC4)** — layout + joint-I/O reconstructed, but the ctor field-init + the two + integrators (IntegrateEyeJoint @004b2ec0 / IntegrateBody @004b30ec) are incomplete → writes NaN + to the root joint. Reverted to a stub. A full subsystem-sim reconstruction (bigger than Torso). +- **MechControlsMapper look/eyepoint commit** — reconstructed but its offsets collide with declared + members; arbitrate before enabling (no port consumer yet). The fire-trigger-through-mapper is + still the `gBTWeaponTrigger` bring-up. +- **Myomers authentic coupling** — the structural un-stub is INERT (mover feed + heat-gen no-op). + Real coupling needs the 0xBD3 advanced-damage gate + `MoverAttach` routing into the LIVE + JointedMover (must be reconciled with the gait cutover first). +- **SeekVoltageGraph** — 4 Seek* attrs unpublished (a cluster-child, not config-called; non-blocking). + +## Locomotion / combat polish (non-gating) +- Authentic per-mech TURN-RATE constant (currently a bring-up constant rate). +- Body-callback gimp handlers (states 16-19, targets 0x70b2/0x7161 undecoded → fall back to stand); + airborne callbacks (`FUN_004a6344`/`FUN_004a7970`). +- Wall-block-vs-climb tuning; collision DAMAGE application (computed, not applied in bring-up). +- Death: reconstruct `DeathShutdown` (RP `VTV::DeathShutdown` analog) + collapse anim + destroyed + skin. The wreck-stays behavior we ship is faithful; do NOT issue DestroyEntityMessage on death. + +## Multiplayer (Phase 7 / P6) +- Cross-pod COMBAT (target a replicant + route damage to the owning master). +- Replicant GAIT animation (replicants don't animate joints yet — derive from replicated velocity). +- The pod-LAN config (real IPs, bare-IP pilot entries). The WinSock2 TCP stack (L4NET.CPP) is + reconstructed + smoke-tested on one box (two instances share a world, movement replicates). + +## Rendering follow-ups (non-blocking) +- 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 + to shared terrain mats); needs the terrain-material model understood. +- RGBA4444 alpha (TREE.BSL cutout) decoded but not alpha-tested outside punch batches; VTX/TGA paths. + +## Content build sub-project (low priority) +- Lab/other-build maps (`des`/`burnt`/`frstrm`) are source-only (`.map` in CONTENT/BT/MAPS/); would + need compiling into a RES via the DOS `btl4tool.exe`. The 8 RES maps cover testing. + +## Key Relationships +- Feeds from: every subsystem/render topic (their deferral notes collect here). +- Gating master: [[source-completeness]] (the missing BT source). diff --git a/context/reconstruction-gotchas.md b/context/reconstruction-gotchas.md new file mode 100644 index 0000000..91f4956 --- /dev/null +++ b/context/reconstruction-gotchas.md @@ -0,0 +1,166 @@ +--- +id: reconstruction-gotchas +title: "Reconstruction Gotchas — the systemic bug classes (check these FIRST)" +status: established +source_sections: "CLAUDE.md §5a, §10c; docs/HARD_PROBLEMS.md; docs/RESOURCE_AUDIT.md; gauge-wave notes" +related_topics: [reconstruction-method, decomp-reference, subsystems, combat-damage, gauges-hud] +key_terms: [shadow-field, databinding-trap, Wword-trap, FORCE-trap, dtor-epilogue, bridge, attribute-pointer] +open_questions: + - "Which reconstructed classes still carry un-audited raw-offset reads?" +--- + +# Reconstruction Gotchas + +The reconstruction is a **layout + linkage** problem as much as a logic problem. Our compiled +classes are NOT byte-identical to the 1995 binary, and the BT link uses `/FORCE`, so a whole +family of bugs is **silent** — garbage that happens to be non-fatal, or a runtime AV with no +link error. When a reconstructed class misbehaves, walk this checklist FIRST; the answer is +usually here, not in the logic. + +> **The core rule (RULE: no stand-ins):** the full game logic IS in the pseudocode — a "gap" +> is a reconstruction stub not yet filled, never a hole in the original. Never write +> placeholder logic for an apparent gap; read the decomp. (User: "there are no gaps, just +> work to be done.") Bring-up scaffolding (env-var paths, explosion-for-beam) is clearly +> marked and meant to be REPLACED, never to substitute for reading the decomp. [T2] + +--- + +## 1. Shadow field — re-declaring an engine-base field (THE most common) + +**Symptom:** a field reads `0xCDCDCDCD` (fresh-heap fill) even though the ctor "sets" it; or an +object over-sizes past its factory alloc. **Cause:** the reconstruction re-declared a field the +engine base already owns, at the *binary's* offset. Two failures: (a) the copy **shadows** the +base — the engine ctor writes the base field, the reconstruction reads its own uninitialised +copy; (b) it lands at a **different offset** than the binary assumed. + +**Fix:** delete the re-declaration; use the inherited member/accessor. Examples: `Mech` +`damageZoneCount`/`damageZones` (shadowed `Entity`'s → zones never built); `Mech__DamageZone` +`structureLevel`→engine `damageLevel`; the whole `HeatableSubsystem` de-shadow +(`statusFlags`→`simulationFlags`, `destroyed`→`simulationState`, `statusBits`→`ForceUpdate()`). +[T2] + +## 2. Wword(N) — a global scratch bank, NOT this+N + +`mechrecon.hpp:192` defines `Wword(int i)` as `static BTVal bank[0x400]; return bank[i&0x3ff]`. +Any `Wword(N)` used for OBJECT access reads/writes a shared global, not `this+i*4`. e.g. +`(Mech*)Wword(3)` for a zone's owner → garbage; use `GetOwningSimulation()`. [T2] + +## 3. Databinding trap — raw offsets read garbage + +Our compiled layout != the 1995 binary, so `*(T*)(obj+0xNN)` reads garbage for ANY object we +compile. This is WHY shadow fields fail and why raw subsystem reads (e.g. a gauge reading +`owner+0x438`) return junk. **Fix:** use compiled named members/accessors; for a cross-TU raw +op, use a **bridge** (§8). A `+0x128`-style owner offset in subsystem code is the +[[subsystem-roster]] (`subsystemArray`), NOT the segment table — check every `GetSegment(int)` +in a reconstructed subsystem ctor. [T2] + +## 4. Resource-struct layout mismatch (sibling of the shadow bug) + +**Symptom:** RESOURCE fields read garbage (silently — a `heatSinkIndex` reading `10.0f`). +**Cause:** `*__SubsystemResource` structs overlay pre-built 256-byte records loaded VERBATIM at +fixed offsets, so OUR struct must match the binary byte-for-byte. Breaks two ways: (a) wrong +inheritance base (the resource must mirror the CLASS hierarchy — `HeatableSubsystem`'s resource +inherits `MechSubsystem__SubsystemResource` 0xE4, not `Subsystem::SubsystemResource` 0x30); (b) +under-sized fields (a 12-byte record field typed as a 4-byte `ResourceID`). **Diagnose:** log +compiled offsets `(char*)&res->field - (char*)res` vs the binary's; dump raw record bytes as +int+float. **Lock:** `static_assert(offsetof(...)==0xNN)` + `static_assert(sizeof(...)==0xNN)`. +The 33-agent RESOURCE_AUDIT fixed 8 such bugs (docs/RESOURCE_AUDIT.md). [T2] + +## 5. Alias / phantom / interior fields (object layout) + +- **Alias field:** a subclass member re-declaring an inherited slot the ctor reuses under a new + name (Condenser `refrigerationOutput`==inherited `massScale@0x160`; Emitter + `outputVoltage`==`rechargeLevel@0x320`). → delete, use the inherited name. +- **Alarm-interior field:** a value the binary reads at `alarm+0x14` modeled as a separate + member (HeatSink `heatState@0x184` == `heatAlarm.GetLevel()`). → route to the accessor. +- **Phantom field:** a member at an offset PAST the object (Generator `shortFlag@0x25C` is + really `*(owner+0x190)+0x25c` the msg-manager). → remove; read the real source. +`GaugeAlarm54` = 0x54 (the real `AlarmIndicator`; STATUS level at +0x14, so +`subsystem+0x184 == heatAlarm+0x14 == GetLevel()`); `SubsystemConnection` = 0xC. [T1] + +## 6. /FORCE trap — unresolved symbol → runtime AV, not a link error + +`/FORCE` turns an unresolved external (or a prose-only vtable slot) into a **runtime AV near +`__ImageBase`**, NOT a link error. **When a /FORCE build crashes with a garbage call target +near the image base, grep the link log for "unresolved external" — the "successful" build is +lying.** Corollary: a bridge fn / a `.data` fn-ptr callback MUST have a real (stub) definition. +A `SetVideoPathPriority` defined in an anonymous namespace → internal linkage → unresolved in +another TU → stubbed by /FORCE → AV in `LoadMissionImplementation`. [T2] + +## 7. Dtor-epilogue rule — do not reconstruct compiler glue + +In a decompiled DESTRUCTOR, the trailing member-dtor calls (`FUN_xxx(this+N, 2)`), the base-dtor +call (`FUN_xxx(this, 0)`), and the `(flags&1) && operator delete(this)` tail are COMPILER GLUE. +Reconstruct only the body ABOVE them; C++ re-emits member+base destruction at the closing brace. +An explicit base-dtor call runs the whole `~JointedMover → ~Mover → ~Entity` chain **TWICE** = +the P5 double-free (re-`delete[]`s `collisionLists`, re-runs `DeletePlugs` over the freed segment +table). ONE bug = BOTH the death-row crash AND the app-exit crash. [T2] + +## 8. Bridges — the databinding-safe escape hatch + +When TU A needs a raw-offset-safe op but its local RECON stubs collide with the real class +headers, put the op in a **bridge**: a free function in a complete-type TU, `extern`-declared in +A. Examples: `BTResolveWeaponMuzzle` (mech4.cpp — a complete-Mech TU with the segment API), +`BTRecomputeCondenserValves` (heatfamily_reslice.cpp — sees Condenser), `BTResolveMessageBoard` +(btplayer.cpp — complete BTPlayer), `BTGetSubsystemAuxScreen` (powersub.cpp — casts through the +real PoweredSubsystem). Keep the alloc SIZE + special-cache when swapping a factory case. [T2] + +## 9. Message-handler chaining + entity validity + +- A reconstructed class's `MessageHandlers` set must be built **chained to the parent's** + (`Receiver::MessageHandlerSet(Entity::GetMessageHandlers())`). An empty default-ctor set has + no parent chain → `Receiver::Receive` finds no handler → every inherited message (TakeDamage!) + is **silently dropped**. [T2] +- `Entity::Dispatch` delivers synchronously only for a VALID master; an invalid entity `Post`s a + deferred event that never fires. A manually-spawned entity (no CheckLoad handshake) must call + `SetValidFlag()` itself — else EVERY message defers forever (the replicant / spawned-dummy / + MessageBoard-source class of bug). [T2] + +## 10. Container-Execute must override (gauges) + +The 2007 engine `Gauge::Execute` base is `Fail("not overridden")` → `abort()` (GAUGE.cpp:598); +`GuardedExecute`'s SEH **cannot catch abort()**. So a container/parent gauge MUST override +`Execute` (even as a no-op) AND override `BecameActive` with a **non-inactivating** body (the +default `GaugeBase::BecameActive` inactivates). A `GraphicGaugeBackground`-derived widget +(PrepEngrScreen/BackgroundBitmap) has NO Execute virtual → the hazard doesn't apply; there the +overridden slot is `BecameActive`. [T2] + +## 11. Dense-table hazard (attribute publishing) + +`AttributeIndexSet::Build` leaves gap slots uninitialized and `Find` strcmps EVERY slot → a +published attribute table MUST be a **dense prefix** from the parent's `NextAttributeID`; a gap +AVs. Fill gaps with a shared read-only pad member. Same for a class's `AttributeID` enum. +[T2] + +## 12. Verification gotchas (don't fool yourself) + +- **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 + window** before concluding. (Cost a long detour this session.) [T2] +- **ReconStream is a no-op:** `btl4gau3.cpp`'s `DebugStream` is the `ReconStream` whose + `operator<<` is `{ return *this; }` — it DISCARDS everything. Use the engine `DEBUG_STREAM` + (what heat.cpp uses) for a log that reaches the BT_LOG file. [T2] +- **Head-on repro hides intermittent bugs:** a straight ram gives 1 clean result; the bug shows + on GLANCING/sliding/rough-terrain contact. Reproduce with an angled/terrain-crossing approach. + [T2] +- **`static_assert` not runtime `Check`:** a runtime `Check(sizeof<=alloc)` in a factory bridge + does NOT fail the build (it's a runtime assert → heap overflow at construction). Use a + compile-time `static_assert` sizeof lock. [T2] +- **Engine-class new member:** a NEW member on a 2007 engine class (d3d_OBJECT, DPLRenderer) MUST + be initialized in EVERY ctor init-list (debug heap fills 0xCDCDCDCD → an uninit flag reads + TRUE); and any device state a special draw path sets must be save/restored exactly. Deleting + stale `.obj`s fixes layout-mismatch corruption when a base class grows. [T2] + +--- + +## Diagnostic recipe (the standard loop) +1. Read the RAW decomp `reference/decomp/all/part_*.c` for the `FUN_xxxx`. +2. Map `FUN_`/`DAT_`/`this+0xNN` to engine symbols via BT headers + WinTesla MUNGA source + + `CLASSMAP.md` + RP's parallel code. +3. Write the REAL reconstruction; `static_assert`-lock the layout. +4. Build; run env-gated; read `btl4.log`; cdb on any crash (0xCDCDCDCD=uninit, 0xFEEEFEEE=freed). +5. For exhaustive multi-function analysis: a read-only Workflow (understand), then implement hands-on. + +## 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/phases/phase-01-context-restructure.md b/phases/phase-01-context-restructure.md new file mode 100644 index 0000000..a56ad7d --- /dev/null +++ b/phases/phase-01-context-restructure.md @@ -0,0 +1,60 @@ +# Phase 01 — Progressive Context Restructure + +**Goal:** adapt the `spark-lesson`/`expert-seed.md` progressive-context pattern to the bt411 +port, turning the 2236-line monolithic `CLAUDE.md` into a thin router + a `context/*.md` +knowledge graph. Motivation: CLAUDE.md is loaded every session and is too large; the deep RE +knowledge should load on-demand. + +## Design decisions +- **In-place** (context/ + reference/ at bt411 root, mirroring spark-lesson), NOT a separate repo. +- The router serves DOUBLE duty: expert-agent knowledge routing AND the load-bearing WORK + directives (build recipe, "keep current" mandate) — bt411's CLAUDE.md drives active work, unlike + spark-lesson's pure knowledge base. +- The existing **`docs/*.md` ledgers stay** as the DETAILED running logs (RECONCILE 1555 lines, + GAUGE_COMPOSITE 627, HARD_PROBLEMS, SUBSYS_PLAN, P3_LOCOMOTION, …). The `context/*.md` files are + the CURATED digests that route into them. (docs/ ≈ spark-lesson's phases/; context/ ≈ its context/.) +- **Evidence tiers adapted for RE:** T0 engine/header truth · T1 decompiled+verified · T2 + reconstructed+runtime-verified · T3 best-effort/guarded · T4 unconfirmed hypothesis. +- **Non-destructive:** the live `CLAUDE.md` is UNCHANGED until the migration is complete + approved. + The proposed router is `context/_ROUTER-DRAFT.md`. + +## Status + +### ✅ Foundation built (this pass) +- `reference/glossary.yaml` — ~45 terms (platform/engine, formats, scene/entity, reconstruction). +- `context/decomp-reference.md` — the quantitative hub: resource-type enum, ClassID map, mech + offsets, damage delivery, weapon constants, env gates, tools. +- `context/reconstruction-gotchas.md` — the 12 systemic bug classes (the conventions/DO-NOT hub). +- `context/bgf-format.md` — geometry format + CONN/PCONN + LOD √3 + ramp shading (frames docs/BGF_FORMAT.md). +- `context/gauges-hud.md` — the gauge/MFD system (frames docs/GAUGE_COMPOSITE.md); the just-completed wave. +- `context/open-questions.md` — deferred systems + get-from-Nick. +- `context/_ROUTER-DRAFT.md` — the proposed slim CLAUDE.md (identity, protocols, quick-lookup, + evidence tiers, conventions, structure). + +### ☐ Remaining topic files (the next tranche — migrate from CLAUDE.md §/docs) +| topic | source in CLAUDE.md / docs | +|---|---| +| `project-overview.md` | §1-3 | +| `pod-hardware.md` | §3 | +| `content-archives.md` | §4, §4a | +| `asset-formats.md` | §5, docs/ASSET_PIPELINE.md | +| `source-completeness.md` | §5a, docs/BT_SOURCE_STATUS.md | +| `wintesla-port.md` | §5b, §8 | +| `build-and-run.md` | §10a, §10a-bis | +| `reconstruction-method.md` | §10b | +| `locomotion.md` | §7, docs/P3_LOCOMOTION.md | +| `subsystems.md` | §10d, docs/SUBSYS_PLAN.md, docs/VEHICLE_SUBSYSTEMS.md | +| `combat-damage.md` | §10c, docs/HARD_PROBLEMS.md | +| `rendering.md` | §10 render-fidelity notes (large) | +| `multiplayer.md` | §7 Phase 7, §8 P6 | + +### ☐ Swap (after review + full migration) +1. Move the current `CLAUDE.md` deep content into the topic files above. +2. Replace `CLAUDE.md` with `context/_ROUTER-DRAFT.md` content (preserving the git history of the old). +3. Verify: a fresh session reads the router → quick-lookup → topic files load on demand. + +## Notes +- The router keeps the build one-liners + the "keep current" mandate so active port work is + uninterrupted. The detailed §10 combat-bring-up narrative migrates to `combat-damage.md` + + `locomotion.md` + `rendering.md` + the docs/ ledgers it already references. +- Cross-references use `[[topic-id]]`; the glossary uses `related_topics`. diff --git a/reference/glossary.yaml b/reference/glossary.yaml new file mode 100644 index 0000000..b526f85 --- /dev/null +++ b/reference/glossary.yaml @@ -0,0 +1,343 @@ +# BattleTech Pod-Port — Glossary +# Terms, acronyms, class/format names used across the reverse-engineering + reconstruction. +# Each entry: definition, kind (concept|format|class|tool|file), where it's discussed, related terms. +# Evidence tiers (see CLAUDE.md): T0 engine/header truth · T1 decompiled+verified · +# T2 reconstructed+runtime-verified · T3 best-effort/guarded · T4 unconfirmed hypothesis. + +# --------------------------------------------------------------------------- +# Platform / engine +# --------------------------------------------------------------------------- +MUNGA: + kind: concept + definition: The proprietary VWE game engine shared by BattleTech (BT) and Red Planet (RP). Renderer, mover/animation, math, scene graph. Lives in engine/MUNGA/. + related_terms: [MUNGA_L4, L4, BT, RP, WinTesla] + related_topics: [project-overview, wintesla-port] + +MUNGA_L4: + kind: concept + definition: The hardware-abstraction layer (HAL) for MUNGA — video-out, input, timing, file I/O, audio. "L4" is the HAL brick. On the port it targets Win32/Direct3D9. Lives in engine/MUNGA_L4/. + related_terms: [MUNGA, L4, L4D3D, DPL] + related_topics: [wintesla-port, rendering] + +L4: + kind: concept + definition: Shorthand for the MUNGA_L4 HAL. Prefixes its files (L4VIDEO, L4NET, L4CTRL, L4GAUGE, L4RIO, L4SERIAL, ...). + related_terms: [MUNGA_L4, RIO] + +BT: + kind: concept + definition: BattleTech — the primary port target. VWE arcade pod game, Tesla platform release 4.10 (~1995-96). Game logic = CODE/BT (mostly MISSING as source; reconstructed from the binary). + related_terms: [RP, MUNGA, BTL4OPT, source-completeness] + related_topics: [project-overview, source-completeness] + +RP: + kind: concept + definition: Red Planet — the sibling VWE pod game sharing the MUNGA engine. Its game source (VTV/VTVMPPR/WEAPSYS) SURVIVES in the WinTesla archive and is the live reference for BT's parallel systems (VTV≈mech, WEAPSYS≈weapons). + related_terms: [BT, WinTesla, MUNGA] + related_topics: [wintesla-port, source-completeness] + +Tesla-platform: + kind: concept + definition: VWE's arcade pod hardware/software platform. Original BT ran 32-bit DOS under a DPMI32 extender (Borland C++ 5 + TASM32) on Novell DOS pods with a Division IG image-generator board. + related_terms: [IG-board, DPL, pod] + related_topics: [pod-hardware, project-overview] + +pod: + kind: concept + definition: The physical arcade cockpit. 2 video cards -> 7 monitors (main 3D 800x600, radar 640x480, five mono MFDs as one 1280x480 span). Cockpit I/O ("RIO") over serial. Now runs Windows 10 + a Voodoo/Glide wrapper. + related_terms: [RIO, MFD, IG-board] + related_topics: [pod-hardware] + +RIO: + kind: concept + definition: The pod cockpit I/O — joystick X/Y, throttle, pedals, buttons over serial COM (L4RIO, L4SERIAL). Remapped to keyboard/gamepad on a dev box. + related_terms: [pod, L4, MechControlsMapper] + related_topics: [pod-hardware, locomotion] + +IG-board: + kind: concept + definition: Division "IG" image-generator board (dual EISA) — the original 3D renderer, driven by the closed-source libDPL.lib + VREND microcode. The port REPLACES it with a dpl_* -> Direct3D9 shim (done in WinTesla as L4D3D). + related_terms: [DPL, L4D3D, WinTesla] + related_topics: [rendering, wintesla-port] + +DPL: + kind: concept + definition: libDPL / dpl_* — the closed Division rendering API MUNGA called for 3D (dpl_*) and 2D (dpl2d_*). Shipped binary-only. Replaced by L4D3D in WinTesla; a few unported dpl_* effects (weapon beams) are re-implemented in the port. + related_terms: [IG-board, L4D3D, libDPL] + related_topics: [rendering] + +L4D3D: + kind: file + definition: MUNGA_L4/L4D3D.cpp — the WinTesla renderer that replaced the IG board / libDPL with Direct3D9. THE hardest infrastructure, already done in the WinTesla archive. + related_terms: [DPL, WinTesla, rendering] + related_topics: [wintesla-port, rendering] + +WinTesla: + kind: concept + definition: The Windows port of the MUNGA engine found in the "Elsewhen RP411" archive (VS2005). Renderer bypass (L4D3D), audio (OpenAL+libsndfile), L4 HAL, and full Red Planet game logic all work. BT's game layer is ABSENT (must be reconstructed). The bt411 repo builds on this engine. + related_terms: [MUNGA, RP, L4D3D, source-completeness] + related_topics: [wintesla-port, source-completeness, build-and-run] + +# --------------------------------------------------------------------------- +# Binary / decompilation +# --------------------------------------------------------------------------- +BTL4OPT: + kind: file + definition: BTL4OPT.EXE — the real BT game binary (1.24 MB, uncompressed PE32, Phar Lap TNT). Unwrapped from btrel410.exe (a PKZIP self-extractor). The decompile ORACLE — retains class names, Class::Method strings, and source-file paths in asserts. In content/. + related_terms: [decomp, btrel410, oracle, BTL4.RES] + related_topics: [source-completeness, decomp-reference, reconstruction-method] + +decomp: + kind: concept + definition: The raw Ghidra pseudo-C recovered from BTL4OPT.EXE, in reference/decomp/all/part_*.c. The source-of-truth every reconstruction is verified against ("there are no gaps, just work to be done"). + related_terms: [BTL4OPT, oracle, reconstruction-method] + related_topics: [reconstruction-method, decomp-reference] + +oracle: + kind: concept + definition: Using the original binary (BTL4OPT.EXE) as a behavioral reference — running it under cdb, or reading its decompiled logic — to verify a reconstruction is behavior-equivalent. + related_terms: [decomp, BTL4OPT, cdb] + related_topics: [reconstruction-method] + +BTL4.RES: + kind: file + definition: The compiled BT resource archive (3.23 MB). Holds maps (8 loadable), animation clips (lowercase names, blhrrl=904), models, materials, skeletons, damage-zone/subsystem records. Resolved by name or ID via FindResourceDescription. In content/. + related_terms: [BTL4OPT, EGG, resource-type] + related_topics: [content-archives, decomp-reference] + +cdb: + kind: tool + definition: The x86 Windows debugger (Debuggers/x86/cdb.exe). Used to trace faulting stacks. Debug CRT marks fresh heap 0xCDCDCDCD (uninitialised) and freed 0xFEEEFEEE — invaluable for "was this ever constructed?". + related_terms: [BT_HEAPCHECK, decomp] + related_topics: [build-and-run, reconstruction-method] + +# --------------------------------------------------------------------------- +# Asset formats +# --------------------------------------------------------------------------- +BGF: + kind: format + definition: DIV-BIZ2 little-endian nested-TLV geometry. OBJECT->LOD->PATCH(geogroup)->PMESH; interleaved float32 verts; QUAD faces (must triangulate); materials by library:material name; damage/articulation name-driven via SV_SPECIAL dz_* tokens. + related_terms: [DIV-BIZ2, PMESH, CONN, PCONN, LOD, SV_SPECIAL, material-ramp] + related_topics: [bgf-format, asset-formats] + +DIV-BIZ2: + kind: format + definition: The Division nested Tag-Length-Value container magic used by BGF (geometry) and BMF (materials). Chunk tags catalogued in dsys/PFBIZTAG.H (0x2046=LOD_DISTANCE, 0x2037=SV_SPECIAL, 0x2047/0x2048 reference/transition). + related_terms: [BGF, BMF, PFBIZTAG] + related_topics: [bgf-format, asset-formats] + +BMF: + kind: format + definition: DIV-BIZ2 (FILETYPE=1) material+texture LIBRARY (no pixels). Material -> DIFFUSE color + MATERIAL_TEXTURE -> texture -> TEXTURE_MAP image basename. Tag 0x18 BITSLICE selects a BSL sub-image; 0x28 ramp ref; 0x26 emissive. + related_terms: [BGF, BSL, material-ramp, VMF] + related_topics: [asset-formats, rendering] + +BSL: + kind: format + definition: DIV-BSL2 BIT-SLICED texture container. w*h 32-bit texel words hold SIX independent 4-bit GRAYSCALE sub-images (nibble pair-swapped, byte 0 pad) OR RGB444/RGBA4444 (sliceType 7/8). The BMF 0x18 BITSLICE tag picks the slice. Decoding it wrong = the "rainbow/graffiti mech" bug. + related_terms: [BMF, VTX, TGA, material-ramp] + related_topics: [asset-formats, rendering] + +SKL: + kind: format + definition: INI joint skeleton. [joint] parent=, Type=(hingex/y/z|ball|balltranslate), Object=part.bgf, dzone=, tranx/y/z, pitch/yaw/roll. Build a DCS tree; neutral pose ~ translations. + related_terms: [ANI, DCS, joint, MOD] + related_topics: [asset-formats, locomotion] + +ANI: + kind: format + definition: INI keyframe animation. [HEADER] framecount/framerate(30)/skeletonfile; [frameN] joint->rx ry rz radians; [RootTranslation] per-keyframe x y z where .z = forward root SPEED (units/s). Walk speed is animation-driven, not physics. + related_terms: [SKL, RootTranslation, locomotion] + related_topics: [asset-formats, locomotion] + +MOD: + kind: format + definition: INI mech/object definition — skeleton, skins, collision, gauges, physics, animations, class. The .MOD physics (MoverMass/drag) governed thrust VEHICLES + collisions, NOT walk gait. + related_terms: [SKL, ANI, SLD, DZM] + related_topics: [asset-formats] + +SLD: + kind: format + definition: Collision solids. Terrain is modeled AS BoxedSolid collision volumes (Block/YCyl/Cone/Ramp/Wedge). The ground model + object collision both query these via the engine BoundingBoxTree. BT solids ship ZERO tiles. + related_terms: [MoveAndCollide, BoundingBoxTree, ground-model] + related_topics: [combat-damage, locomotion] + +DZM: + kind: format + definition: INI damage-zone -> material map (mech skin damage states). + related_terms: [damage-zone, DZ] + related_topics: [combat-damage] + +GIM: + kind: format + definition: Vector MFD/radar line maps (+ .GAT color table). Cockpit HUD vector assets. + related_terms: [GAT, PCC, gauge] + related_topics: [gauges-hud] + +PCC: + kind: format + definition: ZSoft PCX 8-bit gauge rasters — the cockpit HUD bitmap assets (fonts helv15/helv42, label strips, dials). + related_terms: [GIM, gauge, PCX] + related_topics: [gauges-hud] + +VMF: + kind: format + definition: DIV-VIZ2 material SOURCE (VIDEO/MAT/.../BTFX.VMF etc.). Carries the SCROLL animation + MAP{} image binding for effects (beams, muzzle, smoke, sky). The .BMF only names the texture; the .VMF binds it + scroll speed. THE key effects asset. + related_terms: [BMF, BSL, beam] + related_topics: [rendering] + +material-ramp: + kind: concept + definition: The IG board colours no-normal geometry (terrain/mesas/sky/buildings/mech) by a material's 2-endpoint RAMP (dpl_SetMaterialRamp) — texture luminance indexes a low->high gradient. Normal-bearing geometry (vehicles) is LIT instead. Applying the ramp to lit geometry = the "white blobs" bug. + related_terms: [BMF, BSL, emissive, hasNormals] + related_topics: [rendering] + +# --------------------------------------------------------------------------- +# Scene / entity model +# --------------------------------------------------------------------------- +DCS: + kind: concept + definition: Dynamic Coordinate System — a transform node in the scene graph. A skeleton builds a DCS tree; each joint is a DCS. + related_terms: [SKL, joint, scene] + related_topics: [asset-formats, locomotion] + +joint: + kind: class + definition: Engine Joint node (JOINT.h). GetJointType (hinge 0..2 / ball 4..5 / balltranslate 5); SetRotation(Radian|EulerAngles), SetTranslation. Resolved by name via Mech::ResolveJoint -> GetSegment(name) -> JointSubsystem::GetJoint. + related_terms: [SKL, DCS, EntitySegment, Torso] + related_topics: [locomotion] + +EntitySegment: + kind: class + definition: A skeleton segment (288 bytes) in the mech's segmentTable. Maps a joint + geometry. Muzzle SITE segments (siterugunport etc.) resolve to world muzzle positions via GetSegmentToEntity x localToWorld. NOT the subsystem roster. + related_terms: [joint, subsystem-roster, GetSegment] + related_topics: [locomotion, combat-damage] + +subsystem-roster: + kind: concept + definition: subsystemArray @mech+0x128 (count @0x124) — the inherited Entity table of the mech's Subsystem* instances (heat/weapons/gyro/sensor/...). A +0x128-style owner offset in subsystem code is the ROSTER, NOT the segment table (a classic bug source). + related_terms: [subsystem, factory, EntitySegment] + related_topics: [subsystems, reconstruction-gotchas] + +subsystem: + kind: class + definition: A mech component (Subsystem-derived). Built by the per-class factory (Mech ctor). Ticks a per-frame Performance/Simulation. Families: heat (HeatSink/Condenser/Reservoir/AggregateHeatSink), power (Generator/PoweredSubsystem), weapons (Emitter/PPC/ProjectileWeapon/MissileLauncher/GaussRifle), Sensor/Gyroscope/Torso/Myomers/Searchlight/ThermalSight/AmmoBin/MechTech/HUD. + related_terms: [subsystem-roster, factory, ClassID, PoweredSubsystem] + related_topics: [subsystems, decomp-reference] + +factory: + kind: concept + definition: The Mech-ctor per-class subsystem factory (switch on resource ClassID). The case LABELS are systematically MISLABELED — the real class is the ctor-address comment reconciled via CLASSMAP.md, not the case name. Each real class wired via a CreateSubsystem bridge. + related_terms: [ClassID, subsystem, CLASSMAP] + related_topics: [subsystems, decomp-reference] + +ClassID: + kind: concept + definition: RegisteredClass::ClassID — the per-subsystem-class id (base 3000=0xBB8). e.g. 0xBB9 Mech, 0xBBD Condenser, 0xBBE AggregateHeatSink(bank), 0xBC8 Emitter, 0xBD4 PPC. Read via sub->GetClassID() (sub+4). Full map in decomp-reference / CLASSMAP.md. + related_terms: [factory, subsystem, CLASSMAP] + related_topics: [subsystems, decomp-reference] + +master: + kind: concept + definition: A locally-simulated entity (the pod's own mech + spawned dummies). Ticks its full drive/subsystems and EMITS network update records. Contrast replicant. + related_terms: [replicant, viewpoint-entity, multiplayer] + related_topics: [multiplayer] + +replicant: + kind: concept + definition: A remote entity's local proxy (a peer pod's mech). Does NOT drive itself — it DeadReckons toward the master's replicated position. Must SetValidFlag() at creation or every message defers forever. + related_terms: [master, dead-reckon, multiplayer] + related_topics: [multiplayer] + +viewpoint-entity: + kind: concept + definition: application->GetViewpointEntity() — the local pod's own mech (the cockpit view + player controls). Gate player-only logic on `this == GetViewpointEntity()` (Mech::PerformAndWatch runs for EVERY mech). + related_terms: [master, MakeViewpointEntity] + related_topics: [combat-damage, locomotion] + +EGG: + kind: format + definition: The mission INI (.EGG): [mission] map= scenario= time= weather=, [pilots] roster (pilot=ip[:port] + per-pilot vehicle=/dropzone). map= resolved from BTL4.RES; a missing name null-derefs. Default test egg = DEV.EGG (map=grass, time=day). + related_terms: [BTL4.RES, mission, pilot] + related_topics: [content-archives, multiplayer] + +# --------------------------------------------------------------------------- +# Reconstruction concepts (the systemic bug classes live in reconstruction-gotchas.md) +# --------------------------------------------------------------------------- +shadow-field: + kind: concept + definition: A reconstructed class re-declaring a field its engine base already owns -> shadows the base (reads uninitialised 0xCDCDCDCD) AND lands at a different offset. THE most common reconstruction bug. Fix: use the inherited member/accessor. + related_terms: [databinding-trap, Wword-trap, alias-field, phantom-field] + related_topics: [reconstruction-gotchas] + +databinding-trap: + kind: concept + definition: Our compiled class layout != the 1995 binary, so a RAW offset read *(T*)(obj+0xNN) reads garbage. Use compiled named members / accessors / a bridge fn in a complete-type TU. The systemic reason raw-offset reads fail. + related_terms: [shadow-field, bridge, Wword-trap] + related_topics: [reconstruction-gotchas] + +Wword-trap: + kind: concept + definition: mechrecon.hpp Wword(int i) is `static BTVal bank[0x400]; return bank[i&0x3ff]` — a GLOBAL scratch bank, NOT this+i*4. Any Wword(N) used for object access reads/writes garbage. + related_terms: [databinding-trap, shadow-field] + related_topics: [reconstruction-gotchas] + +FORCE-trap: + kind: concept + definition: The BT link uses /FORCE, so an unresolved symbol (or a prose-only vtable slot) becomes a RUNTIME AV near __ImageBase, NOT a link error. When a /FORCE build crashes with a garbage call target near the image base, grep the link log for "unresolved external". + related_terms: [bridge, dtor-epilogue] + related_topics: [reconstruction-gotchas, build-and-run] + +dtor-epilogue: + kind: concept + definition: In a decompiled DESTRUCTOR the trailing member-dtor calls, the base-dtor call FUN_xxx(this,0), and the (flags&1)&&operator delete(this) tail are COMPILER GLUE — reconstruct only the body ABOVE them. An explicit base-dtor call runs the chain TWICE (the P5 double-free). + related_terms: [FORCE-trap, P5] + related_topics: [reconstruction-gotchas, combat-damage] + +bridge: + kind: concept + definition: A free function in a complete-type TU that does a raw-offset-safe operation the caller's TU can't (its local stubs collide). e.g. BTResolveWeaponMuzzle (mech4.cpp), BTRecomputeCondenserValves, BTResolveMessageBoard. The databinding-safe escape hatch. + related_terms: [databinding-trap, FORCE-trap] + related_topics: [reconstruction-gotchas, subsystems] + +attribute-pointer: + kind: concept + definition: The engine system binding cockpit gauges to game state by NAME. A class publishes attributes via a AttributeID enum (dense prefix from parent NextAttributeID) + static AttributePointers[] (ATTRIBUTE_ENTRY) + GetAttributeIndex() chained to parent. ParseAttribute->FindSubsystem->GetAttributePointer resolves the config binding. DENSE-TABLE HAZARD: a gap AVs. + related_terms: [gauge, methodDescription, dense-table] + related_topics: [gauges-hud, reconstruction-gotchas] + +methodDescription: + kind: concept + definition: A gauge widget's registration record: { "keyword", Class::Make, {ParameterDescription rows} }. Added to BTL4MethodDescription[] (btl4grnd.cpp). An UNREGISTERED keyword is parse-SKIPPED (never built). The Make factory allocs + placement-news the widget from config params. + related_terms: [gauge, attribute-pointer, GaugeRenderer] + related_topics: [gauges-hud] + +GaugeRenderer: + kind: class + definition: The engine gauge system. BuildConfigurationFile parses gauge\l4gauge.cfg via GaugeInterpreter (Initialize->GetProcedureBody->ParsePrimitive). Builds LAZILY -> a too-early process kill shows a false "not built". Composites onto the pod's SVGA16 bit-plane surfaces. + related_terms: [methodDescription, MFD, gauge, SVGA16] + related_topics: [gauges-hud] + +MFD: + kind: concept + definition: Multi-Function Display — the pod's mono cockpit screens (Heat/Comm/Mfd1/Mfd2/Mfd3), bit-plane MASKS over one shared SVGA16 pixelBuffer (Heat 0x4000, Comm 0x8000, Mfd1 0x0100, Mfd2 0x0400, Mfd3 0x1000, sec = palette low byte). Eng1-3 = engineering-mode alt planes. + related_terms: [gauge, GaugeRenderer, SVGA16, pod] + related_topics: [gauges-hud, pod-hardware] + +damage-zone: + kind: class + definition: Mech__DamageZone (engine DamageZone base). Armor model: damageLevel += damageAmount*damageScale[type], accumulates to 1.0 (destroyed). Built by the Mech ctor from the DamageZoneStreamResourceType (0x14) resource. Aimed hits route via Entity::TakeDamageMessage. + related_terms: [DZM, TakeDamage, DZ] + related_topics: [combat-damage] + +ground-model: + kind: concept + definition: The authentic 1995 vertical-position model (FUN_004a9b5c). NO GRAVITY — an ABSOLUTE per-frame ground SNAP via the zone BoundingBoxTree (FindBoundingBoxUnder), lift = 5% of volume X-width. Walls block by FULL FRAME REJECTION; crushable props pass with a crunch. + related_terms: [SLD, BoundingBoxTree, MoveAndCollide] + related_topics: [locomotion] + +RootTranslation: + kind: concept + 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]