πŸ›‘οΈ TA Archive

Procedural Map Generation

The Map Builder on this site generates playable Total Annihilation maps entirely in your browser. This article documents how it works β€” the same format knowledge from Reverse Engineering TA Files, applied in the opposite direction: instead of parsing a map, we write one.

The trick: borrow the tile atlas

A TNT file contains three big blocks: the tile map (which tile goes where), per-cell attributes (height, features), and the tile atlas β€” the actual 32Γ—32-pixel tile graphics, unique per map. A big map carries thousands of unique tiles, easily 10+ MB.

Creating tile graphics from scratch is a content problem, not a format problem β€” that’s where AI-generated maps (and several failed generator attempts) produce the infamous “random tile noise”. Our builder sidesteps it entirely: it borrows the complete tile atlas of Ancient Issaquah, a Cavedog map whose terrain covers green land, cliffs, shores and water. Every tile in the atlas is indexed with its elevation profile (the four height values of its 2Γ—2 attribute cells), learned from the original map.

That gives the generator something no text model has: a vocabulary of tiles that are known to look good, each tagged with the terrain height it represents.

The pipeline

  1. Heightmap β€” value noise over the map grid (one height per 16-px attribute cell), with an optional island falloff that pushes the map border below sea level.
  2. Tile matching β€” for every 32Γ—32 block (2Γ—2 cells), look up the atlas tile whose elevation profile best matches the four generated heights (minimum absolute difference). The result is consistent terrain: shores get shore tiles, heights get highland tiles β€” because that’s what the atlas tiles represented in the original map.
  3. Attribute block β€” the chosen tiles’ height values are written into the attribute cells verbatim, so the visible terrain and the walkable heights agree exactly. Features are set to none (0xFFFF).
  4. Assembly β€” 64-byte header (pointers to each block), tile map, attributes, only the used subset of the atlas, then the minimap (252Γ—252 palette indices, water/lowland/highland colored).
  5. Companion OTA β€” a matching mission file with four ring-layout start positions, generated by the same rules as the OTA Builder.

Header field 0x2c: the flag that nobody documents

Our first generated map parsed perfectly, rendered a minimap β€” and crashed Total Annihilation on launch. The culprit: header field 0x2c (kbot calls it Unknown1), which we had set to 0. Checking every real TA map we have: the field is 1 in all of them, without exception (195/195). The engine evidently requires it to be 1 for a well-formed TA map; write 0 and the game dies before the minimap is even considered.

Two more conventions we confirmed the same way, by comparing 195 maps:

This is exactly why the builder is verified against the archive: the format knowledge exists in no single document, only in 600 files that happen to agree.

Step tiles and walkability (offline generator, 2026-09-05)

The browser builder matches tiles to arbitrary heights. For free-form terraced terrain we went the opposite way: quantize a value-noise heightmap onto the green tileset’s quasi-flat levels (191/208/224/236/255 β€” no tileset has perfectly flat tiles at each level; “flat” means Β±8 corner spread), then fill every transition with real step tiles from a step inventory extracted from the atlas.

Hard-won details:

Two free test maps from this pipeline (noise-seeded terraces, 4 start positions): Terra_16x16_s7 and Terra_16x16_s11 in the download archive.

On-the-fly synthetic tilesets (2026-09-05)

The atlas-borrowing approach has a hard ceiling: tile shading is baked into the borrowed graphics, and the green step tiles of Ancient Issaquah carry only weak shading (luminance spread 15–45/255 across a tile) β€” slopes are feelable (the engine slows units per height rules) but invisible. Since a TNT embeds its own tile graphics, nothing stops us from inventing a tileset at generation time (_gen_synth.py):

Minimap pitfall: the minimap is 252Γ—252 palette indices, not luminance. Writing raw height-derived brightness values produces wild colors β€” it must map height through the same palette ramp as the tiles.

Open: texture variety (dirt/rock sprinkles via a second ramp), synthetic cliff tiles, and porting the shading idea back into atlas-based maps.

AI-generated image maps (2026-09-05)

The pipeline went from borrowing tile atlases to painting the entire map as an image and slicing it into tiles β€” the Cavedog workflow, automated:

  1. Color image (any AI generator, orthographic top-down prompt) is rescaled to the map pixel grid and mapped onto PALETTE.PAL (nearest color, 5-bit spatial hash).
  2. Grayscale heightmap (generated as a companion image in the same AI session so composition matches) rescaled to the attribute cell grid, blurred, minima compressed (craters must not become impassable holes), quantized to 8-unit steps with a walkability relaxation pass (max 24 per cell).
  3. Feature map (optional third image, signal colors on black: green = tree, red = vent, white = metal pad, gray = rock) is sampled per attribute cell and written into the attr feature indices.
  4. Paint-then-slice: the whole image is rendered first, then cut into 32Γ—32 tiles β€” tile-boundary artifacts are impossible by construction.
  5. Start positions are auto-placed on the 4 flattest, far-apart land cells.

Gotchas learned in-game:

The whole pipeline runs client-side at β€” upload three images, get TNT+OTA.

Try it

Head to the Map Builder, pick a size and seed, hit Generate, download .tnt + .ota, drop both into your TA folder and start a skirmish. If a generated map looks off in-game, that’s a bug we want to hear about β€” contact in the Imprint.