# 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\` | | External MFD | `Gameleap/code/CoreTech/Libraries/GameOS/coord.cpp`: `texuv2` and `offset2` | Loose `Gameleap/mw4/hsh/hud/.bmp` | | Radar secondary damage display | `coord.cpp`: `texuv3` and `offset3` | Loose `Gameleap/mw4/hsh/radar/hud/.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: ```cpp 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: ```text 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: ```text (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: ```text (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: ```text 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: ```cpp 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\` and loads the loose bitmap at `hsh\hud\.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: ```cpp 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\.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: ```bash 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: ```text MFD: Gameleap/mw4/hsh/hud/.bmp Radar: Gameleap/mw4/hsh/radar/hud/.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: ```bash python3 "Finished HUDS from J&J/generate_comparison_maps.py" ``` For the 2026-08-07 data set, the expected terminal result is: ```text 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. 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 | | 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: ```text MFD: annihilator, argus, assassin2, avatar, behemoth, blackhawk, Fafnir, flea, gladiator, kodiak, longbow, warhammer Radar: annihilator, assassin2, avatar, behemoth, blackhawk, hellspawn, warhammer ``` 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: ```text Finished HUDS from J&J/AssassinII/ ``` The supplied files are: ```text 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: ```text 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`: ```cpp {{ 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`: ```cpp {{ 6,148},{282,148},{ 6, 10},{266, 10},{206, 22},{ 98, 22},{144,180},{ 0, 0},{158,140},{148, 10},{ 0, 0}} ``` Radar `texuv3`: ```cpp {{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`: ```cpp {{ 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.