From 0344418aaebdd1cc0ba408ae4d85ca7f3146f6b3 Mon Sep 17 00:00:00 2001 From: RT Date: Thu, 23 Jul 2026 21:56:14 -0500 Subject: [PATCH] mech_loadouts.md: document MechEditor data sources, conversions, hsh naming (2026-07-23) --- BTFrstrm/mech_loadouts.md | 360 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 360 insertions(+) diff --git a/BTFrstrm/mech_loadouts.md b/BTFrstrm/mech_loadouts.md index 15dbe6c4..2715c9d7 100644 --- a/BTFrstrm/mech_loadouts.md +++ b/BTFrstrm/mech_loadouts.md @@ -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//`. + +#### `.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/.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\.bmp` where texturename = `hud\` ? file = `hsh/hud/.bmp` +- radar: `hsh\radar\.bmp` ? file = `hsh/radar/hud/.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//.bmp +``` +so the user knows exactly what to rename or create. +