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.
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.
- Reduce the image to 320x320.
- Expand the canvas to 340x340, centered. This adds 10 pixels on every side.
- Separate adjacent body sections with a 4x4-pixel black line so source rectangles do not overlap or bleed into one another.
- Save this as the unexploded working view.
- For every zone, record its unexploded
(x0,y0,x1,y1)rectangle, preferring even values. - Starting from the unexploded view in the upper-left, place copies of the separated pieces in their exploded positions.
- Record each exploded piece's upper-left
(x2,y2)coordinate, preferring even values. - Expand the canvas to 512x512, anchored at the upper-left. Added area is black.
- 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.
- Reduce the image to 400x400.
- Expand the canvas to 410x410, centered. This adds 5 pixels on every side.
- Expand the canvas to 512x512, anchored at the upper-left. Added area is black.
- Save this as the unexploded working view.
- Split the body into non-overlapping components from each component's upper-left to lower-right.
- Outline components with a 2x2-pixel white line.
- Record each unexploded
(x0,y0,x1,y1)rectangle, preferring even values. - Starting from the unexploded view in the upper-left, place copies of the pieces in their exploded positions.
- Record each exploded piece's upper-left
(x2,y2)coordinate, preferring even values. - 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
- Find the Mech ID in
MechLabHeaders.h. - Confirm that the same row number is used in all four
coord.cpparrays. Comments are useful labels but do not control the mapping; array position does. - Read the eleven
texuvrectangles in the fixed zone order above. - Read the eleven matching
offsetpoints in the same order. - Ignore spelling differences in end-of-row comments unless they indicate an actual row-order
error. Examples in the current file include
M_HollnaderIIand capitalization differences. - Check that every nonzero rectangle has a corresponding nonzero offset, except where a design
intentionally draws a component at
(0,0). - 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
- Preserve the Mech's row position. Never sort one array independently.
- Prepare separate MFD and Radar unexploded images using the pipelines above.
- Measure source rectangles in zone order and record them in a text file before touching code.
- Prepare exploded layouts and record each upper-left destination point.
- Verify visually with overlays.
- Replace only that Mech's row in
texuv2,offset2,texuv3, andoffset3as needed. - Preserve
coord.cppCRLF line endings and avoid reformatting unrelated rows. - Inspect the diff. A one-Mech repair should change at most four rows unless art or roster registration is also being changed.
- Rebuild the game because
DXRasterizer.cppincludescoord.cppdirectly. Rebuild the needed Release/Profile targets and redeploy the resulting executable. - 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.
- Install a changed runtime BMP under the matching
Gameleap/mw4/hshpath. Loosehshart does not require a resource-package rebuild. - 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
texuvrectangles 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
offsetas 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 isassassin2; do not overwrite the separate historicalassassinii.bmpby accident. - The existing Fafnir runtime file is
Fafnir.bmpwith an uppercaseFon 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:
- Confirm both images are 512x512.
- If file bytes match, the destination is byte-exact.
- Otherwise decode both images, convert both to a common mode such as RGBA, and compare every pixel.
- 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.
- 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.mdcontains 19EXACTrows and noDIFFERENTrows.- Each of
texuv2,offset2,texuv3, andoffset3still has 65 active rows. - Every active row still has exactly 11 zones.
- The edited
coord.cppremains 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/hshis 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/offsetfromhuddamage.cppwhen the task is external MFD/Radar mapping. - Using the small
hsh/MFDatlas image as the external damage-mask source. - Pre-dividing Radar coordinates by two before putting them in
coord.cpp. - Adding the MFD
+100,+40or Radar+138,+406UI 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
offset2header 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
assian2typo as the destination instead of canonicalassassin2.bmp. - Rounding odd coordinates despite a visually aligned overlay.