Modders' workshop · guide · 33 min read
The campaign modder's tools: overview, startpos, crashes and tests
Where do you start when you want to mod a Total War: Warhammer III campaign map? This page is the overview: each tool, what it is for and its guide; the words of the trade; then what depends on no tool in particular: the start position, crashes, automated test games. Written after four days porting a complete map from Warhammer I to Warhammer III.
Reviewed on 25 September 2026, update 9.0. Written against its sources, CAIME 1.0.1, RPFM 5.0.6, the Assembly Kit and the game on patch 8.1; what 9.0 changed on our project has been added, the rest has not been rechecked point by point. When in doubt, check, and tell us.
The toolbox at a glance§
A campaign goes through eight families of tools. None does everything, each has its own domain, and almost all have a trap. The first four have their own guide.
- CAIME (Campaign AI Map Editor)
The Campaign Map Toolkit's editor, for the logical half of the map: regions, ground, climates, roads, towns, and its exports (Map Data, pathfinding, borders).
- RPFM (Rusted PackFile Manager)
Open, create and edit packs: tables, texts, scripts, images; add a unit, a faction, a lord; be driven by an AI (MCP server).
- Terry
The Assembly Kit's 3D terrain editor: relief, textures, vegetation, props, logical zones.
- BOB
The Assembly Kit's builder: it turns sources into game files and packs, following
rules.bobfiles. - Dave and Tweak
Dave (CA writes it “DaVE”:
DaVE.retail.x64.exe; the Steam launcher says Database Editor: Dave) edits the Assembly Kit's XML tables (the database CAIME and BOB read); Tweak is the editor of which Terry is a mode.Steam → Library → Tools
- A scripting language
Python for us: everything repetitive, declaring tables, producing layers, checking a pack, running tests.
- WinDbg (cdb)
Reading the game's crash dumps.
cdb.exelives in the package'samd64subfolder, outside the PATH.winget install Microsoft.WinDbg --scope user - The game's logs
Knowing what the game really did: loading, scripts, rejected data.
See “When the game crashes” below
The words of the trade§
| Word | What it means |
|---|---|
| Pack | a mod's archive (.pack), placed in the game's data folder; to play, you tick it in the launcher |
| Table, key | a table of the game's database; each row has a unique key, and a mod adds fragments (pieces of a table) to it |
.loc | the displayed texts: one key per sentence |
| Startpos, ESF | a campaign's turn-0 save (startpos.esf), in Total War's binary format |
| Generation pack | a pack loaded only on the day the startpos is generated: it serves the start_pos_* tables to the game |
raw_data / working_data | what the Assembly Kit's tools read / what they produce |
| Campaign playable area | the campaign_map_playable_areas row that describes a campaign's map |
| Control case | a case known to be good, rerun identically next to yours |
Dump (.mdmp) | a snapshot of the game's memory at the moment of the crash |
| Null pointer | a read at address 0 (or very close to it): often, not always, the game looked for some data and did not find it |
%APPDATA% | your Windows account's settings folder (type it into Explorer) |
Words specific to the map (hex, layer, swatch, lookup…) are in the CAIME guide.
Who does what, from database to game§
The whole chain of a new map, with each step's tool and the files it produces:
- The database: the Assembly Kit's tables (Dave, or the XML files by script), or a pack's (RPFM).
- The logical half: CAIME, then its exports: the CAIME guide.
- The visual half, meanwhile: Terry, then BOB: Terry, BOB.
- The pack: RPFM gathers everything: the RPFM guide.
- Turn 0: the game itself generates the startpos from that pack (below); the files it produces go back into the pack, which you rebuild.
The Assembly Kit in brief§
The Assembly Kit is Creative Assembly's official kit (Steam → Library → Tools). Three folders: binaries (its programs), raw_data (what the tools read: XML tables, terrain, maps) and working_data (what they produce). Back up before writing to raw_data\: it is the database CAIME and BOB read.
- Dave edits the
raw_data\tables; they can also be written by script, backup first.db\ *.xml - Tweak (
binaries\) is CA's editor. During CAIME's Map Data and Dynamic Resources exports, Tweak and Terry must be closed (Terry is a mode of Tweak). Don't rely on CAIME's warning: it looks for a program namedtweak.modder.x64.exe Tweak.AssemblyKit, while Warhammer III's kit shipstweak.modder.x64.exe. - Terry, a mode of Tweak: CA's page, written for the Warhammer I kit, presents it as the tool for campaign maps and battlefields, and states that the kit only lets you create battle tiles; we see the same in the Warhammer III kit, whose
raw_data\folder only holds battle data. While open, Terry locksterrain working_data: the Terry guide. - BOB turns sources into game files and packs, following
rules.bobfiles. BOB opens from the Assembly Kit's Steam launcher (Exporter: BOB), from its executablebob.modder.x64.exe, or from Terry (Process with BOB, Ctrl+P), which hands it the job. On the command line, its only known option is-no_console, the one the launcher passes: the BOB guide.
RPFM and packs in brief§
A Total War mod is a .pack file: an archive of tables, texts (.loc), Lua scripts, images and models. RPFM opens the game's packs read-only and builds yours; since version 5, its MCP server lets an AI drive it. Everything is in the RPFM guide, with three complete procedures: adding a unit, a faction, a legendary lord.
- Don't overwrite a game table: give your fragments your own name. When two fragments give the same key, the game keeps the one whose name sorts first; a fragment named like the game's file (
data__) replaces it entirely. - Don't redefine a game text: a mod's
.locfiles apply to every language. - In
data, a pack wins over loose files: a regenerated file (a startpos) only counts once it is back in the pack.
Campaign map terrain in brief§
Neither the Warhammer III kit nor the Warhammer I kit ships raw campaign terrain data. Yet Terry can open a campaign-type project: ChaosRobie proved it. For Immortal Empires Expanded, a mod he made with the CAIME team, he rebuilt the Immortal Empires map as a Terry project, and shared that project with the community. The project folder, its raster sizes, conventions and limits are in the Terry guide; the order of BOB's runs in the BOB guide.
A full run on a campaign terrain has 15 actions (for us, about a minute once the relief is compressed, 25 minutes the first time). For a map that the game's database (the one in CA's packs) does not know, BOB runs only 6 of them, without a message; adding the map to the kit's database or to a mod pack changes nothing. We know of no official way to lift this limit: make a control case, then ask the community. The control case: a project known to be good, copied under another name; if it also stops at 6 actions, your files are not to blame.
The start position (startpos)§
The startpos (startpos.esf) is the turn-0 save: factions, regions, characters, armies, diplomacy. Each campaign has a single start file, and a mod cannot add to it: it replaces it entirely (“there can be only one startpos”, says the modding wiki). Two mods touching the same campaign's startpos therefore do not combine.
The start_pos_* tables are written in the Assembly Kit (Dave), but the game does not read its XML files. For generation, they are served to it in a separate pack, loaded that day only; they never go into the pack you play. Only the generated files do.
The game builds the startpos itself, in a special mode driven by the file %APPDATA%\. Three routes lead there: RPFM's Build Startpos, BOB's Process start pos action, or a user.script.txt written by hand. For us, only writing it by hand worked. RPFM (right-click on the pack's root → Build Startpos) writes the generation's user.script.txt itself, but puts only one mod line in it: the open pack's. Our generation reads two packs (the mod and the start-table pack), so we write the script by hand. An add_working_directory line, which we had tried, made the game exit within nine seconds without producing anything.
Generating it§
- The start tables
The
start_pos_*tables, in the game's format, in a generation pack; next to it, the up-to-date mod pack.trap: the game does not read the kit's XML files
- The game's script
user.script.txtloads the packs, asks for the generation, then for the game to close.trap: restore it afterwards
- The game, in a special mode
It creates the campaign world, saves it, then closes. Steam must be running.
trap: closed does not mean done: check the file's date
- Turn 0
The startpos and, for a new map, the AI pathfinding data, written as loose files in
data.startpos.esf,hlp_data.esf,spd_data.esf - Into the pack
The three files join the mod's pack, which you rebuild.
trap: the pack wins over loose files
Order matters: the mod pack first, then the generation, then the rebuilt pack. Step by step, as we do it:
- The mod pack, built and placed in the game's
datafolder, up to date (scripts included): the generation reads its tables (campaigns, the map…) and runs its scripts. - The generation pack, also placed in
data: it holds thestart_pos_*tables (see “The generation pack” below). - The script: keep a copy of
user.script.txtif it exists, then write the lines below into it: onemodline per pack, the generation, the closing. - The generation: with Steam open and the game closed, start the game's executable directly (
Warhammer3.exe; for us, asteam_appid.txtfile sat next to it, as RPFM does), then wait for it to close by itself, without touching its window. - The produced files: the game writes
<game>\; withdata\ campaigns\ <campaign>\ startpos.esf process_campaign_ai_map_data, for us,hlp_data.esfandspd_data.esfin<game>\. Check their date and time.data\ campaign_maps\ <map>\ - The rebuilt pack: copy these files into the mod's sources, rebuild the pack, then remove the loose copies from
data: the pack wins over them. - The clean-up: restore
user.script.txt; left as is,quit_after_campaign_processing;closes the game on every launch.
mod my_mod.pack;
mod my_start_tables.pack;
process_campaign_startpos my_campaign;
process_campaign_ai_map_data;
quit_after_campaign_processing;process_campaign_startpostakes a single argument, the campaign key.- With
process_campaign_ai_map_data, the game also writes the AI pathfinding data (hlp_data.esf,spd_data.esf), essential for a new map: without them, the game crashes while setting up the player. The time varies a lot: 16 s for us (400 × 440 hexes); tw-modding's campaign map tutorial gives under a minute forspd, about five minutes forhlpon a medium map and well over an hour on a large one; RPFM announces 10 to 30 minutes. - To our knowledge, these commands are not publicly documented: we checked them on patch 8.1 of the game, then with update 9.0 (September 2026). Check them again after an update.
- Loading a mod: to play, you tick the mod in the launcher; to generate a startpos by hand, one
mod <pack>;line per pack inuser.script.txt. Started directly, the executable only loads the packs named this way. - The three files go into the pack:
campaigns/,<campaign>/ startpos.esf campaign_maps/and<map>/ hlp_data.esf campaign_maps/.<map>/ spd_data.esf - With RPFM (right-click on the pack's root → Build Startpos): the pack must be in the
datafolder, the campaign declared in the pack (campaigns_tables), and the game closed; everything the generation reads must be in that one pack. Through its MCP server,build_starposanswers “Success” in zero seconds: that means “game launched”, not “startpos built”; wait for the game to close, then callbuild_starpos_postandbuild_starpos_cleanup. Details: the RPFM guide.
The generation pack§
It is an ordinary mod pack, created with RPFM, used only for generation. It holds the start_pos_* tables in the game's format: pack tables, not the kit's XML files. For us, the campaign's campaigns row and the other tables the generation reads stay in the mod pack, loaded at the same time; with RPFM's Build Startpos, which loads a single pack, everything must be in that one.
- For us, it was built once from the start tables that BOB's database export writes, in the game's format, for each campaign (
raw_data\), without the XML files orEmpireDesignData\ campaigns\ <campaign>\ campaigns(the BOB guide describes that export). Then every row added to the kit was copied into it by script, through RPFM's MCP server. - Reread what BOB writes: it had set
defaultas the subtype of eight of our characters, a value the game rejects in a mod pack; we put back the kit's values. - Our notes do not describe copying the kit's rows into this pack by hand, in RPFM's window. To create a pack, add a table to it and paste rows: the RPFM guide.
- Check before launching the game: every value that points to another table (a faction, a subtype, an AI group) must exist there. Otherwise, the game's validator names the faulty record (box below).
What a startpos requires: the crashes we went through§
- Every settlement has a
primaryslot (start_pos_region_slot_templates): without it the startpos still generates, and the game crashes on loading. - Every faction has an AI group containing a personality: otherwise, a crash.
- Without
start_pos_starting_general_options, no lord can be picked on the selection screen. - In
start_pos_regions,rebel_faction_nameandlong_descriptionstay empty: any text (the kit's “PLACEHOLDER”) crashes world creation, with no dump and no crash report: the log stops right after the mods load. - A starting character placed at (0, 0) joins, as a garrison, the settlement that
start_pos_character_to_settlementsgives it; without that link, it appears in the corner of the map, on cell (1, 1). - A scripted campaign is generated AFTER its scripts: during generation, the scripts set the save counter to 1 (
cm:is_new_game()). With a startpos older than the scripts, every new game is taken for a saved game: no intro, no missions. - For us, CAIME's Map Data export (MapDataBuilder, which relies on a kit DLL) wrote a
map_data.esfin the ESF0xABCBvariant, while the ones CA ships are0xABCA; generation crashed until it was converted. Ask the CAIME team whether your version already writes the right variant. - Check before playing: compare the new startpos's structure with that of a startpos that loads (same blocks, same children).
When the game crashes: reading the clues§
Five questions, in order; each “yes” tells you which file to open:
%APPDATA%\: rejected data. TheThe Creative Assembly\ Warhammer3\ crash_report\ bad_mods_report.txt .mdmpdumps are in the same folder; the*.stack.txtsays almost nothing, the dump does.%APPDATA%\: how far loading got (database read, checksum, campaign environment created).The Creative Assembly\ Warhammer3\ logs\ mp_log.txt - The script log: a non-empty
script/file in a loaded pack, and the game writesenable_console_logging script_log_DDMMYY_HHMM.txtin its folder (an empty file is ignored). Clicks, even simulated ones, are logged there with the component's path. - The dump in the debugger: install WinDbg (
winget install Microsoft.WinDbg --scope user).cdb.exeis not on the PATH: PowerShell gives the package folder with(Get-AppxPackage Microsoft.WinDbg*).InstallLocation, andcdb.exeis in itsamd64subfolder. Then, in PowerShell,$cdb = "<folder>\(amd64\ cdb.exe" <folder>: that package folder), and& $cdb -z <dump.mdmp> -c ".ecxr; k 40; q". Without.ecxr, you are looking at the crash reporter's thread, not the faulting one. - Reading the verdict: code
0xC0000005with a read at a null or near-null address is a null pointer: often, not always, some expected data is missing. Look first in your tables for what the game did not find. - File crashes by test case and address: the address depends on the campaign and on the game version; note the game version next to each address. After an update, old addresses no longer compare.
- Steam must be running: otherwise the game closes within seconds, and it looks like a crash.
Testing without playing: automated games§
To know whether a campaign holds for thirty turns, you don't have to play them. Our method:
- A separate test pack (created for the test, removed afterwards) holds a Lua script that clicks through the interface like a player: menu, campaign, lord, game start, end turn. Components are addressed by their path in the interface.
all_players_ai;inuser.script.txtalso hands the player's turns to the AI; for us it neither recruited nor built for them: this mode tests stability, not the player's faction. To our knowledge, this command is not publicly documented: checked on patch 8.1 and update 9.0.
- At the end: a turn report, the
script_logread through, the dumps if any. Thenuser.script.txtrestored and the test pack removed, even after a forced stop. - The limits: in “AI plays everything” mode, no mission is issued to an AI. This mode tests stability (AI, invasions, dilemmas); a lord's story and mechanics are tested in player mode.
- An event that never arrives: on our map,
FactionTurnStartreached no script, underall_players_aias in player mode, whileFactionBeginTurnPhaseNormalarrived every turn. The cause: in the released game, the conditions of all of an event's listeners are evaluated in a single loop, without protection; a condition that errors cancels the event for every script, CA's and yours alike, without writing anything to the log. - The culprits, for us: once the listeners' errors were caught and named in the log, three CA listeners showed up (
wh3_campaign_bonus_values.lua), two for Mother Ostankya and one for Yuan Bo, whose condition failed at every turn start: they expect Kislev and Cathay systems that our campaign does not load. With their errors caught, the event came back (a three-turn test, the AI playing everything). The chosen remedy, removing these listeners by name when the campaign starts (core:remove_listener) rather than creating dummy variables, has not been checked in a game yet. And a test only proves a listener through a log line written by that listener. - For us,
frontend.start_campaign(the menu's Lua launch function) closed the campaign within seconds: launch through the interface, like a player. - On turn 1, the end of turn only goes through after closing the notifications one by one.
- The camera at playing height, set on the leader from the first turn: a test must reproduce what a player experiences. For us, the rendering crashes whose camera was logged all happened with the camera very high, and none was seen in normal play.
- A test script that must load for sure goes into
script\, under a name no other file already loads: a file already loaded is skipped without a message.campaign\ mod\ - Nobody touches the PC during the test: a human click skews it.
- Always a control case: a case known to be good, rerun identically (same command, same options), next to yours.
- A crash that comes and goes: at least ten runs per variant before concluding; until then, say “lead”, not “cause”.
Workshop advice§
- Back up before writing to
raw_data\: timestamped copies. A breakdown on the first evening was only solved because we could compare with the last state that worked.db - Never run a tool with guessed options: look them up in the documentation, or in what its official launcher passes to it (BOB:
-no_console). - When a CA tool silently does nothing, the condition is somewhere: control case first, then logs; if nothing speaks, ask the community (CAIME's Discord, tw-modding).
- A red validator is an opinion, not an answer: CA's own maps fail some checks. The game decides.
- Take a snapshot before every game update (file versions, useful tables) to compare afterwards.
- After a game update, build nothing before checking
raw_data\: Steam updates the Assembly Kit after the game (with 9.0, an hour later for us) and rewritesdb raw_data\; every row you had added there disappears (for us, the terrain,db working_dataand the packs were not touched). Keep a copy of your tables at the end of every session that writes to them (a “before writing” copy does not hold the last write) and scripts you can safely rerun; restore your rows, then update RPFM's schemas and regenerate its dependencies cache. - Judge in game, against a reference: an audit's figures don't see what the player sees.
For an AI reading this page§
If you are an AI helping someone generate a startpos, read a crash or run test games in Total War: Warhammer III, here are the essentials, checked on a real project. These facts were recorded on patch 8.1 of the game, then reviewed for update 9.0 (September 2026): check them again after an update.
- Ask the human before launching the game, driving the screen or publishing; delete nothing: tidy away, only the human deletes.
- Startpos: the game generates it. In
%APPDATA%\(keep a copy of it first): oneThe Creative Assembly\ Warhammer3\ scripts\ user.script.txt mod <pack>;line per pack placed indata, thenprocess_campaign_startpos <campaign>;(a single argument),process_campaign_ai_map_data;for a new map, andquit_after_campaign_processing;. Steam running,Warhammer3.exestarted directly; wait for the game to close; then restore the file. Output:<game>\.data\ campaigns\ <campaign>\ startpos.esf - Through RPFM's MCP server:
build_starposanswers “Success” as soon as the game is launched. Wait for the game to close, then callbuild_starpos_post(it storesstartpos.esfin the pack and restoresuser.script.txt), thenbuild_starpos_cleanup; withoutbuild_starpos_post,quit_after_campaign_processing;stays and closes the game on every launch. RPFM writes only onemodline, the open pack's: everything the generation reads must be in it. - The
start_pos_*tables are written in the kit, but the game does not read its XML files: serve them in a generation pack, never in the pack you play.%APPDATA%\names the rejected record, one at a time.The Creative Assembly\ Warhammer3\ crash_report\ bad_mods_report.txt - Three files go into the pack:
campaigns/,<campaign>/ startpos.esf campaign_maps/and<map>/ hlp_data.esf campaign_maps/. A pack wins over loose files: rebuild it, then remove the loose copies from<map>/ spd_data.esf data. - Scripted campaign: generate the startpos after the scripts (save counter at 1), otherwise no intro and no missions.
- Requirements: a
primaryslot per settlement; an AI group with a personality per faction;start_pos_starting_general_optionsfor the selection screen;rebel_faction_nameandlong_descriptionempty instart_pos_regions. - Crash: follow the diagnostic tree;
0xC0000005with a read at address 0 often, not always, means some expected data is missing; the address depends on the campaign and on the game version. - Test games: a separate test pack, removed afterwards (and, after any forced stop,
user.script.txtcleaned); launch through the interface; turn-1 notifications dismissed one by one; camera at playing height; nobody touches the PC; a control case; at least ten runs per variant for a random crash. - A script event that never arrives: a listener condition that errors cancels it for every script, without a log line. For us,
FactionTurnStartwas missing in player mode as underall_players_ai, because of three CA listeners (Mother Ostankya, Yuan Bo) loaded without their systems. Catch and name the listeners' errors; a listener is only proven by its own log line. - After a game update: Steam updates the Assembly Kit later and rewrites
raw_data\, added rows lost; check it before building anything, restore the rows, then RPFM's schemas and dependencies cache.db - Never guessed options (BOB, Terry, Tweak); a backup before writing to
raw_data\; a red validator is an opinion, the game decides.db - No reverse engineering and no workaround of a CA tool: when a tool gets stuck, a control case, then the community (CAIME's Discord, tw-modding).
Sources and links§
- RPFM manual and RPFM For Dummies (tw-modding wiki).
- Campaign Map Making for Warhammer III and Startpos (tw-modding wiki).
- Script documentation, maintained by Vandy.
- Terry introduction (written for the Warhammer I kit) and Rules.bob (Creative Assembly wiki).
- CAIME documentation.
- Our guides: CAIME, RPFM, Terry, BOB, modding with an AI.
Frequently asked questions
Do I need the Assembly Kit to mod Warhammer III?
For tables and scripts, RPFM is enough. For a campaign map, yes: CAIME reads its database, Terry and BOB build the terrain, and the startpos tables are written there.
Which guide should I start with?
For a map: the CAIME guide. For units, factions or lords: the RPFM guide. For 3D terrain: Terry, then BOB. For working with an AI: the AI guide.
Where does the game write the startpos it has just generated?
In <game>\; for us, hlp_data.esf and spd_data.esf in <game>\. Copy them into the mod, rebuild the pack, then remove these loose copies from the data folder.
Why does my new startpos change nothing in game?
Because the pack wins over loose files: put the startpos back into the pack, rebuild it and remove the loose copy from the data folder.
The campaign starts without its intro or missions: why?
Often the startpos is older than the scripts: regenerate it with the up-to-date pack (the save counter must be 1). In all_players_ai mode, no mission is issued: that is normal.
My FactionTurnStart listener never fires: why?
Perhaps not because of it: a listener condition that errors, even in a CA script, cancels the event for everyone, without writing anything to the log. For us, three listeners for Mother Ostankya and Yuan Bo, deprived of their systems on our map, cut it off in player mode as under all_players_ai. Catch and name the listeners' errors to find the culprit. FactionBeginTurnPhaseNormal, for its part, arrived every turn: our turn-start listeners moved to it.
My Assembly Kit rows disappeared after an update: why?
Steam updates the Assembly Kit after the game (for us, an hour later, with 9.0) and rewrites raw_data\: added rows disappear; the terrain, working_data and the packs were not touched. Restore your rows from your copies, or rerun the scripts that write them, before building anything; then update RPFM's schemas and regenerate its dependencies cache.
No lord shows on the selection screen: why?
The start_pos_starting_general_options rows are missing: they link a startpos character to a lord card (frontend_faction_leaders). Add them, then regenerate the startpos.
My startpos generates, but the campaign crashes on loading: what should I check?
A primary slot for every settlement, an AI group with a personality for every faction, hlp_data.esf and spd_data.esf in the pack for a new map, and the %APPDATA%\ file.
Why does the game close by itself on launch?
Often a user.script.txt left over from a startpos generation (quit_after_campaign_processing;), or Steam not running.
Why do my texts replace the game's in every language?
The game loads a mod's .loc files whatever the language. Only add new keys, and make one pack per language.
Where are the logs?
%APPDATA%\ (with the .mdmp dumps next to it), %APPDATA%\, the script log (script_log_*.txt) in the game folder, and BOB's logs in the kit's binaries folder.
How do I turn on Warhammer III's script log?
Put a non-empty script/ file in a loaded pack: the game then writes script_log_*.txt files in its folder.
This guide as plain text (Markdown), for AIs and offline reading
This is a community guide
A mistake, a pitfall you hit, a tip or a newer tool version: tell us, and every correction will be checked, then added with its source.
Suggest an idea