Workshop journal · · 13 min read
Tutorial: porting a Warhammer I campaign map to Warhammer III
Moving a campaign map from one Total War to another is not copy and paste: two engines, two formats, two coordinate systems. Here is the order I followed with Claude for The Season of Revelation, why each step matters, and the traps that cost us hours.

Before you start
This tutorial describes a method, not a magic button. All of it was checked on our port, from 20 to 25 September 2026, up to the game's 9.0 update. You need to be comfortable with the game's files, and patient. You will need:
- Warhammer III and its Assembly Kit, CA's official tools (Steam → Library → Tools), plus the DLCs for your map's factions: without them, those factions exist in the database but not in game.
- Warhammer I and its Assembly Kit.
- CAIME (Campaign AI Map Editor), the Campaign Map Toolkit's editor, for the logical map, and RPFM for packs and tables.
- A scripting language (Python for us) for everything repetitive: layers, tables, checks.
- A debugger, cdb (it comes with WinDbg): it's what makes crashes talk.
Here is the whole path at a glance. Each step has its own section below, with its commands and its traps.
Vocabulary in one minute
- Logical half, visual half: a campaign map describes the same land twice. The logical half is what the game computes (regions, movement, settlements); the visual half is what the player sees (terrain, textures, trees, water).
- Hex: one cell of the logical half's grid. Our map is 400 × 440 of them.
- map.hex: the CAIME project, i.e. the logical map, layer by layer (regions, ground types, climates, rivers, roads, settlements).
- ESF: one of CA's binary formats, used by
map_data.esfand by the startpos. - Startpos: the file that freezes a campaign's starting state (factions, characters, settlements, diplomacy).
- Database table: one of the game's data tables (regions, campaigns, factions…).
- Pack: the mod archive, dropped into the game's
datafolder. - Terry: the Assembly Kit's terrain editor. BOB: the builder that turns sources into game files.
The logical half
CAIME: ground types, regions (borders in red), road, river, settlement slots (schematic: a Warhammer III town covers 19 hexes). This is what the game computes.
The visual half
Terry and BOB: relief, coast, forests, water, set dressing. This is what the player sees. Both must describe the same land.
Step 0: take inventory
Before touching anything, measure. Warhammer I's kit ships the mini-campaign's logical map (its map.hex: 400 × 440 hexes, 61 regions) and its database, but not its campaign terrain. The compiled terrain, the startpos and the scripts are in the game's packs, which you read with RPFM.
The inventory also tells you what got renamed. Faction keys change prefix from one game to the next: match them on culture and on the end of the key, never on the prefix (wh_dlc05_brt_mini_bastonne becomes wh_main_brt_bastonne). A script applies the rules in order and writes down the reason for each exception. For us, all 30 factions found a counterpart; only one orc tribe was missing, replaced by a minor tribe that isn't on the map.
Step 1: the logical map in CAIME
The logical map tells the game where the regions, settlements, rivers, roads and impassable areas are. Use CAIME 1.0.1 (23 September 2026) or later: it reads Warhammer I's mini-campaign maps, which 1.0.0 couldn't open, and it loads the database on any Windows whose decimal separator is the comma (French, for instance).
Declaring the map
First the kit's database: the map, its campaign and playable area, its regions (each with a unique RGB colour) and its provinces. Nine tables are involved (campaigns, campaign_map_playable_areas, regions, campaign_map_regions, region_to_province_junctions…), and the map's name comes back in all of them, case included. Column by column: the CAIME guide.
Remapping the layers
Then bring the layers of Warhammer I's map.hex into CAIME. Layer indices don't survive the jump between games: 13 land ground types and 24 climates in Warhammer I, 15 and 40 in Warhammer III. So remap by name, never by number. Settlement slots grow too: 7 hexes in Warhammer I, 19 in Warhammer III, as a disc around the centre, with 16 on land and 3 on the sea side for a port.
Validate, then export
CAIME's command line does both. Save first, keep the project in its place under raw_data, and close Tweak and Terry: the Map Data export depends on it, and CAIME's warning doesn't spot the Warhammer III kit's tool.
CAIME.exe validate --map map.hex --all
CAIME.exe process --map map.hex --map-data --pathfinding --trade-routes --lookupThe borders (borders.pbd) come next, through the Process → Borders data menu: from the command line their options stay empty and the export fails, which is why process --all always ends in failure. You get map_data.esf, pathfinding.ppd, borders.pbd, trade_routes.ptd and the region lookup image. Success shows in the Logger: “MapDataBuilder exited with code 0 (Success)”.
- Row 0 of the grid is at the bottom of the map: an image drawn “top up” gets flipped before it is encoded.
- In a layer file (
.hex_layer), Impassable is 1 for passable. Never infer what a flag means from its name. - If your map's row is the last one in
campaign_map_playable_areas, the Map Data export crashes. One row after yours is enough. validatefails as soon as there is one Warning, and even CA's maps fail: read the report. Its settlement sprawl rule fails five mountain-pass settlements on CA's Immortal Empires map: judge those in game.- Change one hex and you have to re-export, then regenerate the startpos: what to redo.
Step 2: the startpos
The startpos (startpos.esf) is the turn 0 save: factions, regions, characters, armies, diplomacy. A campaign has only one, and a mod replaces it entirely. The game builds it itself, from the Assembly Kit's start_pos_* tables.
But the game doesn't read the kit's XML: you hand it those tables in a generation pack, loaded only for the generation, next to the mod pack. They never go into the pack people play. Generation is driven by %APPDATA%\, which the game reads at launch:
mod my_mod.pack;
mod my_start_tables.pack;
process_campaign_startpos my_campaign;
process_campaign_ai_map_data;
quit_after_campaign_processing;- Put the mod pack, up to date (scripts included), and the generation pack in the game's
datafolder. - Keep a copy of
user.script.txt, then write the lines above into it: onemodline per pack. - With Steam open, launch
Warhammer3.exeand wait for it to close on its own, without touching its window. - Check the date of
startpos.esf,hlp_data.esfandspd_data.esf, copy them into the mod's sources and rebuild the pack: it wins over the loose copies, which you then remove. - Put
user.script.txtback the way it was: otherwisequit_after_campaign_processingwill close the game at every launch.
process_campaign_ai_map_data writes the AI's pathfinding data (hlp_data.esf, spd_data.esf), essential for a new map: without it, the game crashes while setting up the player. For us, 16 seconds for 400 × 440 hexes. RPFM's Build Startpos loads only one pack; with two, we write the script by hand. The details: the tools guide.
What a startpos needs
A startpos often generates even when it's wrong: the game crashes at loading, or later. Here is each symptom we went through, and what was missing:
Step 3: terrain with Terry and BOB
Neither Warhammer III's kit nor Warhammer I's ships a campaign's raw terrain. ChaosRobie, the author of Immortal Empires Expanded, rebuilt Immortal Empires' as a Terry project and shared it: that's the model to follow. The project lives in raw_data/, and BOB's recipe in the parent folder's rules.bob.
Raster sizes follow the hex count: 8 pixels per hex (plus 4 rows) for heights, seabed and textures, so 3200 × 3524 for us; 4 for the fog of war; 2 for trees, snow, corruption and tile_map.png. The sea is declared in tile_map.png (the sea tile), not by height. The details: the Terry guide.
Building with BOB
BOB builds the project in about a minute; the very first pass, which compresses the heights, took about 26 minutes. So you can iterate without launching the game. After each pass, read bob.log (the number of actions announced, then one STATUS: Finished line per action done) and count the “Failed to find tile” lines in bob_warnings.log: each one is a hole in the terrain.
This is where a new map hits the wall: for a map the game's database (the one in CA's packs) does not know, BOB runs only 6 actions, with no heights or textures, and no message; adding the map to the kit's database or to a mod pack changes nothing. It is a limit of the tool: run a control case, then ask the community (the CAIME Discord, tw-modding), without taking CA's tool apart or working around it.
- The Generate Camera Height Map action crashes BOB: make
camera_heightmap.pngyourself, from the heights. - BOB doesn't produce
lf_normal.dds, the terrain's distant lighting. We first reused Warhammer I's, green channel flipped; we now compute it from our own heights. - It writes the fog of war (
shroud_heights.dds) flat: we recompute it after each pass. - It rewrites its outputs on every pass: a hand edit in
working_datadisappears. - A campaign has two terrains: its own, and a battle terrain named by its playable area's
terrain_foldercolumn. Without it, the game crashes at the first land battle, even between two AIs, so often at the first end of turn. - An open Terry locks
working_data: close it before running BOB from its own window or building the pack. And until some terrain is compiled, open the project with 3D View unticked, or Terry crashes.
Step 4: objects, trees and mountains
This is where the map gets its personality. I wanted Warhammer I's map exactly as it was: its objects, its trees, its mountains. Warhammer III still reads Warhammer I's models, but not always their materials: base every conversion on the objects CA converted itself.
Warhammer I
Warhammer III- Never overwrite a Warhammer III file: the pack would replace it in every campaign. Keep the Warhammer I versions at paths CA doesn't use (
_wh1/for us): otherwise the engine picks up CA's textures lying next to them. - Only natural set dressing is visible to everyone; Chaos or vampire dressing shows by culture or by the region's corruption, the way CA does it.
- Immortal Empires places its mountains as objects (3,044 of them): we did the same with Warhammer I's sculpted relief: its 514 mountains and 635 inland cliffs, 1,149 placements. Its 233 coastal cliffs, made for its stepped coastline, are removed: on a smoothed coast, they drew walls.
- Warhammer I's tree list already has Warhammer III's format: its trees ship at their own positions.
The trap that cost us the most: two coordinate frames. Objects live in hex space, terrain in a grid of square pixels, and Terry stretches one onto the other. So a point (x, z) reads the terrain at row z × √3/2; read at row z, objects float or sink. And judge a “floating” object by the real bottom of its box, never by its origin point.
Step 5: rivers, coast and water
Water gets judged in game: Terry doesn't show it the way the game does. Three rules make it appear: its surface is at 0, made of large water planes placed as objects; the seabed (sea_height) goes below 0 everywhere, even under land; the sea is declared in tile_map.png.
Warhammer III's rivers are meshes using the sea's water material. We rebuilt Warhammer I's as continuous surfaces, a few centimetres of water over a dark bed, with a current computed along the network down to the river mouth: the water's direction can't be read from Warhammer I's tiles.
On the coast, Warhammer III's shoreline tiles only accept a regular strip: a one-hex spit of land leaves a hole. And from afar, the game simplifies the terrain: a one-pixel step between land and seabed becomes a staircase several cells wide. Warhammer I's coastline only stays smooth in game with a gently sloping bank and a sea that stays on the bottom near land.
Before
AfterStep 6: scripts and text
Our rule: Warhammer III gameplay, Warhammer I story. The campaign scripts are rewritten in Lua on top of CA's libraries. Nice surprise: the mini-campaign's 43 voiced lines are still in Warhammer III's sound banks, in English and in French.
- Use your own keys and never replace a CA table: other campaigns must stay intact with the mod enabled. A fragment carrying the game file's name replaces it entirely (who wins over whom).
- A mod only sets text keys the game doesn't know: otherwise it overrides CA's text in every language. Two languages, two packs: the second holds only its text and takes priority over the first.
- Check every name against the game's own localisation: in French it's Gasconnie, Aquitanie and Bastogne, not Carcassonne, Aquitaine and Bastonne.
- Regenerate the startpos after any script change: it's during generation that the scripts set the save counter to 1. Otherwise every new game thinks it's a save, with no intro and no missions.
Step 7: testing
Any pack that adds or changes a database table gets a startup test before it's announced: the game has to hold for 45 seconds. For us, one table too many (a content ownership table, which has no place in a mod pack) was enough to close the game in eight seconds, with no message. After that, read the crash reports every time you pick the work back up: the diagnostic tree.
For long games, Claude set up automated runs. A separate test pack holds a Lua script that clicks through the interface like a player, closes the notifications one by one, then ends the turn; the pack is removed at the end and user.script.txt put back the way it was.
- The mode where the AI plays everything (
all_players_ai) tests stability, not story: no mission is issued to an AI. A lord's story gets tested in player mode. - An event that never arrives: on our map,
FactionTurnStartreached no script at all. In the released game, a listener condition that crashes cancels the event for everyone, without writing anything to the log. Three CA listeners were waiting for Kislev and Cathay systems our campaign doesn't load; once removed at startup (core:remove_listener), the event came back. - Steam has to be running: otherwise the game closes within a few seconds, and you think it crashed.
One last tip: if someone is at the computer, warn them before launching the game. I clicked in test games myself, and we took it for crashes.
The recap
- Inventory: what each game ships, keys matched, a phased plan.
- Logical map: database declared, layers remapped by name, validation, export (Borders through the menu).
- Startpos: start tables in a generation pack, AI data, pack rebuilt,
user.script.txtput back. - Terrain: a Terry project modelled on ChaosRobie's, BOB and its logs, a battle terrain.
- Objects and mountains: Warhammer I's files at paths of your own, coordinate frame checked.
- Water: surface at 0, seabed below 0 everywhere, continuous rivers, gently sloping coast, judged in game.
- Scripts and text: your own keys, names checked, startpos regenerated.
- Testing: a 45-second startup, crash reports, automated games.

campaign_maps, the startpos in campaigns, our table fragments in db, one folder per table.The story of those days is in this article, and the lessons on method in this one. What comes next is being drawn on the map of all Bretonnia.