mech_loadouts.md: document MechEditor data sources, conversions, hsh naming (2026-07-23)
This commit is contained in:
@@ -292,3 +292,363 @@ At the time this doc was written:
|
||||
- non-playable mechs are explicitly marked `No`
|
||||
- Templar and other mechs with rear or side mounts have facing visible in the CSV
|
||||
- the file is intended to be a living audit artifact for future data cleanup and enablement work
|
||||
|
||||
---
|
||||
|
||||
## MechEditor Web App — Data Sources, Fields, and Conversions (documented 2026-07-23)
|
||||
|
||||
The MechEditor is a single-file Python HTTP server at `/home/rich/Repositories/MechEditor/mech_editor.py`.
|
||||
It runs at `localhost:8765`, parses the firestorm content tree, and presents a full mech configuration editor in the browser.
|
||||
This section documents every data field it reads, every conversion it performs, and every naming rule it enforces.
|
||||
|
||||
---
|
||||
|
||||
### Data Files Parsed Per Mech
|
||||
|
||||
All files live under `Gameleap/mw4/Content/Mechs/<MechDir>/`.
|
||||
|
||||
#### `.data` file (`parse_data()`)
|
||||
|
||||
| Field in file | Editor key | Notes |
|
||||
|---|---|---|
|
||||
| `VehicleTonnage` | `tonnage` | chassis base tonnage (float) |
|
||||
| `MaxVehicleTonnage` | `max_tonnage` | max loadout tonnage (float) |
|
||||
| `TechType` | `tech` | `$(Tech_IS)` ? `"IS"`, `$(Tech_Clan)` ? `"Clan"` |
|
||||
| `MaxHeat` | `max_heat` | heat capacity (int) |
|
||||
| `VehicleTradeValue` | `trade_value` | C-bills |
|
||||
| `DragoonValue` | `dragoon` | used in Power Rating bar scaling |
|
||||
| `MinMaxSpeed` | `min_max_speed` | base speed ceiling in **m/s** |
|
||||
| `MaxSpeed` | `max_speed` | absolute speed ceiling in **m/s** (engine upgrades may not exceed this) |
|
||||
| `FullStopTurnRate` | `full_stop_turn` | turn rate at zero speed, in **degrees/sec** |
|
||||
| `TopSpeedTurnRate` | `top_speed_turn` | turn rate at top speed, in **degrees/sec** |
|
||||
| `Acceleration` | `acceleration` | forward acceleration in **m/s²** |
|
||||
| `Decceleration` | `decceleration` | forward braking in **m/s²** — **double-c spelling is canonical in the engine source** |
|
||||
| `ReverseAccelerationMultiplier` | `rev_accel_mult` | multiplier applied to Acceleration for reverse |
|
||||
| `ReverseDeccelerationMultiplier` | `rev_decel_mult` | multiplier applied to Decceleration for reverse — double-c canonical |
|
||||
| `MinStandTransitionSpeed` | not surfaced | animation threshold only — see note below |
|
||||
| `CanLoadJumpJets` | `can_jj` | Yes/No |
|
||||
| `CanLoadECM` | `can_ecm` | Yes/No |
|
||||
| `CanLoadBeagle` | `can_bap` | Yes/No |
|
||||
| `CanLoadLightAmp` | `can_lightamp` | Yes/No |
|
||||
| `CanLoadAMS` | `can_ams` | Yes/No |
|
||||
| `CanLoadLAMS` | `can_lams` | Yes/No |
|
||||
| `CanLoadIFF_Jammer` | `can_iff` | Yes/No |
|
||||
|
||||
**MinStandTransitionSpeed** is the speed (m/s) below which the mech switches from its walking animation to its idle/standing animation.
|
||||
It is NOT a hard movement limit. It is purely an animation state machine threshold in `Mech.cpp`.
|
||||
Code path: `animStateEngine?RequestState(StandState)` when `currentSpeedMPS <= minStandTransitionSpeed`.
|
||||
Must be > 0 (validated in `Vehicle_Tool.cpp`). Argus = 12.631 m/s = 45.5 kph.
|
||||
|
||||
#### `.instance` file (`parse_instance()`)
|
||||
|
||||
| Field | Editor key | Notes |
|
||||
|---|---|---|
|
||||
| `PowerRating` | `PowerRating` | mechlab bar value, 0–100 |
|
||||
| `ArmorRating` | `ArmorRating` | mechlab bar value, 0–100 |
|
||||
| `SpeedRating` | `SpeedRating` | mechlab bar value, 0–100 |
|
||||
| `HeatRating` | `HeatRating` | mechlab bar value, 0–100 |
|
||||
| `DoesHaveLightAmp` | `has_lightamp` | 0 or 1, default 1 — whether LightAmp is currently installed |
|
||||
|
||||
#### `.subsystems` file (`parse_subsystems()`)
|
||||
|
||||
Provides: armor type + per-zone multipliers, installed heatsinks, jump jets, engine upgrades, weapons, electronics.
|
||||
|
||||
**Armor block:**
|
||||
- `ArmorType=` ? armor type string (`Standard`, `FerroFiberus`, `Reactive`, `Reflective`, `Solarian`)
|
||||
- Per-zone entries: `LeftArm=1.0`, `RightTorso=2.5`, etc. — multiplier for that zone
|
||||
|
||||
**Engine:**
|
||||
- `EngineUpgrade` blocks counted ? `engine_upgrades` (0–5)
|
||||
|
||||
**Weapons** — each weapon block contains:
|
||||
- `Model=` ? weapon subsystem resource path (name extracted)
|
||||
- `InternalLocation=` ? zone name
|
||||
- `Site=` ? mount port name (from armature)
|
||||
- `GroupIndex=` ? weapon group number
|
||||
- `WeaponFacing=` ? 0=Front, 1=Rear, 2=Side (absent = Front)
|
||||
- `AmmoCount=` ? rounds for ammo-using weapons
|
||||
- `EjectSite=` ? optional ejection site for ammo
|
||||
|
||||
**Electronics:** ECM, Beagle (BAP), AMS, LAMS, IFF_Jammer detected by subsystem model path.
|
||||
|
||||
#### `.damage` file (`parse_damage()`)
|
||||
|
||||
Per zone section `[ZoneInternal]`:
|
||||
- `BaseArmorValue` ? starting armor (float)
|
||||
- `MaxArmorValue` ? hard cap on armor pts for that zone
|
||||
- `InternalHPValue` ? internal structure HP
|
||||
- `OmniSlots`, `BeamSlots`, `MissileSlots`, `ProjectileSlots` ? weapon slot counts
|
||||
|
||||
Special zones:
|
||||
- `SpecialAttachedToZone=` ? which body section this special zone is attached to
|
||||
- `DamagePropagationZone=` ? where overflow damage propagates
|
||||
|
||||
#### `.engine` file (`parse_engine()`)
|
||||
|
||||
| Field | Editor key | Notes |
|
||||
|---|---|---|
|
||||
| `NumHeatSinks` | `NumHeatSinks` | free heatsinks from engine (not in subsystems) |
|
||||
| `TonsPerUpgrade` | `TonsPerUpgrade` | tonnage cost per engine upgrade tier |
|
||||
| `MPSPerUpgrade` | `MPSPerUpgrade` | m/s speed gain per upgrade tier |
|
||||
|
||||
#### `.torso` file (`parse_torso()`)
|
||||
|
||||
| Field | Notes |
|
||||
|---|---|
|
||||
| `TwistSpeed` | torso horizontal rotation speed — may be a macro reference |
|
||||
| `PitchSpeed` | torso vertical rotation speed — may be a macro reference |
|
||||
| `TwistRadius` | max horizontal twist angle — may be a macro reference |
|
||||
| `PitchRadius` | max vertical pitch angle — may be a macro reference |
|
||||
| `ArmRatioAngle` | ratio of arm tracking vs torso rotation — may be a macro reference |
|
||||
|
||||
All torso fields may reference macros from `Content/Defines/MechTorso.defines`.
|
||||
The editor resolves them using a preloaded `TORSO_DEFINES` dict. Notable values:
|
||||
|
||||
```
|
||||
NORMAL_RATIO = 40
|
||||
SNAIL_TSPEED = 40
|
||||
NORMAL_TSPEED = 60
|
||||
FAST_TSPEED = 80
|
||||
WIDE_TRADIUS = 160
|
||||
NORMAL_PRADIUS = 40
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Calculated Ratings and Conversions
|
||||
|
||||
#### Speed (kph)
|
||||
|
||||
```
|
||||
top_speed_kph = min(MinMaxSpeed + MPSPerUpgrade × engine_upgrades, MaxSpeed) × 3.6
|
||||
```
|
||||
|
||||
- `MinMaxSpeed` and `MaxSpeed` from `.data` (m/s)
|
||||
- `MPSPerUpgrade` from `.engine` (m/s per tier)
|
||||
- `engine_upgrades` from `.subsystems` (0–5)
|
||||
- Multiply by 3.6 to convert m/s ? kph
|
||||
- Argus example: (20.28 + 1.11 × 10) × 3.6 = 113.5 kph
|
||||
|
||||
#### Speed Rating (bar)
|
||||
|
||||
```
|
||||
speed_rating = (top_speed_mps / MaxSpeed) × 100 [capped at 100]
|
||||
```
|
||||
|
||||
#### Turn Rate — degrees to radians
|
||||
|
||||
The `.data` file stores turn rates in **degrees/sec**. The mechlab UI label was changed to show rad/sec:
|
||||
- `StringResource.rc`: `IDS_ML_CH_TURNRATE` ? `"Turn Rate (Top Speed Rad/Sec):"`
|
||||
- Conversion: `radians = degrees × ?/180` where `?/180 ? 0.017453`
|
||||
- Argus: FullStopTurn = 75° = **1.309 rad/sec**, TopSpeedTurn = 45° = **0.785 rad/sec**
|
||||
|
||||
#### Acceleration / Deceleration (m/s²)
|
||||
|
||||
Stored directly in `.data`. Reverse values are derived:
|
||||
```
|
||||
reverse_accel = Acceleration × ReverseAccelerationMultiplier
|
||||
reverse_decel = Decceleration × ReverseDeccelerationMultiplier
|
||||
```
|
||||
|
||||
**The double-c spelling (`Decceleration`, `ReverseDeccelerationMultiplier`) is canonical — it matches the engine source. Do not "fix" the spelling.**
|
||||
|
||||
#### Heat Rating (bar)
|
||||
|
||||
```
|
||||
total_hs = NumHeatSinks (engine) + installed_heatsinks (subsystems)
|
||||
effective_hs = total_hs × (2 if Double else 1)
|
||||
heat_rating = (effective_hs / MaxHeat) × 100 [capped at 100]
|
||||
```
|
||||
|
||||
#### Power Rating (bar)
|
||||
|
||||
```
|
||||
total_damage = ? (DamageAmount × NumFire) for each installed weapon
|
||||
power_rating = (total_damage / 80) × 100 [capped at 100]
|
||||
```
|
||||
|
||||
- `DamageAmount` and `NumFire` come from `WeaponSubsystems/<weapon>.data` following `!include` chains
|
||||
- Parsed by `load_weapon_damages()` at server startup, cached in `Handler.weapon_damages`
|
||||
- Argus example with default load: ~36.2 total damage ? 45 rating (stored = 42)
|
||||
|
||||
#### Armor Rating (bar)
|
||||
|
||||
```
|
||||
for each zone:
|
||||
pts = multiplier × ARMOR_PTS_PER_TON[armor_type]
|
||||
effective = min(pts, MaxArmorValue[zone])
|
||||
armor_rating = (? effective / ? MaxArmorValue) × 100
|
||||
```
|
||||
|
||||
Armor pts per ton by type (from `Adept/ResourceImagePool.cpp` and game design):
|
||||
|
||||
| Type | Pts/ton |
|
||||
|---|---|
|
||||
| Standard | 32 |
|
||||
| FerroFiberus | 38 |
|
||||
| Reactive | 30 |
|
||||
| Reflective | 30 |
|
||||
| Solarian | 60 |
|
||||
|
||||
Note: `FerroFiberus` is the canonical internal token (not player-visible). The player sees `DNL_FERROFIB = "Ferro Fibrous"` via string lookup.
|
||||
|
||||
---
|
||||
|
||||
### Active-in-Game Detection
|
||||
|
||||
Source: `Gameleap/mw4/Content/Tables/MechChassisTable.tbl`
|
||||
|
||||
Format:
|
||||
```
|
||||
DisplayKey=Mechs\DirName\FileName.data
|
||||
//CommentedKey=Mechs\DirName\FileName.data <- inactive
|
||||
```
|
||||
|
||||
- Active = entry exists AND is not prefixed with `//`
|
||||
- Currently the only inactive mech: **Dasher** (commented out)
|
||||
- The editor shows a green **ACTIVE IN GAME** or red **NOT IN GAME** banner at top of Stats tab
|
||||
|
||||
---
|
||||
|
||||
### hsh/ Image Naming Conventions
|
||||
|
||||
The `hsh/` directory under `Gameleap/mw4/` holds loose BMP files loaded at runtime (not packed into `.mw4`).
|
||||
There are four relevant subdirectories, each with a different naming authority.
|
||||
|
||||
#### `hsh/hud/` — in-game HUD damage silhouette (own mech)
|
||||
#### `hsh/MFD/` — MFD target display silhouette (target mech)
|
||||
#### `hsh/radar/hud/` — radar damage overlay
|
||||
|
||||
**All three use identical stems** sourced from `huddamage.cpp` `texturename[]` array.
|
||||
|
||||
Load path:
|
||||
- hud/MFD: `hsh\<texturename>.bmp` where texturename = `hud\<stem>` ? file = `hsh/hud/<stem>.bmp`
|
||||
- radar: `hsh\radar\<texturename>.bmp` ? file = `hsh/radar/hud/<stem>.bmp`
|
||||
|
||||
Code: `render.cpp` `CRadar_Device::LoadRadarDamageTexture()` and `huddamage.cpp` `HUDDamage`.
|
||||
|
||||
#### `hsh/Mechs/` — mw4print scorecard portrait
|
||||
|
||||
Load path: `recscore.cpp` ? `GetLocString(model->m_nameIndex)` ? `DNL_*` string from `StringResource.rc` ? lowercased filename.
|
||||
Different naming authority from the other three.
|
||||
|
||||
---
|
||||
|
||||
### Complete Canonical Name Table
|
||||
|
||||
Key: mech directory name (case-insensitive) ? canonical stem for `hsh/hud/`, `hsh/MFD/`, `hsh/radar/hud/`.
|
||||
Entries in **bold** differ from the directory name.
|
||||
|
||||
| Directory | hud/MFD/radar stem | hsh/Mechs/ portrait filename |
|
||||
|---|---|---|
|
||||
| Annihilator | annihilator | annihilator.bmp |
|
||||
| Archer | archer | archer.bmp |
|
||||
| ArcticWolf | arcticwolf | arctic wolf.bmp |
|
||||
| Ares | ares | ares.bmp |
|
||||
| Argus | argus | argus.bmp |
|
||||
| Assassin2 | assassin2 | **assassin ii.bmp** |
|
||||
| Atlas | atlas | atlas.bmp |
|
||||
| Avatar | avatar | avatar.bmp |
|
||||
| Awesome | awesome | awesome.bmp |
|
||||
| Battlemaster | battlemaster | battlemaster.bmp |
|
||||
| Battlemaster2c | **battlemasteriic** | **battlemaster iic.bmp** |
|
||||
| Behemoth | behemoth | behemoth.bmp |
|
||||
| Behemoth2 | **behemothii** | **behemoth ii.bmp** |
|
||||
| Blackhawk | blackhawk | black hawk.bmp |
|
||||
| Blacknight | **blackknight** | black knight.bmp |
|
||||
| Blacklanner | blacklanner | black lanner.bmp |
|
||||
| Brigand | brigand | brigand.bmp |
|
||||
| Bushwacker | bushwacker | bushwacker.bmp |
|
||||
| Catapult | catapult | catapult.bmp |
|
||||
| CauldronBorn | cauldronborn | **cauldronborn.bmp** (table key has hyphen; DNL does not) |
|
||||
| Chimera | chimera | chimera.bmp |
|
||||
| Commando | commando | commando.bmp |
|
||||
| Cougar | cougar | cougar.bmp |
|
||||
| Cyclops | cyclops | cyclops.bmp |
|
||||
| Daishi | daishi | daishi.bmp |
|
||||
| Deimos | deimos | deimos.bmp |
|
||||
| Dragon | dragon | dragon.bmp |
|
||||
| Fafnir | fafnir | fafnir.bmp |
|
||||
| Flea | flea | flea.bmp |
|
||||
| Gladiator | gladiator | gladiator.bmp |
|
||||
| Grizzly | grizzly | grizzly.bmp |
|
||||
| Hauptmann | hauptmann | hauptmann.bmp |
|
||||
| Hellhound | hellhound | hellhound.bmp |
|
||||
| Hellspawn | hellspawn | hellspawn.bmp |
|
||||
| Highlander | highlander | highlander.bmp |
|
||||
| Hollander | **hollanderii** | **hollander ii.bmp** |
|
||||
| Hunchback | hunchback | hunchback.bmp |
|
||||
| Kodiak | kodiak | kodiak.bmp |
|
||||
| Loki | loki | loki.bmp |
|
||||
| Longbow | longbow | longbow.bmp |
|
||||
| Madcat | madcat | mad cat.bmp |
|
||||
| Madcat_MKII | **madcat2** | mad cat mkii.bmp |
|
||||
| Masakari | masakari | masakari.bmp |
|
||||
| Mauler | mauler | mauler.bmp |
|
||||
| Novacat | novacat | nova cat.bmp |
|
||||
| Osiris | osiris | osiris.bmp |
|
||||
| Owens | owens | owens.bmp |
|
||||
| Puma | puma | puma.bmp |
|
||||
| Raven | raven | raven.bmp |
|
||||
| Rifleman | rifleman | rifleman.bmp |
|
||||
| Ryoken | ryoken | ryoken.bmp |
|
||||
| Shadowcat | shadowcat | shadow cat.bmp |
|
||||
| Solitaire | solitaire | solitaire.bmp |
|
||||
| Sunder | sunder | sunder.bmp |
|
||||
| Templar | templar | templar.bmp |
|
||||
| Thanatos | thanatos | thanatos.bmp |
|
||||
| Thor | thor | thor.bmp |
|
||||
| Uller | uller | uller.bmp |
|
||||
| Urbanmech | urbanmech | urbanmech.bmp |
|
||||
| Uziel | uziel | uziel.bmp |
|
||||
| Victor | victor | victor.bmp |
|
||||
| Vulture | vulture | vulture.bmp |
|
||||
| Warhammer | warhammer | warhammer.bmp |
|
||||
| Wolfhound | wolfhound | wolfhound.bmp |
|
||||
| Zeus | zeus | zeus.bmp |
|
||||
|
||||
**Critical mismatches where directory name ? hud/MFD stem (files must use the stem, not the dir name):**
|
||||
|
||||
| Directory | Wrong name (dir-based) | Correct name (stem) |
|
||||
|---|---|---|
|
||||
| Battlemaster2c | battlemaster2c.bmp | **battlemasteriic.bmp** |
|
||||
| Behemoth2 | behemoth2.bmp | **behemothii.bmp** |
|
||||
| Blacknight | blacknight.bmp | **blackknight.bmp** |
|
||||
| Hollander | hollander.bmp | **hollanderii.bmp** |
|
||||
| Madcat_MKII | madcat_mkii.bmp | **madcat2.bmp** |
|
||||
|
||||
**Portrait mismatches for hsh/Mechs/ (table key ? DNL string):**
|
||||
|
||||
The `MechChassisTable.tbl` display key and `GetLocString()` DNL string differ for these mechs.
|
||||
mw4print uses the DNL string. The table key is NOT the correct portrait filename for these 5 mechs.
|
||||
|
||||
| Directory | Table key (wrong for mw4print) | DNL string (correct portrait stem) |
|
||||
|---|---|---|
|
||||
| Assassin2 | AssassinII ? assassinii | `DNL_ASSASSIN2 "Assassin II"` ? **assassin ii.bmp** |
|
||||
| Battlemaster2c | BattlemasterIIC ? battlemasteriic | `DNL_BATTLEMASTERIIC "Battlemaster IIc"` ? **battlemaster iic.bmp** |
|
||||
| Behemoth2 | BehemothII ? behemothii | `DNL_BEHEMOTHII "Behemoth II"` ? **behemoth ii.bmp** |
|
||||
| CauldronBorn | Cauldron-Born ? cauldron-born | `DNL_CAULDRONBORN "Cauldronborn"` ? **cauldronborn.bmp** |
|
||||
| Hollander | HollanderII ? hollanderii | `DNL_HOLLANDERII "Hollander II"` ? **hollander ii.bmp** |
|
||||
|
||||
---
|
||||
|
||||
### MechEditor Implementation Notes
|
||||
|
||||
The editor encodes all of the above knowledge in two Python dicts:
|
||||
|
||||
**`MECH_HSH_STEMS`** (in `mech_editor.py`)
|
||||
Maps lowercase dir name ? canonical stem for `hsh/hud/`, `hsh/MFD/`, `hsh/radar/hud/`.
|
||||
Source: `huddamage.cpp` `texturename[]` array.
|
||||
|
||||
**`MECH_PORTRAIT_OVERRIDES`** (in `mech_editor.py`)
|
||||
Maps lowercase dir name ? portrait stem for `hsh/Mechs/` where the DNL string differs from the chassis table key.
|
||||
Source: `DNL_*` entries in `Gameleap/code/mw4/Code/scriptstrings/StringResource.rc`.
|
||||
|
||||
For all other mechs, the portrait stem is derived dynamically from `MechChassisTable.tbl` (display key, lowercased).
|
||||
|
||||
The Assets tab in the editor shows all four image types. When an image is missing, it displays:
|
||||
```
|
||||
Wants: hsh/<subdir>/<expected_filename>.bmp
|
||||
```
|
||||
so the user knows exactly what to rename or create.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user