# Adding a new 'Mech chassis — full workflow

How to add a new playable 'Mech chassis to BattleTech: FireStorm (MW4/Gameleap 5.03 engine).
Reconstructed 2026-07-01 from the repo itself; the previous maintainer ("MSL", MekTek-era)
marked **every registration point** with the comment **`// MSL ADD MECH`** — when in doubt:

```
grep -ri "MSL ADD MECH" Gameleap\
```

(~41 files; the authoritative checklist is the union of those sites, which this doc walks through.)

## The big picture

A chassis is spread across **three layers**, and all three must agree on one number — the
**Mech ID** (`M_<Name>`), an index into parallel arrays everywhere:

| Layer | What lives there | What it takes to apply changes |
|---|---|---|
| Engine code (`Gameleap\code`) | ID defines, per-mech stat/UI tables, string resources | Rebuild `MW4.exe` (+ `MW4pro.exe`, `ScriptStrings.dll`, `MissionLang.dll`) |
| Content source (`Gameleap\mw4\Content`) | The mech's asset folder, tables, build manifests, shell scripts | Repack resources (`-build` via a LAB_ONLY exe) + redeploy |
| Loose runtime assets (`Gameleap\mw4\hsh`) | 2D bitmaps (selection image, target-MFD, HUD) | Copy files (loaded from disk, not packed) |

Two spreadsheets in [BTFrstrm/](BTFrstrm/) support the process:
- **`MechInfo_5.04.xls`** — the design source of truth (stats for every chassis).
- **`scriptaddmech.xls`** (Frank Galatis) — a **generator workbook** with one sheet per target file
  (`MechLabHeaders`, `Conlobby.script`, `Netlobby.script`, `chassis.script`, `infobox.script`,
  `MechBay_Main.script`, `weapons.script`). It emits the tab-delimited per-mech rows you paste
  into those files, keeping all the index-aligned arrays consistent. Use it — hand-editing 10+
  parallel arrays is how IDs drift.

### ⚠️ Mech IDs are positional — decide placement first

Mech IDs are assigned **alphabetically** today (`M_Annihilator`=0 … `M_Zeus`=64, `NoMechID`=65),
and every table below — the `.tbl` files, `mechspecs.cpp`, `huddamage.cpp`, `coord.cpp`,
shell-script arrays — is a **parallel array indexed by that ID**. Two options when adding:

1. **Insert alphabetically and renumber everything after it** (what MSL did between releases —
   compare `MechLabHeaders_BK.h` where `M_Argus`=0 vs. current =4). Cleanest UI ordering; the
   generator xls exists precisely to make the renumber safe. Cost: saved player variants and
   old netgame compatibility break (acceptable between FireStorm releases).
2. **Append at the end** (new mech = old `NoMechID`). Preserves existing IDs, but the chassis
   list is no longer alphabetical and every list/table must still grow by one.

Either way, **all clients in multiplayer must run the same tables** — the mech index travels
over the wire (`m_nMechIndex`).

---

## Step 0 — Produce the art (external pipeline)

This is the long pole. Everything else is bookkeeping.

**3D assets** (per body part, exported from 3ds Max via the plugins in
[Gameleap/mw4/Binaries/3DSPlug-ins/](Gameleap/mw4/Binaries/3DSPlug-ins/)):
- Geometry `.erf` files, one per body part, **plus `_DAM.erf` damaged variants** (see
  [Gameleap/mw4/Content/Mechs/Argus/](Gameleap/mw4/Content/Mechs/Argus/) — note its parts are
  named `m3_*`: part meshes can be shared/reused across chassis).
- A cockpit **cage** erf (`<name>_cage.erf`).
- Collision: `<Name>_Skeleton.obb` (hierarchical) + `<Name>_Skeleton_SOLID.obb`.
- Skeleton/armature with the standard joint naming (`joint_luarm`, `joint_torsobelow`, …) and
  **weapon-mount sites** (`site_lmissileport`, `site_rugunport`, `site_light`, `site_eyepoint`, …).
- Animations: `animation\*.mw4anim` (walk/run/fall/getup set) + a `<Name>.animscript` state script.
- Textures registered in the texture pool (see Step 4).

**2D assets** (this is what the [Finished HUDS from J&J/](Finished%20HUDS%20from%20J&J/) folder
holds for ~13 pending chassis — MFD + Radar art):
- `hsh\Mechs\<name>.bmp` — mech-selection portrait (mechlab/lobby).
- `hsh\MFD\<name>.bmp` — target-damage MFD image (engine tiles these into a 1024×1024 atlas at
  128×128 per mech — [render.cpp:1375](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L1375)).
- `hsh\hud\<name>.bmp` and radar art under `hsh\radar\`.
- `Content\textures\HUD\<name>.tga` — the in-cockpit damage-readout texture (paper-doll).
- (Optional) footstep decal textures (`footsteps\<name>_default`, `_dirt`, … in the texture pool).

**Practical route:** clone the folder of an existing chassis with the same body plan, get it
in-game end-to-end, then swap in real art piece by piece.

---

## Step 1 — Create the content folder

`Gameleap\mw4\Content\Mechs\<Name>\` plus a `<Name>_destroyed\` sibling (the wreck model has its
own smaller `.data`/geometry). Files, using
[Argus](Gameleap/mw4/Content/Mechs/Argus/) as the template (all text, INI-style, `!Include`-able):

| File | Purpose |
|---|---|
| `<Name>.data` | The chassis definition: `Class=MechWarrior4::Mech`, `MechID=$(M_<Name>)`, `TechType`, `NameIndex=$(IDS_<NAME>)`, tonnage/trade value, `CanLoadECM/AMS/JumpJets/...` flags, death entity, OBB refs, lighting, effects |
| `<Name>.instance` | Runtime instance: pulls together Model/Armature/Subsystems/DamageObjects, power/armor/speed/heat ratings shown in UI |
| `<Name>.armature` | Joint hierarchy: rotations/translations/child links for every `joint_*`/`site_*` |
| `<Name>.contents` | Maps each joint to its part model in `armaturedata\*.data` (which reference the `.erf` meshes) |
| `<Name>.subsystems` | **Default loadout**: `[Engine]`, `[Armor]` (zone multipliers), `[Sensor]`, `[Torso]`, heat sinks, and the stock weapons — each weapon section names its `WeaponSubsystems\...` model, mount `Site=`, `InternalLocation=`, ammo |
| `<Name>.damage` | Damage zones per body part: armor values, splash scaling, **and the mechlab hardpoints** — `OmniSlots=`/`BeamSlots=`/`MissileSlots=`/`ProjectileSlots=` per zone define what the mechlab lets you fit |
| `<Name>.engine` | Engine subsystem: heat sinks, upgrade steps (`MPSPerUpgrade`, `TonsPerUpgrade`) |
| `<Name>.torso` | Torso twist/pitch joints, speeds, radii (uses `$()` macros from [MechTorso.defines](Gameleap/mw4/Content/Defines/MechTorso.defines)) |
| `<Name>.animscript` | Animation state machine |
| `armaturedata\`, `animation\`, `armaturevideo\` | Per-part `.data`, `.mw4anim` clips, support data |
| `*.erf`, `*_DAM.erf`, `*.obb` | Geometry + collision (from Step 0) |

`$(M_<Name>)` and `$(IDS_<NAME>)` resolve because
[ProjectSpecific.include](Gameleap/mw4/Content/ProjectSpecific.include) pulls in
`Defines\MissionLang.defines` and `ShellScripts\MechLabHeaders.h` for all content parsing.

---

## Step 2 — Assign the Mech ID (three places, must match EXACTLY)

The header itself warns: *"IF YOU CHANGE THESE, YOU MUST ALSO CHANGE THE CONSTANTS IN
MWCONST.ABI!!! YOU MUST ALSO CHANGE THE CHASSIS LOOKUP TABLE IN MECH.HPP/MECH.CPP"*.

1. **[MechLabHeaders.h](Gameleap/code/mw4/Code/MW4/MechLabHeaders.h#L112)** — add
   `#define M_<Name> <id>`, bump `LastMechID` and `NoMechID` (and note `M_CameraShip`/
   `EmptyMechID` float above `LastMechID` automatically). This file exists as **byte-identical
   copies** that must stay in sync:
   - `Gameleap\code\mw4\Code\MW4\MechLabHeaders.h` (compiled into the game)
   - [Gameleap/mw4/Content/ShellScripts/MechLabHeaders.h](Gameleap/mw4/Content/ShellScripts/MechLabHeaders.h)
     (`#include`d by shell scripts and by content `.data` parsing)
   - `Gameleap\mw4\Content\ShellScriptsDev\MechLabHeaders.h` (dev-scripts mirror)
   - `Gameleap\code\mw4\Code\mw4print\MechLabHeaders.h` (the mw4print tool keeps its own copy)
2. **[mwconst.abi](Gameleap/mw4/Content/ABLScripts/mwconst.abi#L470)** — the same `M_*` constants
   for the ABL scripting language (AI/bots use `GetMechType()` against these).
3. **Engine lookup tables** — Step 3 below.

---

## Step 3 — Engine code tables (rebuild required)

All in `Gameleap\code`, each marked `// MSL ADD MECH`; every array is sized by
`NoMechID`/`LastMechID+1`, so the compiler catches a missed row once the define is bumped.
There is also a runtime guard: `gosASSERT(NoMechID == (LastMechID - FirstMechID + 1))` at
[MW4Shell.cpp:14213](Gameleap/code/mw4/Code/MW4/MW4Shell.cpp#L14213).

| File | Table | Content |
|---|---|---|
| [Mech.cpp:339](Gameleap/code/mw4/Code/MW4/Mech.cpp#L339) | `Mech::s_mechSlots[NoMechID]` | **Execution slot** — memory/animation slot; new mechs *share* a slot with a similar chassis (e.g. Argus shares with Catapult). Slot enum in [MW4.hpp:228](Gameleap/code/mw4/Code/MW4/MW4.hpp#L228) |
| [mechspecs.cpp:4](Gameleap/code/mw4/Code/MW4/mechspecs.cpp#L4) | `g_aMechSpecs[NoMechID]` | `{ DNL_<NAME>, topSpeed, tonnage, IDS_ML_CH_<class>, DNL_<tech>, IDS_ML_<NAME>_INFO }` |
| [huddamage.cpp:55](Gameleap/code/mw4/Code/MW4/huddamage.cpp#L55) | `texturename[]`, `texuv[][11][4]`, `offset[][11][2]` | Cockpit damage paper-doll: texture-pool name (`hud\<name>`) + UV rects/offsets for the 11 damage zones of your HUD tga |
| [coord.cpp:2](Gameleap/code/CoreTech/Libraries/GameOS/coord.cpp#L2) | `texuv2[65][11][4]`, `offset2[65][11][2]` | Same paper-doll zones for the **target MFD** bmp (bump the `65`s) |
| [render.cpp:546](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L546) | `mechnames[]` | Base filename list for the 2D art atlas (`hsh\mfd\<name>.bmp`) |
| [MWApplication.cpp:18447](Gameleap/code/mw4/Code/MW4/MWApplication.cpp#L18447) | `s_paMechNames[]` | Name list used by the CTCL/dedicated path |
| [GameLobby.cpp:26](Gameleap/code/mw4/Code/mw4dedicatedui/GameLobby.cpp#L26) + `DedicatedUI.hpp` | `mechlist[64][256]` etc. | Dedicated-server UI mech list sizing |

**String resources** (two DLL projects + their content-side mirrors):

| Where | Add | Feeds |
|---|---|---|
| [StringResource.rc](Gameleap/code/mw4/Code/scriptstrings/StringResource.rc) + `resource.h` (scriptstrings project) | `DNL_<NAME> "<Display Name>"`, `IDS_ML_<NAME>_INFO "<mechlab blurb>"` | `ScriptStrings.dll`; IDs mirrored in [Content/ShellScripts/ScriptStrings.h](Gameleap/mw4/Content/ShellScripts/ScriptStrings.h) for the scripts |
| [MissionLangScript.rc](Gameleap/code/mw4/Code/MissionLang/MissionLangScript.rc) + `resource.h` (MissionLang project) | `IDS_<NAME>` | `MissionLang.dll`; ID number mirrored in [MissionLang.defines](Gameleap/mw4/Content/Defines/MissionLang.defines) (used by `NameIndex=$(IDS_<NAME>)` in the `.data`) |

---

## Step 4 — Content registration (tables, build manifests, texture pool)

All in `Gameleap\mw4\Content`, all marked `// MSL ADD MECH`:

1. **[Tables/MechChassisTable.tbl](Gameleap/mw4/Content/Tables/MechChassisTable.tbl)** —
   `<Display Name>=Mechs\<Name>\<Name>.data`. **Row order = Mech ID order** (parallel array;
   the mechlab reads class/tech per row via `TBL_GetResNames`).
2. **[Tables/MechTable.tbl](Gameleap/mw4/Content/Tables/MechTable.tbl)** — same key →
   `...\<Name>.instance`. (The `.mpt` MechPak variants of both tables are **commented out** in
   `core.build` — the `.tbl`s are live; keep the `.mpt`s in sync only if reviving MekPak support.)
3. **[core.build](Gameleap/mw4/Content/core.build)** (packs `resource\core.mw4`) — the mech block:
   ```
   //FireStorm:<Name>
   data=mechs\<name>\<name>.engine
   data=mechs\<name>\<name>.torso
   data=mechs\<name>\<name>.data
   data=mechs\<name>_destroyed\<name>_destroyed.data
   armature=mechs\<name>\<name>.contents
   subsystems=mechs\<name>\<name>.subsystems
   damage=mechs\<name>\<name>.damage
   instance=mechs\<name>\<name>.instance
   ```
   (The `MWTable=` lines near the end of core.build already pull in the two `.tbl`s.)
4. **[textures.build](Gameleap/mw4/Content/textures.build)** (packs `resource\textures.mw4`) —
   one `File=Mechs\<Name>\<part>.erf` line for **every** erf, including `_DAM` and the cage.
5. **[props.build](Gameleap/mw4/Content/props.build)** (packs `resource\props.mw4`) —
   `File=Mechs\<Name>\<Name>.animscript`.
6. **[textures/textures.hint](Gameleap/mw4/Content/textures/textures.hint)** — texture-pool
   entries: `[hud\<name>]` for the cockpit damage tga (put the tga in `Content\textures\HUD\`),
   plus body textures and optional `[footsteps\<name>_*]` entries.
7. **`hsh\` bitmaps** — drop `hsh\Mechs\<name>.bmp`, `hsh\MFD\<name>.bmp`, `hsh\hud\<name>.bmp`
   (loose files, loaded from disk at runtime — no packing step).

---

## Step 5 — Shell scripts (mechlab + lobby UI)

In [Gameleap/mw4/Content/ShellScripts/](Gameleap/mw4/Content/ShellScripts/) (and mirrored in
`ShellScriptsDev\` if you use the dev shell). These ship as text with the deployment and are
parsed at runtime — no exe rebuild, but a redeploy. **Generate the rows with
`BTFrstrm\scriptaddmech.xls`** — each sheet matches one file:

| Script | Per-mech data to extend |
|---|---|
| `MechBay\chassis.script` | `mech_num`, `class[]` (weight class per ID), tech arrays |
| `MechBay\gs_chassis.script` | `mech_num`, `create_limit` (arrays fill via `TBL_GetResNames` callback from the .tbl) |
| `MechBay\weapons.script` | `mech_num`, hardpoint label arrays (`special1_label[]`, …) and on-screen locations per zone (`head_location[]`, `leftarm_location[]`, …) |
| `MechBay\armor.script` | `mech_num`, armor-bar screen positions per zone (`head_bar[]`, …) |
| `MechBay\infobox.script` | `LAST_MECH_INFO_ID` + `temp_mech_id[]` info-string ↔ mech-ID mapping |
| `MechBay\MechBay_main.script` | `mech_name[]` (localized `DNL_*` per ID) |
| `ConLobby.script` | `MAX_ALLOWED_MECHS`, allowed-mech roster arrays, `ROOKIEMECH*` picks |
| `NetLobby.script` | `mechlist[]` / `chassis_names[]` (localized names, index-aligned) |

---

## Step 6 — Rebuild and repack

1. **Rebuild code** (VC6 — see `CLAUDE.md` STEP 3):
   ```
   msdev Gameleap\code\mw4\Code\MechWarrior4.dsw /MAKE "MW4Application - Win32 Release" /OUT build.log
   msdev Gameleap\code\mw4\Code\MechWarrior4.dsw /MAKE "MW4Application - Win32 Profile" /OUT buildpro.log
   ```
   Release gives the shipping `MW4.exe`; **Profile gives `MW4pro.exe`, which you need because
   only LAB_ONLY builds can run `-build`**. `ScriptStrings.dll` / `MissionLang.dll` rebuild as
   dependencies.
2. **Repack resources** — run `build-env\build-resources.ps1` (wraps `MW4pro.exe -build`; also
   handles moving DDrawCompat's `ddraw.dll` aside). The `.dep` files make it incremental;
   `core.mw4`, `textures.mw4`, `props.mw4` repack because their `.build` manifests changed.
   Full details: [build-env/RESOURCE-BUILD.md](build-env/RESOURCE-BUILD.md).
3. **Redeploy the game** — `build-env\deploy-mw4.ps1` (fresh `C:\VWE\firestorm\MW4` with new
   exe + DLLs + repacked `resource\*.mw4` + shellscripts + `hsh\`).
4. The **editor** needs no changes (it reads the same content tree in place), but a repacked
   `core.mw4` means re-running it will pick the new chassis up for missions.

## Step 7 — Verification checklist

1. Game launches (the `gosASSERT` on ID-count consistency fires immediately if a list is off).
2. Mechlab: chassis appears in the list with correct name/class/tech/tonnage; info text shows;
   hardpoints match the `.damage` slot counts; default loadout matches `.subsystems`; armor
   screen bars sit on the artwork.
3. Instant action: mech spawns, animates (walk/run/fall/getup), weapons fire from the right
   sites, cockpit damage paper-doll regions light up correctly (the `texuv`/`offset` UVs),
   targeting another instance shows the MFD image.
4. Destroyed: kill it — wreck (`_destroyed`) appears.
5. Multiplayer smoke test: name shows in lobby/roster (`NetLobby` arrays), both clients agree.
6. Editor: place the mech in a mission, `-report` runs clean.

## Quick-reference: every touch point

```
ART        Content\Mechs\<Name>\ + <Name>_destroyed\   (erf/obb/armature/anim + 9 text configs)
           hsh\Mechs|MFD|hud\<name>.bmp                Content\textures\HUD\<name>.tga
IDS        code\mw4\Code\MW4\MechLabHeaders.h          Content\ShellScripts\MechLabHeaders.h
           Content\ShellScriptsDev\MechLabHeaders.h    code\mw4\Code\mw4print\MechLabHeaders.h
           Content\ABLScripts\mwconst.abi
CODE       MW4\Mech.cpp (exec slots)                   MW4\mechspecs.cpp (stats)
           MW4\huddamage.cpp (paper-doll UVs)          GameOS\coord.cpp (MFD UVs)
           GameOS\render.cpp (2D art list)             MW4\MWApplication.cpp (name list)
           mw4dedicatedui\GameLobby.cpp|DedicatedUI.hpp
STRINGS    scriptstrings\StringResource.rc|resource.h  Content\ShellScripts\ScriptStrings.h
           MissionLang\MissionLangScript.rc|resource.h Content\Defines\MissionLang.defines
TABLES     Content\Tables\MechChassisTable.tbl         Content\Tables\MechTable.tbl
MANIFESTS  Content\core.build                          Content\textures.build
           Content\props.build                         Content\textures\textures.hint
SCRIPTS    ShellScripts\MechBay\{chassis,gs_chassis,weapons,armor,infobox,MechBay_main}.script
           ShellScripts\{ConLobby,NetLobby}.script     (+ ShellScriptsDev mirrors)
TOOLS      BTFrstrm\scriptaddmech.xls (row generator)  BTFrstrm\MechInfo_5.04.xls (design stats)
```
