Files
firestorm/MFD-RADAR-MAPPINGS.md
T
dicion deafc2b01b Make Behemoth II inherit validated display mappings
Copy Behemoth's validated texuv2, offset2, texuv3, and offset3 rows from Mech ID 11 to Behemoth II at Mech ID 12. Both variants intentionally share the same external MFD and Radar geometry.

Add an inherited-mapping assertion to the J&J comparison generator so future runs fail if any Behemoth II coordinate row diverges from Behemoth. Record the passing inheritance check in the generated summary.

Document the inherited variant relationship and effective mapping count in MFD-RADAR-MAPPINGS.md.

Compare hud/behemothii.bmp and radar/hud/behemothii.bmp against the corresponding Behemoth runtime assets. Both 512x512 grayscale images are already pixel-identical, so no hsh art files are copied or replaced.
2026-08-07 20:52:00 -05:00

21 KiB

MFD and Radar Damage Mapping Guide

This document explains how the external MFD and Radar damage-paper-doll mappings are authored, stored, rendered, checked, and added for a new 'Mech. It records the workflow established while reviewing and installing all usable art under Finished HUDS from J&J/ on 2026-08-07.

Scope and terminology

There are three related damage displays. Do not mix their mapping tables:

Display Mapping source Art path
Normal in-cockpit HUD Gameleap/code/mw4/Code/MW4/huddamage.cpp: texuv and offset Content texture hud\<name>
External MFD Gameleap/code/CoreTech/Libraries/GameOS/coord.cpp: texuv2 and offset2 Loose Gameleap/mw4/hsh/hud/<name>.bmp
Radar secondary damage display coord.cpp: texuv3 and offset3 Loose Gameleap/mw4/hsh/radar/hud/<name>.bmp

This guide is about the last two displays, both controlled by coord.cpp. The nearby // MFD comments refer to the external display. The normal cockpit HUD has different coordinates in huddamage.cpp.

The small images in hsh/MFD/ are also a different asset set. render.cpp tiles those images into a mech texture atlas. They are not the 512x512 damage-mask images mapped by coord.cpp.

Controlling code

coord.cpp defines four positional arrays:

float texuv2[65][11][4]; // MFD source rectangles
int   offset2[65][11][2]; // MFD exploded destination positions
float texuv3[65][11][4]; // Radar source rectangles
int   offset3[65][11][2]; // Radar exploded destination positions

The first index is the numeric Mech ID. It must match the positional ID sequence in Gameleap/code/mw4/Code/MW4/MechLabHeaders.h. At present there are 65 active IDs, 0 through 64. Each array therefore has exactly 65 active rows in the same order, from Annihilator through Zeus. Dasher rows exist but are commented out because Dasher is not active in the roster.

The second index always uses this eleven-zone order:

0 LL   left leg
1 RL   right leg
2 LA   left arm
3 RA   right arm
4 RT   right torso
5 LT   left torso
6 CT   center torso
7 CTR  center torso rear
8 HD   head
9 S1   special 1
10 S2  special 2

Use {0,0,0,0} and {0,0} for a zone that has no art. The comment above offset2 currently says S2 S1; the array consumers and the other tables use index 9 as S1 and index 10 as S2. Treat the header comment as stale and preserve the index order above.

What the values mean

An unexploded rectangle is recorded as:

(x0, y0, x1, y1)

(x0,y0) is the upper-left pixel and (x1,y1) is the lower-right pixel of that component in the prepared unexploded image. In code this becomes one texuv2 or texuv3 entry.

An exploded position is recorded as:

(x2, y2)

It is the upper-left destination position of that component in the exploded layout. In code this becomes one offset2 or offset3 entry. The component keeps the width and height implied by its unexploded rectangle:

width  = x1 - x0
height = y1 - y0
exploded bounds = (x2, y2, x2 + width, y2 + height)

Coordinates are normally even because both authoring pipelines were designed around even pixel boundaries. Do not silently round a visually validated odd value: several supplied sets contain intentional odd coordinates, including Assassin II Radar S1 y=3, Behemoth MFD RA x=281, and Black Hawk Radar RT x=85. Radar's integer /2 truncates odd values at runtime, so changing an odd authored value can move an edge or origin in the rendered 256x256 view. Work from the upper-left to lower-right of each component, and do not overlap source rectangles.

MFD authoring pipeline

Start with the finished 1024x1024 full-color 'Mech image.

  1. Reduce the image to 320x320.
  2. Expand the canvas to 340x340, centered. This adds 10 pixels on every side.
  3. Separate adjacent body sections with a 4x4-pixel black line so source rectangles do not overlap or bleed into one another.
  4. Save this as the unexploded working view.
  5. For every zone, record its unexploded (x0,y0,x1,y1) rectangle, preferring even values.
  6. Starting from the unexploded view in the upper-left, place copies of the separated pieces in their exploded positions.
  7. Record each exploded piece's upper-left (x2,y2) coordinate, preferring even values.
  8. Expand the canvas to 512x512, anchored at the upper-left. Added area is black.
  9. Convert the exploded view to indexed color mode and save it as the runtime BMP.

Enter the rectangle values in texuv2 and the exploded upper-left values in offset2.

At runtime, huddamage.cpp draws an MFD component as:

mfd_device.DrawTexture(
    offset2[mech][zone][0] + 100,
    offset2[mech][zone][1] + 40,
    color,
    texuv2[mech][zone][0],
    texuv2[mech][zone][1],
    texuv2[mech][zone][2],
    texuv2[mech][zone][3]);

The +100,+40 values position the complete paper doll in the MFD UI. They are not part of the authored coordinates and must not be added to coord.cpp.

CMFD_Device::LoadDamageTexture() receives the logical name hud\<name> and loads the loose bitmap at hsh\hud\<name>.bmp.

Radar authoring pipeline

Start with the same finished 1024x1024 full-color image.

  1. Reduce the image to 400x400.
  2. Expand the canvas to 410x410, centered. This adds 5 pixels on every side.
  3. Expand the canvas to 512x512, anchored at the upper-left. Added area is black.
  4. Save this as the unexploded working view.
  5. Split the body into non-overlapping components from each component's upper-left to lower-right.
  6. Outline components with a 2x2-pixel white line.
  7. Record each unexploded (x0,y0,x1,y1) rectangle, preferring even values.
  8. Starting from the unexploded view in the upper-left, place copies of the pieces in their exploded positions.
  9. Record each exploded piece's upper-left (x2,y2) coordinate, preferring even values.
  10. Convert the exploded view to indexed color mode and save it as the runtime BMP.

Enter the rectangle values in texuv3 and the exploded upper-left values in offset3.

Radar coordinates are authored in the full 512x512 coordinate space. The runtime deliberately divides every rectangle and offset coordinate by two, switches the texture dimensions to 256x256 for the draw, and then adds the Radar UI origin:

radar_device.DrawTexture(
    (offset3[mech][zone][0] / 2) + 138,
    (offset3[mech][zone][1] / 2) + 406,
    color,
    texuv3[mech][zone][0] / 2,
    texuv3[mech][zone][1] / 2,
    texuv3[mech][zone][2] / 2,
    texuv3[mech][zone][3] / 2);

Do not pre-divide values when editing coord.cpp. Author and store the full-size values; preserve visually validated odd coordinates because runtime integer division determines their final half-resolution placement. The runtime loads hsh\radar\hud\<name>.bmp through CRadar_Device::LoadRadarDamageTexture().

Reading an existing mapping

  1. Find the Mech ID in MechLabHeaders.h.
  2. Confirm that the same row number is used in all four coord.cpp arrays. Comments are useful labels but do not control the mapping; array position does.
  3. Read the eleven texuv rectangles in the fixed zone order above.
  4. Read the eleven matching offset points in the same order.
  5. Ignore spelling differences in end-of-row comments unless they indicate an actual row-order error. Examples in the current file include M_HollnaderII and capitalization differences.
  6. Check that every nonzero rectangle has a corresponding nonzero offset, except where a design intentionally draws a component at (0,0).
  7. Check rectangle bounds, non-overlap, and whether any odd coordinate is intentional and visually aligned. MFD source geometry is normally inside the 340x340 working area. Radar's nominal art area is 410x410, but validate imported legacy Radar rectangles against the full 512x512 prepared canvas: accepted Black Hawk geometry extends beyond 410 and aligns visually.

Making or fixing a mapping

  1. Preserve the Mech's row position. Never sort one array independently.
  2. Prepare separate MFD and Radar unexploded images using the pipelines above.
  3. Measure source rectangles in zone order and record them in a text file before touching code.
  4. Prepare exploded layouts and record each upper-left destination point.
  5. Verify visually with overlays.
  6. Replace only that Mech's row in texuv2, offset2, texuv3, and offset3 as needed.
  7. Preserve coord.cpp CRLF line endings and avoid reformatting unrelated rows.
  8. Inspect the diff. A one-Mech repair should change at most four rows unless art or roster registration is also being changed.
  9. Rebuild the game because DXRasterizer.cpp includes coord.cpp directly. Rebuild the needed Release/Profile targets and redeploy the resulting executable.
  10. Compare the supplied exploded BMP to the existing canonical runtime BMP by decoded pixels, not only by file hash. Replace it only when the pixels differ. See "Runtime art comparison" below.
  11. Install a changed runtime BMP under the matching Gameleap/mw4/hsh path. Loose hsh art does not require a resource-package rebuild.
  12. Test both intact and damaged states on the real external MFD and Radar displays.

When adding a new chassis rather than repairing an existing row, also increase the first dimension of all four arrays and add one correctly positioned row to every array. This is part of the larger positional-ID workflow documented in ADDING-A-MECH.md.

Overlay validation method

Visual overlays are the fastest way to distinguish a coordinate problem from an art problem.

Generate every available J&J comparison and the status report with:

python3 "Finished HUDS from J&J/generate_comparison_maps.py"

This requires Pillow. It writes two PNGs per available display and updates Finished HUDS from J&J/COMPARISON-SUMMARY.md. The summary reports exact numeric matches, different zones, missing display inputs, and malformed source measurements.

Two J&J measurement formats exist:

  • Modern: the Unexploded file contains four-value source rectangles and the Exploded file contains two-value destination origins.
  • Legacy: the Unexploded file contains only each source rectangle's upper-left point, while the Exploded file contains the complete destination bounding box. For legacy data, derive the source width and height from the exploded box, apply those dimensions at the unexploded point, and use the exploded box's upper-left as the destination offset.

Some combined Coords.txt files contain both sections. Trust the section heading and tuple shape, not the filename alone. The generator handles both formats and indexes current mappings by numeric Mech ID rather than end-of-row comment spelling.

All-zero absent-zone tuples are found in both two-value and four-value forms in legacy files. Normalize an all-zero tuple to the width expected by its section; it still means no art. For nonzero tuples, a wrong tuple width is an input error and must not be guessed.

For an unexploded comparison:

  • Use the correctly transformed unexploded canvas, not the exploded-pieces BMP.
  • Draw the current texuv rectangles in red.
  • Draw proposed rectangles in green.
  • Label each rectangle with its zone.
  • A good rectangle encloses one component, follows its outer extent, and does not include pixels from another component.

For an exploded comparison:

  • Use the exploded-pieces BMP.
  • Mark each offset as an upper-left cross.
  • Draw a box from that point using the matching unexploded rectangle's width and height.
  • Draw current mappings in red and proposed mappings in green.
  • A good exploded mapping places the box exactly over the intended exploded piece.

Do not draw texuv2 rectangles directly on the raw 1024x1024 source. For MFD, first apply 1024 -> 320, center on 340, then place at the upper-left of 512. For Radar, apply 1024 -> 400, center on 410, then place at the upper-left of 512. When a supplied legacy unexploded image is already 512x512, use it directly rather than reconstructing and cropping it; the supplied coordinates may intentionally reference pixels outside the nominal 410 area.

Runtime art comparison

The exploded 512x512 BMP is the runtime asset. The unexploded BMP and full-color source image are authoring and measurement references only; do not copy those into hsh as the runtime art.

Resolve the destination by the canonical runtime texture stem for the numeric Mech ID, not by blindly copying the supplied filename. Current destinations are:

MFD:   Gameleap/mw4/hsh/hud/<stem>.bmp
Radar: Gameleap/mw4/hsh/radar/hud/<stem>.bmp

Known naming traps from this review:

  • Assassin II supplied files are misspelled assian2_*, but the canonical runtime stem is assassin2; do not overwrite the separate historical assassinii.bmp by accident.
  • The existing Fafnir runtime file is Fafnir.bmp with an uppercase F on this case-sensitive checkout. Resolve destination names case-insensitively, then preserve the existing spelling.
  • Folder names, comments, and supplied filenames are labels. Numeric Mech ID and the runtime texture-name table remain authoritative.

BMP byte equality is stricter than visual/runtime equality. A supplied file may be indexed P, grayscale L, or RGB, and may carry a different palette, header, row padding, or metadata while decoding to exactly the same pixels. Compare in this order before replacing anything:

  1. Confirm both images are 512x512.
  2. If file bytes match, the destination is byte-exact.
  3. Otherwise decode both images, convert both to a common mode such as RGBA, and compare every pixel.
  4. If decoded pixels match, the destination is already correct. Keep the existing runtime file; replacing it creates binary churn and may exchange a compact indexed/grayscale BMP for RGB.
  5. Copy the supplied exploded BMP only if decoded pixels differ, then repeat the pixel comparison against the installed destination.

Loose hsh art is not packed into a .mw4 resource for this path, so an actual art replacement does not require build-resources.ps1. Coordinate changes still require rebuilding the game executable because DXRasterizer.cpp includes coord.cpp directly.

Reproducible validation

After editing coordinates, rerun:

python3 "Finished HUDS from J&J/generate_comparison_maps.py"

For the 2026-08-07 data set, the expected terminal result is:

Generated 38 maps for 19 display sets: 19 exact, 0 different
Warnings: 0; missing display sets: 7

Also verify all of the following before considering the source complete:

  • COMPARISON-SUMMARY.md contains 19 EXACT rows and no DIFFERENT rows.
  • Each of texuv2, offset2, texuv3, and offset3 still has 65 active rows.
  • Every active row still has exactly 11 zones.
  • The edited coord.cpp remains CRLF-only; the repository deliberately uses byte-exact files.
  • The diff changes only intended Mech rows. This installation changes 38 rows total: two rows for each of 19 display sets.
  • git status --short -- Gameleap/mw4/hsh is empty when every runtime image was already correct.
  • A Windows VC6 Release/Profile rebuild and physical MFD/Radar test remain required; the Linux comparison workflow validates data and geometry but cannot replace that runtime test.

J&J mapping status as of 2026-08-07

All 19 complete display sets under Finished HUDS from J&J/ are installed in coord.cpp and report EXACT in COMPARISON-SUMMARY.md: 12 MFD sets and 7 Radar sets across 13 chassis. Seven opposite-display sets have no complete supplied mapping/image inputs and were not changed. Behemoth II (Mech ID 12) has no separate J&J measurement set because it deliberately shares Behemoth's geometry and art. Its four rows now inherit Behemoth's validated ID 11 rows, bringing the effective installed result to 21 MFD/Radar mappings across 14 Mech IDs. The comparison generator asserts this inheritance on every run and fails if any row diverges.

Installed sets:

Mech ID Chassis MFD Radar
0 Annihilator installed installed
4 Argus installed no supplied set
5 Assassin II installed installed
7 Avatar installed installed
11 Behemoth installed installed
12 Behemoth II inherited from ID 11 inherited from ID 11
13 Black Hawk installed installed
27 Fafnir installed no supplied set
28 Flea installed no supplied set
29 Gladiator installed no supplied set
33 Hellspawn no supplied set installed
37 Kodiak installed no supplied set
39 Longbow installed no supplied set
62 Warhammer installed installed

The supplied exploded BMP for every installed display set was also compared to its canonical runtime destination under Gameleap/mw4/hsh/hud/ or Gameleap/mw4/hsh/radar/hud/. All 19 are pixel-identical at 512x512. Their file hashes differ because the supplied files use different BMP encodings, palettes, or headers; the runtime copies are already correct, so none were replaced.

Pixel-identical runtime files:

MFD:   annihilator, argus, assassin2, avatar, behemoth, blackhawk, Fafnir,
       flea, gladiator, kodiak, longbow, warhammer
Radar: annihilator, assassin2, avatar, behemoth, blackhawk, hellspawn, warhammer

Behemoth II uses separate canonical files, hud/behemothii.bmp and radar/hud/behemothii.bmp. Both were decoded and compared to their corresponding Behemoth runtime BMP on 2026-08-07; both were already pixel-identical 512x512 grayscale images, so no art files were copied or replaced.

Three obvious measurement-file transcription errors were normalized without changing the validated geometry: Behemoth MFD LT 1742 to 174, Behemoth Radar CT 202.242 to 202,242, and Fafnir MFD LL 162.326 to 162,326. Behemoth Radar's blank S2 was made explicit as 0,0.

Assassin II details

Reference art and measurements are under:

Finished HUDS from J&J/AssassinII/

The supplied files are:

HUD/assassinII_HUD_unexploded.txt
HUD/assassinII_hud_exploded.txt
Radar/assassinII_radar_unexploded.txt
Radar/assassinII_radar_exploded.txt

The supplied Assassin II values were checked against the correctly transformed source art. Both corrected MFD and Radar mappings align. All four rows were installed in coord.cpp on 2026-08-07 and are pending an executable rebuild and hardware test.

Generated comparison images from the review are:

Finished HUDS from J&J/AssassinII/HUD/assian2_mfd_unexploded_coords_comparison.png
Finished HUDS from J&J/AssassinII/HUD/assian2_mfd_exploded_coords_comparison.png
Finished HUDS from J&J/AssassinII/Radar/assian2_radar_unexploded_coords_comparison.png
Finished HUDS from J&J/AssassinII/Radar/assian2_radar_exploded_coords_comparison.png

The generator now draws the installed coord.cpp mapping in red and the supplied J&J mapping in green, so the colors overlap exactly. These PNGs are review art only and are not loaded by the game.

Implemented Assassin II rows

MFD texuv2:

{{ 98,142,148,328},{190,142,240,328},{ 56, 48,124,154},{216, 48,282,154},{180, 22,220,120},{120, 22,158,120},{144, 36,194,186},{  0,  0,  0,  0},{158, 70,180, 86},{148, 10,190, 48},{  0,  0,  0,  0}}

MFD offset2:

{{  6,148},{282,148},{  6, 10},{266, 10},{206, 22},{ 98, 22},{144,180},{  0,  0},{158,140},{148, 10},{  0,  0}}

Radar texuv3:

{{114,170,178,406},{232,170,294,406},{ 60, 48,144,184},{264, 48,348,184},{218, 18,268,140},{142, 18,190,140},{174, 36,234,140},{  0,  0,  0,  0},{192, 78,218, 98},{178,  2,230, 48},{  0,  0,  0,  0}}

Radar offset3:

{{  8,220},{336,220},{  8, 48},{314, 48},{244, 48},{120, 48},{178,206},{  0,  0},{192, 78},{178,  3},{  0,  0}}

One supplied Radar S1 offset is odd (y=3) despite the general even-coordinate rule. Preserve the measured value until it is deliberately reviewed against the source pixels; do not silently round it while transcribing the row.

Common mistakes

  • Drawing MFD boxes on the raw 1024 image instead of the transformed 340 working canvas.
  • Drawing unexploded rectangles on the exploded-pieces BMP.
  • Using texuv/offset from huddamage.cpp when the task is external MFD/Radar mapping.
  • Using the small hsh/MFD atlas image as the external damage-mask source.
  • Pre-dividing Radar coordinates by two before putting them in coord.cpp.
  • Adding the MFD +100,+40 or Radar +138,+406 UI origins to authored offsets.
  • Treating (x2,y2) as a second rectangle instead of an upper-left destination point.
  • Measuring overlapping component rectangles or omitting separator/outline pixels.
  • Swapping S1 and S2 because of the stale offset2 header comment.
  • Inserting a row into only one parallel array and shifting every later Mech out of alignment.
  • Re-encoding or normalizing the entire legacy source file while changing four rows.
  • Treating a different BMP hash as different art without decoding and comparing pixels.
  • Copying an unexploded/reference BMP instead of the exploded 512x512 runtime BMP.
  • Using the supplied assian2 typo as the destination instead of canonical assassin2.bmp.
  • Rounding odd coordinates despite a visually aligned overlay.