Warcraft III models
Aqua IV reads the models of Warcraft III (MDX version 800) and plays them in its own renderer. The goal is full coverage: every part of the format read, drawn or played the way the game does, and checked. This page is the honest scoreboard. It is regenerated from the project's coverage list, and it changes as work lands.
Reforged models (versions 900 and 1000) are out of scope.
Where we stand
There are 192 rows, one for each chunk, flag, track and rule. Each is judged three ways:
- Read: the file is parsed into the model. 129 of 192 (67 per cent) are done; 60 more are not yet checked.
- Drawn or played: the game shows or plays it. 78 of 192 (41 per cent) are done or need nothing drawn, and 12 more are partly done.
- Checked: a test, a screenshot or a comparison with Warcraft III proves it. 51 of 192 (27 per cent).
The drawn and checked figures are the ones that matter. Reading a file completely is the easy half; the work is in playing what it holds. Rows that are research notes (rules to confirm against the game) count too, which is why the totals are modest while most day-to-day models already look right.
How to read the tables: Done means finished, Partly means some of it works, No means not yet, Not checked means nobody has confirmed the state yet, and Nothing to draw means the data has no visible form.
Research notes from OpenWCIII
39 rows, 6 drawn or played in full.
| Feature | Read | Drawn | Checked |
|---|---|---|---|
| Missing rule: Depth write only for filters None and Transparent; blending from filter ordinal 2; depth test off by flag | Not checked | No | No |
| Missing rule: Alpha discard under 0.02 for Modulate and Modulate2x; discard needs vertex alpha 1 | Not checked | No | No |
| Missing rule: Visibility tracks (KLAV, KATV, KPEV, KP2V, KRVS) are never interpolated | Not checked | No | No |
| Missing rule: Colour tracks stored BGR (KLAC, KLBC, KGAC): channel order on load | Not checked | No | No |
| Missing rule: Keys wrap from the last to the first of a sequence; global-sequence start key injection; constant-sequence shortcut | Not checked | No | No |
| Missing rule: Sequence time in milliseconds; non-looping stops at end - 1; loop modes model (flags 0) and to-next; hide when done | Done | Partly | Done |
| Missing rule: Fog rule: linear ramp, additive blends fade toward black, per-filter fog skips, unfogged flag | Not checked | No | No |
| Missing rule: Layer colour: vertex colour times geoset colour, alpha = layer alpha | Not checked | No | No |
| Missing rule: Texture animation order: translate, rotate about (0.5, 0.5), scale about (0.5, 0.5) | Not checked | No | No |
| Missing rule: Opaque versus translucent geoset passes; geosets with alpha 0 not pickable | Not checked | No | No |
| Missing rule: Attachment visibility threshold 0.1, restart at sequence 0 on reappearing, child hidden with the parent node, .mdl to .mdx path rule | Not checked | No | No |
| Missing rule: Event object name parsing (3-character type, id from character 4), step trigger on key index change, reset on sequence change; SPL life Lifespan + Decay, UBR Birth + Pause + Decay; SPN plays sequence 0 | Not checked | No | No |
| Missing rule: SND: volume / 127, pitch +- variance, random file pick, panner distances | Not checked | No | No |
| Missing rule: Light formulas: omni distance /64 + 1 and inverse square, ambient window attenuation; unknown light type = directional | Not checked | No | No |
| Missing rule: PRE2 gravity times node world scale Z; velocity scaled by node scale; half width and half length; velocity = unit Z through rotation about Z (90 degrees), Y, then X | Not checked | No | No |
| Missing rule: PRE2 squirt starts with key -1 (first frame always a change); emission count ceil of the accumulator; sort key filter + 2 | Not checked | No | No |
| Missing rule: PREM as model-instance particles: random Z rotation, sequence 0, no longitude | Not checked | No | No |
| Missing rule: Ribbon emission cap of 1 per update; chain-factor UV rule; ribbons never lit; alpha cut 0.75 in the ribbon shader | Not checked | No | No |
| Missing rule: Replaceable id table (11, 21, 31 to 37 paths) and the two-digit team id suffix; empty texture path = white texture | Done | Done | Done |
| Missing rule: Node: parent id equal to own id = root; object id order; cameras without object id; billboard first matching flag only; camera-anchored is not billboarding | Not checked | No | No |
| Missing rule: Sequence tag parsing stops at the first unknown token; rarity roll random(0..100) < 10 - rarity; tag rank constants (200000, 10) | Done | Partly | Done |
| Rule differs: Additive versus AddAlpha: both src alpha x one in OpenWCIII; ours one Blend::Add; confirm what it multiplies by | Not checked | No | No |
| Rule differs: Ribbon gravity: position offset gravity * dt^2 (OpenWCIII) versus sink speed gravity * scale * dt (ours); compare on a ribbon model | Not checked | No | No |
| Rule differs: PRE2 accumulator emptied above MAX_PARTICLES (ours) versus no cap; squirt with no key emits nothing (ours) | Not checked | No | No |
Rule differs: Key before the first key and wrap case in anim.rs sample: check a track whose last key is before the sequence end |
Not checked | No | No |
| Rule differs: Non-looping end: clamp at end - 1 versus our "last pose" (row 5): check the off-by-one | Done | Done | Done |
| Rule differs: Sequence blending with blendTime: ratio blendTimeRemaining / blendTime, only when the previous sequence is not -1 (row 3) | Done | Done | Done |
Rule differs: Billboard lock: first matching flag only, in the order 0x8, 0x10, 0x20, 0x40; ours (pose_facing) may apply several |
Not checked | No | No |
| Verify in WC3: Death and Decay hold the last frame (OpenWCIII has no such rule; the unit layer decides) | Done | Done | Partly |
| Verify in WC3: Walk speed scaling by unit speed (OpenWCIII only returns the move speed) | Done | Done | Partly |
| Verify in WC3: Alternate and Morph sequences beyond the tag lists; Attack and Decay selection | Done | Partly | Partly |
| Verify in WC3: CLID cylinder and plane shapes (OpenWCIII does not implement them) | Not checked | No | No |
| Verify in WC3: Camera behaviour for portraits (OpenWCIII stores the fields and tracks only) | Not checked | No | No |
| Verify in WC3: PRE2 longitude (no field in the OpenWCIII loader) and PREM beyond sequence 0 | Not checked | No | No |
| Verify in WC3: Sphere environment map (flag 0x2): view-space normals, not seen in OpenWCIII shaders | Not checked | No | No |
| Verify in WC3: Sort primitives far Z, constant colour, full resolution; priority plane as a sort key | Not checked | No | No |
| Verify in WC3: Per-sequence extents for culling; GEOA use-colour and drop-shadow flags | Not checked | No | No |
| Verify in WC3: Ubersplat terrain placement for UBR event objects | Not checked | No | No |
| Do not copy: OpenWCIII bugs: PRE2 width and length fall back to each other (swapped); ribbon kill clears first and last wrongly; splat normals as signed bytes; EventObjectSpn throws with no sequences; Attachment.update has no null check; an empty reforged texture path throws | Nothing to draw | Nothing to draw | No |
Files and the whole corpus
10 rows, 9 drawn or played in full.
| Feature | Read | Drawn | Checked |
|---|---|---|---|
| VERS (800) | Done | Nothing to draw | Done |
| MODL name, bounds | Done | Done | Done |
| MODL blend time (blending between sequences) | Done | Done | Done |
.mdl text read and write |
Done | Nothing to draw | Done |
| All 3,209 archive models parse | Done | Nothing to draw | Done |
| All 3,209 play every sequence without panic or NaN | Nothing to draw | Done | Done |
| MODL animation-file name (260 bytes, skipped) | Not checked | Nothing to draw | No |
| MODL bounds radius separate from min and max | Not checked | No | No |
Unknown top-level chunks kept by name and ignored; fix stale comment at crates/aqua4-mdx/src/lib.rs:7-8 |
Not checked | Nothing to draw | No |
| v900 chunks (TANG, SKIN) refused cleanly (out of scope) | Not checked | Nothing to draw | No |
Sequences and animation
23 rows, 11 drawn or played in full.
| Feature | Read | Drawn | Checked |
|---|---|---|---|
| SEQS start, end | Done | Done | Done |
| SEQS non-looping | Done | Done | Done |
| SEQS rarity (which variant plays) | Done | Done | Done |
| SEQS move speed (walk animation speed from unit speed) | Done | Done | Done |
| SEQS bounds | Done | Done | Done |
| GLBS global sequences | Done | Done | Done |
| Interpolation none / linear | Done | Done | Done |
| Interpolation hermite / bezier | Done | Done | Done |
| Spherical rotation blending | Done | Done | Done |
| SEQS animation tag lookup by name and subname (Stand, Stand Ready, Walk Fast, Attack, Death, Decay, Dissipate, Morph, Birth, Spell) | Done | Partly | Done |
| SEQS end rules: Death and Decay hold the last frame | Done | Done | Done |
| SEQS walk speed scaling by unit speed | Done | Done | Done |
| SEQS alternate Stand chosen by rarity; Morph and Alternate sequences | Done | Partly | Done |
global_sequence honoured for layer alpha tracks |
Not checked | No | No |
global_sequence honoured for texture animation tracks |
Not checked | No | No |
global_sequence honoured for geoset animation tracks |
Not checked | No | No |
global_sequence honoured for light tracks |
Not checked | No | No |
global_sequence honoured for PRE2 and ribbon tracks |
Not checked | No | No |
global_sequence honoured for camera tracks |
Not checked | No | No |
| Quaternion hermite and bezier with slerp fallback | Not checked | No | No |
| Integer tracks (KMTF, KRTX) held with no interpolation | Not checked | No | No |
| Keys outside the sequence range (first key or default) | Not checked | No | No |
| Absent tracks: identity rotation, scale 1 | Not checked | No | No |
Textures, materials and layers
35 rows, 18 drawn or played in full.
| Feature | Read | Drawn | Checked |
|---|---|---|---|
| TEXS path, wrap width and height | Done | Done | Done |
| TEXS replaceable 1 (team colour) | Done | Done | Done |
| TEXS replaceable 2 (team glow) | Done | Done | Done |
| TEXS replaceable 11, 21, 31 to 37 (cliff, trees) | Done | Done | Done |
| MTLS layer filter None | Done | Done | Done |
| MTLS layer filter Transparent (alpha test) | Done | Done | No |
| MTLS layer filter Blend | Done | Done | No |
| MTLS layer filter Additive | Done | Done | No |
| MTLS layer filter AddAlpha | Done | Partly | No |
| MTLS layer filter Modulate / Modulate2x | Done | Done | No |
| Layer flag unshaded | Done | Done | No |
| Layer flag two sided | Done | Done | No |
| Layer flag sphere environment map | Done | No | No |
| Layer flag unfogged | Done | No | No |
| Layer flag no depth test | Done | Done | No |
| Layer flag no depth set | Done | Done | No |
| Layer alpha (fixed) | Done | Done | Partly |
| Layer alpha track (KMTA) | Done | Done | Partly |
| Layer texture id track (KMTF) | Done | No | No |
| Layer texture animation (TXAN translation, rotation, scale: waterfalls, flames) | Done | No | No |
| Layer coord id (second UV set) | Done | No | No |
| Material priority plane (draw order) | Done | No | No |
| Material flags (constant colour, sort primitives far Z, full resolution) | Done | No | No |
| MTLS constant colour flag (0x1) | Not checked | No | No |
| MTLS sort primitives far Z (0x8) | Not checked | No | No |
| MTLS full resolution (0x10) | Not checked | No | No |
| Layer shading flag bits above 0x80 | Not checked | No | No |
| Unknown filter mode falls back to opaque | Not checked | No | No |
| AddAlpha rule (src = alpha * colour, additive); alpha key threshold 0.75 not scaled by layer alpha | Not checked | No | No |
| TXAN rotation (angle about Z versus quaternion) checked against WC3 | Not checked | No | No |
| Sphere environment map uses view-space normals | Not checked | No | No |
| Team colour: replaceable 1 (colour) and 2 (glow) pictures as the game has them | Done | Done | Done |
| Per-player colour table (24 colours, per instance in the shader) | Done | Done | Done |
| Replaceable ids 8 to 10 and unknown ids | Done | Done | Done |
| Texture path rules: .tga and .blp, case, backslashes, missing file fallback | Not checked | No | No |
Geometry and geoset animation
16 rows, 12 drawn or played in full.
| Feature | Read | Drawn | Checked |
|---|---|---|---|
| GEOS positions, normals, triangles | Done | Done | Done |
| GEOS skinning (vertex groups, bone matrices) | Done | Done | Done |
| GEOS UV set 0 | Done | Done | Done |
| GEOS selection group and flags | Done | Nothing to draw | No |
| GEOA static alpha (hides a geoset) | Done | Done | Partly |
| GEOA alpha track (KGAO: fades, appearing parts) | Done | Done | Partly |
| GEOA colour and colour track (KGAC) | Done | Done | Partly |
| GEOS per-geoset bounds | Not checked | No | No |
| GEOS per-sequence extents (28 bytes each, skipped; per-sequence culling) | Done | No | No |
| GEOS primitive types other than triangles (refused: UnsupportedPrimitive) and PCNT | Done | Done | Done |
| GEOS more than 4 bones per vertex (cap and weights) | Not checked | No | No |
| GEOS index validation | Done | Done | Done |
| GEOS UV sets beyond the first (UVBS) | Not checked | No | No |
| GEOA drop-shadow flag | Done | Nothing to draw | Done |
| GEOA use-colour flag | Done | Done | Done |
| GEOA NO_ID geoset = all geosets | Done | Done | Partly |
Skeleton and nodes
10 rows, 6 drawn or played in full.
| Feature | Read | Drawn | Checked |
|---|---|---|---|
| BONE: translation, rotation, scale tracks | Done | Done | Done |
| BONE geoset id and geoset anim id | Done | Partly | No |
| HELP | Done | Done | Done |
| Node flags: don't inherit translation / rotation / scaling | Done | Done | Done |
| Node flags: billboarded, lock X / Y / Z | Done | No | No |
| Node flag camera anchored | Done | No | No |
| PIVT pivot points | Done | Done | Done |
| BONE with geoset anim id takes alpha from that GEOA | Not checked | No | No |
| Node parent_id NO_ID = root; parent before child; cycle and forward-reference test | Done | Done | Done |
| Node name table; node flag bits above 0x80 | Done | Done | Done |
Particles and ribbons
32 rows, 13 drawn or played in full.
| Feature | Read | Drawn | Checked |
|---|---|---|---|
| PREM (ParticleEmitter v1, model particles) | Done | No | No |
| PRE2 speed, variation, latitude, gravity, life span, rate, width, length (+ tracks) | Done | Done | Partly |
| PRE2 filter modes (blend, additive, modulate, modulate2x, alpha key) | Done | Done | Partly |
| PRE2 rows, columns, head and tail frame intervals | Done | Done | Done |
| PRE2 head / tail / both, tail length | Done | Done | Done |
| PRE2 line emitter | Done | Done | Done |
| PRE2 XYQuad (flat in the ground plane) | Done | Done | Done |
| PRE2 squirt | Done | Done | Partly |
| PRE2 model space (particles move with the emitter) | Done | Done | Done |
| PRE2 unshaded, unfogged | Done | Partly | No |
| PRE2 sort primitives far Z | Done | Done | Done |
| PRE2 priority plane | Done | Done | Partly |
| PRE2 team colour (replaceable id 1, 2) | Done | Done | No |
| PRE2 visibility track | Done | Done | No |
| RIBB ribbons (heights, alpha, colour, life, rate, rows, columns, slot, gravity) | Done | Done | Partly |
| PRE2 KP2V visibility track | Done | No | No |
| PRE2 KP2E emission rate track | Done | No | No |
| PRE2 KP2W width track | Done | No | No |
| PRE2 KP2N length track | Done | No | No |
| PRE2 KP2S speed track | Done | No | No |
| PRE2 KP2L latitude track | Done | No | No |
| PRE2 KP2G gravity track | Done | No | No |
| PRE2 KP2R variation track | Done | No | No |
PRE2 segment colours, alphas and scaling (head, mid, tail with the time split) |
Done | No | No |
PRE2 head and tail intervals with repeat and the random frame choice |
Done | No | No |
| PRE2 texture id plus replaceable id when both are set | Done | No | No |
| PRE2 emission accumulator, emitter life and gravity sign | Not checked | No | No |
| PREM texture path flags (EMITTER_USES_MDL, EMITTER_USES_TGA) | Done | No | No |
| PREM emission rate, gravity, longitude, latitude, life span, initial velocity and tracks KPEV, KPEE, KPEG, KPLN, KPLT, KPEL, KPES | Done | No | No |
| RIBB KRVS visibility, KRHA and KRHB heights, KRAL alpha, KRCO colour, KRTX texture slot | Done | No | No |
| RIBB material id (filter and layer flags matter), emission rate, gravity, rows and columns | Done | No | No |
| RIBB trail ends when the emitter is hidden | Not checked | No | No |
Attachments, events, lights, cameras and collision
27 rows, 3 drawn or played in full.
| Feature | Read | Drawn | Checked |
|---|---|---|---|
| ATCH attachment points (weapon, overhead, origin ...) | Done | No | No |
| ATCH visibility track | Done | No | No |
| LITE lights (all kinds, colours, intensity, attenuation, tracks) | Done | No | No |
| CAMS position, target, field of view (portraits) | Done | Partly | No |
| CAMS translation, target and roll tracks | Done | Partly | Partly |
| EVTS SND (sounds), FPT (footprints), SPN (spawned models), UBR (unit blood), SPL (splats) with their lookup tables | Done | Partly | Partly |
| CLID collision shapes | Done | Nothing to draw | No |
| Visibility tracks (emitters, lights, attachments) | Done | Partly | No |
| ATCH attachment path (child model) and attachment_id | Done | No | No |
| ATCH name to point lookup (Origin Ref, Overhead Ref, Chest Ref, Hand Left, Weapon ...) and child models loaded | Not checked | No | No |
| EVTS SND (AnimSounds.slk) | Done | No | No |
| EVTS FPT (footprints) | Done | No | No |
| EVTS SPN (SpawnData) | Done | No | No |
| EVTS UBR (unit blood) | Done | No | No |
| EVTS SPL (SplatData.slk) | Done | No | No |
| EVTS KEVT global_sequence; key times are sequence-relative | Done | No | No |
| LITE kind (omni, directional, ambient), attenuation start and end | Done | No | No |
| LITE colour, intensity, ambient colour and intensity | Done | No | No |
| LITE seven tracks (KLAV, KLAC, KLAI, KLBC, KLBI, KLAS, KLAE) | Done | No | No |
CLID plane (kind 1) is approximated as a box (parser.rs:503) |
Partly | Nothing to draw | No |
| CLID sphere centre and radius, box min and max | Done | Nothing to draw | No |
| CAMS name, near and far clip | Done | No | No |
| CAMS tracks KCTR, KTTR, KCRL with roll; cameras beyond the first | Done | No | No |
| Billboard flag: billboarded | Done | No | No |
| Billboard flag: lock X | Done | No | No |
| Billboard flag: lock Y | Done | No | No |
| Billboard flag: lock Z | Done | No | No |
Where the detail lives
Each row has an owner, a test or a screenshot behind it, and notes in the repository's docs/MDX-COVERAGE.md. The Warcraft III behaviour is studied from the open OpenWCIII project for its rules and numbers; Aqua IV writes its own code. See also the look for how models are lit, and rendering.