Atlas of Bretonnia

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.

The words of the trade§

WordWhat it means
Packa mod's archive (.pack), placed in the game's data folder; to play, you tick it in the launcher
Table, keya table of the game's database; each row has a unique key, and a mod adds fragments (pieces of a table) to it
.locthe displayed texts: one key per sentence
Startpos, ESFa campaign's turn-0 save (startpos.esf), in Total War's binary format
Generation packa pack loaded only on the day the startpos is generated: it serves the start_pos_* tables to the game
raw_data / working_datawhat the Assembly Kit's tools read / what they produce
Campaign playable areathe campaign_map_playable_areas row that describes a campaign's map
Control casea 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 pointera 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:

1 · the logical halfIThe databasedeclare map, regions, campaignregionscampaignsIICAIMEpaint the layersmap.hextile_map.pngIIICAIMEvalidate (11 checks)savedTweak and Terry closedproject in raw_dataIVCAIMEexport (Process)map_data.esfpathfinding.ppdborders.pbd<campaign>_lookup.bmp2 · the visual halfVTerryrelief, textures, trees, water<map>.terrytile_map.png ← CAIMEVIBOBa run: the terrainfull_height_map.ddstile_list.binnew map: BOB runs only 6 of its 15 actions3 · togetherVIIRPFMbuild the pack:exports, terrain, tablesmy_mod.packVIIIThe gamegenerate turn 0 (two packs)startpos.esfhlp_data.esfspd_data.esfIXRPFMput turn 0 back in the packmy_mod.packXIn gametest, read the logsfix and repeat: export again, then a new startpos1 · the logical halfIThe databasedeclare map, regions, campaignregionscampaignsIICAIMEpaint the layersmap.hexIIICAIMEvalidate (11 checks)IVCAIMEexport (Process)map_data.esfpathfinding.ppdborders.pbd<campaign>_lookup.bmpsavedTweak and Terry closedproject in raw_data2 · the visual halfmeanwhile: starts from the database (I),gets tile_map.png from CAIMEVTerryrelief, textures, trees, water<map>.terrytile_map.png ← CAIMEVIBOBa run: the terrainfull_height_map.ddstile_list.binnew map: BOB runs only 6 of its 15 actions3 · togetherVIIRPFMbuild the pack:exports, terrain, tablesmy_mod.packVIIIThe gamegenerate turn 0 (two packs)startpos.esfhlp_data.esfspd_data.esfIXRPFMput turn 0 back in the packmy_mod.packXIn gametest, read the logsfix and repeat:export again (IV), then a new startpos (VIII)
The road map. On top, CAIME builds the logical half: paint, validate, then export, once the project is saved, Tweak and Terry are closed and the project sits in raw_data. Below, meanwhile, Terry and BOB build the visual half, from the database and the tile map CAIME exports; for a new map, BOB runs only 6 of its 15 actions. RPFM builds the mod's pack; the game generates turn 0 with it and the start-table pack, then RPFM puts turn 0 back into it. Under each step, the files it produces. Every change means exporting again, then a new startpos.

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\db: it is the database CAIME and BOB read.

Where each file livesThe game<game>\Warhammer3.exestarted directly for the startposdata\the packs: the game's, yoursmy_mod.packyour modcampaigns\<campaign>\startpos.esfloosecampaign_maps\<map>\hlp_data.esf, spd_data.esf, loose (for us)the pack wins over these loose copies, startpos.esfincluded: put back in the pack, then removedscript_log_DDMMYY_HHMM.txtthe script log, when turned onThe Assembly Kit<Assembly Kit>\from Steam: <game>\assembly_kit\binaries\its programsbob.modder.x64.exeBOBtweak.modder.x64.exeTweak, and Terry, one of its modesbob*.logBOB's logsdave\DaVE.retail.x64.exeDaveraw_data\what the tools readback up before writing to db\EmpireDesignData\campaign_maps\<map>\map.hex: your project, the only place Map Data worksworking_data\what they producelocked while Terry is opencampaign_maps\<map>\what Process writes: map_data.esf, pathfinding.ppd…The game's settings and logs%APPDATA%\The Creative Assembly\Warhammer3\scripts\user.script.txtread at launchafter a generation, put it back as it wascrash_report\bad_mods_report.txtthe rejected data*.mdmpthe crash dumpslogs\mp_log.txthow far loading gotCAIMEits settings, and the official install%APPDATA%\CampaignMapToolkit\Caime\preferences.jsonits settings%LOCALAPPDATA%\CampaignMapToolkit\Projects\the draft of a new project (Ctrl+N)Map Data: denied here; save the project under raw_data(above)Tools\Release\MapDataBuilder.x64.exethe Map Data tool: missing from the v1.0.0 installer;depends on the version, check it
Four drawers: the game folder, the Assembly Kit, the game's settings and logs, and CAIME. In gold, your mod; in red, the trap of each place.

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.

Anatomy of a packatlas_chevaliers.packyour mod: an archive, placed in the game's data folderdb/land_units_tables/never rename or move it (RPFM)atlas_land_unitsyours, with your prefix; @ or ! in front: only to modify agame rowdata__the game table's name: never in your packtext/db/atlas.locone key per sentence; applies to every languagescript/campaign/mod/atlas_script.lualoaded by the game on its ownui/units/icons/<card>.pngthe unit card: 60 × 130ui/flags/<faction>/the flagsui/portraits/portholes/portrait_settings*.binportrait settings, per art setvariantmeshes/variantmeshdefinitions/*.variantmeshdefinitiona new modelcampaigns/<campaign>/startpos.esfmade by the game; one per campaigncampaign_maps/<map>/hlp_data.esf · spd_data.esfmade by the game, for a new mapsettings.rpfm_reserved · notes.rpfm_reservedRPFM's settings and notes, ignored by the gameTables and .loc files: merged by key.Other files: same path, only one wins.
A pack stores each file at its path. The fragments of one table (a _tables folder), like .loc files, are merged by key; for any other file, two files at the same path do not both load: only one wins. In gold, what the game makes; in grey, what only RPFM reads.

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.

Campaign: the order of runsfor a map the game's database knowsA map unknown to the game's database (the one in CA's packs): BOBonly runs 6 actions out of 15, without a message. A control case,then the community.The reliefthe next steps read it from your packfull_height_map.ddsfull_logic_map.compressed_map1Relief into your pack, the pack into the game's data folder, thenclose and reopen BOBThe tile maptile_list.bintile_mask.ddspatch_mask.dds2Tile map into your pack, then close and reopen BOBThe global tile maproads and cliffs, taken from the tile map in your packglobal_map/tile_list.bin3The placed objectsBOB reads CAIME's map.hex, which must match the database;redo it for every region added or removed; up toten minutes, the tutorial saysglobal_props.bin4Generate Camera Height Map: this action crashes BOB;camera_heightmap.png is made separately (the tutorial:approximated from the heightmap, scaled).
The order from tw-modding's campaign map tutorial. BOB reads the packs in the game's data folder: after the relief, then after the tile map, the pack goes there, then you close and reopen BOB; from Terry, each Process with BOB starts a new BOB.

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%\The Creative Assembly\Warhammer3\scripts\user.script.txt. 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§

  1. 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

  2. The game's script

    user.script.txt loads the packs, asks for the generation, then for the game to close.

    trap: restore it afterwards

  3. 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

  4. 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

  5. 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:

  1. The mod pack, built and placed in the game's data folder, up to date (scripts included): the generation reads its tables (campaigns, the map…) and runs its scripts.
  2. The generation pack, also placed in data: it holds the start_pos_* tables (see “The generation pack” below).
  3. The script: keep a copy of user.script.txt if it exists, then write the lines below into it: one mod line per pack, the generation, the closing.
  4. The generation: with Steam open and the game closed, start the game's executable directly (Warhammer3.exe; for us, a steam_appid.txt file sat next to it, as RPFM does), then wait for it to close by itself, without touching its window.
  5. The produced files: the game writes <game>\data\campaigns\<campaign>\startpos.esf; with process_campaign_ai_map_data, for us, hlp_data.esf and spd_data.esf in <game>\data\campaign_maps\<map>\. Check their date and time.
  6. 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.
  7. 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;
The startpos: three places,one round tripThe script%APPDATA%\The Creative Assembly\Warhammer3\scripts\user.script.txt1written by hand: one mod line per pack, then the three commands(the code above)keep a copy before; put it back as it was after, otherwisethe game closes on every launchread at launchThe game<game>\Warhammer3.exe2started directly, Steam open: it loads the two packs,generates turn 0, then closes by itselfdata\my_mod.pack4the mod: campaigns, the map, the scripts; on the wayback, the three files copied into its sources, then thepack rebuiltmy_start_tables.packthe start_pos_* tables only; never in the pack you playcampaigns\<campaign>\startpos.esf3campaign_maps\<map>\for us, the game wrote them herehlp_data.esf · spd_data.esfWith process_campaign_ai_map_data (new map). The timevaries a lot: 16 s for us (400 × 440 hexes);tw-modding's campaign map tutorial gives under a minutefor spd, about five minutes for hlp on a medium map andwell over an hour on a large one; RPFM announces 10 to30 minutes.written loose: once back in the pack, remove themfrom data, the pack wins over themCheckthe three files' date and time: those of the generation.bad_mods_report.txt: no complaint, in:%APPDATA%\The Creative Assembly\Warhammer3\crash_report\scripted campaign: a new game has its intro and missions(save counter at 1)
Three places: the script in %APPDATA%, the game and its two packs in <game>\, the produced files loose in data. They go back into the mod pack, which is rebuilt; then the loose copies go.

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.

What a startpos requires: the crashes we went through§

When it breaks,what is missing?During generationThe script_log stops right after "Loading Mods", with no dump.→ start_pos_regions: rebel_faction_name andlong_description empty, no "PLACEHOLDER".Generation crashes.→ A map_data.esf in the 0xABCA variant (not0xABCB), like CA's.When the campaign loadsA crash at the very start of loading.→ A primary slot for every settlement(start_pos_region_slot_templates).A crash while loading.→ For every faction, an AI group containing apersonality.A crash while setting up the player (new map).→ hlp_data.esf and spd_data.esf in the pack.On the selection screenNo lord to pick.→ The start_pos_starting_general_optionsrows.On turn 1A character in the map's corner, on cell (1, 1).→ Its start_pos_character_to_settlements link(character placed at (0, 0)).No intro, no missions.→ A startpos generated AFTER the scripts (save counterat 1).First: bad_mods_report.txt names the rejected row, one ata time, in:%APPDATA%\The Creative Assembly\Warhammer3\crash_report\
When it breaks tells you where to look: each symptom we went through, and the row or file that was missing.

When the game crashes: reading the clues§

Five questions, in order; each “yes” tells you which file to open:

When the game crashes1Does the game close within 10 s?yes →Is Steam running? Is user.script.txt back to howit was?no2A recent bad_mods_report.txt?yes →the rejected table and row, one at a time.%APPDATA%\The Creative Assembly\Warhammer3\crash_report\bad_mods_report.txtno3Does mp_log.txt stop before the campaign?yes →its last line tells the stage reached: databaseread, checksum, campaign environment.%APPDATA%\The Creative Assembly\Warhammer3\logs\mp_log.txtno4An error in the script_log, or a stop?yes →the faulty script and its line. Generation stoppedright after "Loading Mods": some text in yourstart tables (PLACEHOLDER).<game>\script_log_DDMMYY_HHMM.txtwhen turned on: a non-empty script/enable_console_logging filein a loaded packno5A .mdmp dump: a read at address 0 or close by?yes →often, not always, some expected data is missing:look for it in your tables. Note the address withthe game version.%APPDATA%\The Creative Assembly\Warhammer3\crash_report\*.mdmpnoNothing speaks: a control case, then the community, withyour logs and the dump (CAIME's Discord, tw-modding).
Five questions, in order; each “yes” tells you what the file teaches, and where it is. If nothing speaks: a control case, then the community.
Reading a dumpThe dump: a .mdmp filethe game writes it here when it crashes:%APPDATA%\The Creative Assembly\Warhammer3\ crash_report\*.mdmpthe *.stack.txt next to it says almost nothing; the dump does.1The reader: cdb.exe, from WinDbgwinget install Microsoft.WinDbg --scope usercdb.exe, not on the PATH, is in the amd64 subfolder ofthe folder PowerShell gives:Get-AppxPackage Microsoft.WinDbg*InstallLocation: the package's <folder>2The command, in PowerShell$cdb = "<folder>\amd64\cdb.exe"& $cdb -z <dump.mdmp> -c ".ecxr; k 40; q".ecxr: the faulting thread · k 40: the stack · q: quitWithout .ecxr, wrong thread: you are reading the crashreporter's.3The output, schematic(…): Access violation - code c0000005a0:000> .ecxrrax=… rcx=0000000000000000b…Warhammer3!…+0x…:c mov rax,qword ptr [rcx]b0:000> k 4000 Warhammer3!…+0x…01 Warhammer3!…+0x…ac0000005: an access violation, on a read or a write.brcx is 0 and the instruction reads [rcx]:a read at a null or near-nulladdress: a null pointer.cthe crash address, to note with the game version.Null pointer: often, not always, some expected data ismissing. Look for it in your tables first.4
Four things to know: where the dump is, where cdb is, the command, and what the output says. The output is schematic: the addresses depend on the crash and on the game version.

Testing without playing: automated games§

To know whether a campaign holds for thirty turns, you don't have to play them. Our method:

Starting an automated gameDuring the test, nobody touches the PC:a human click skews it.A separate test packcreated for the test, removed afterwards; two scripts:script\frontend\mod\the menuscript\campaign\mod\the gameA new name: a file already loaded is skipped silently.loaded as for the startpos, one mod line per pack:%APPDATA%\The Creative Assembly\ Warhammer3\scripts\user.script.txt:mod my_mod.pack;mod my_test.pack;all_players_ai;all_players_ai, optional: the AI also plays for the player,but neither recruits nor builds for them, and no mission is issued.Checked on patch 8.1 and update 9.0.Steam open, Warhammer3.exe started directly;user.script.txt kept before, put back after.1The menu, click by clickmain > button_campaignmain > button_start_campaign_newcampaign_select_new> CcoCampaignMapPlayableAreaRecord…> button_campaign_entryif the campaign list shows; "…": your campaign entry's numberlord_select_list > list_box > *> lord_buttonlord_key property = the character's id instart_pos_characters; the list fills in 20 to 45 scampaign_select_new > button_start_parent> button_start_campaigncampaign_key propertyNot frontend.start_campaign (the menu's Lua launchfunction): for us, it closed the campaign within seconds.2
A separate test pack, loaded through user.script.txt as for the startpos, then the menu, click by click: each component is addressed by its path in the interface.
During the gameThe campaign openscustom_loading_screen > bottom_parent> button_continueCamera set on the leader, at playing height: the testmust reproduce what a player experiences.3The turn loopon turn 1, first close the notifications, one by one:end_turn_docker > notification_frame> button_skip× each notificationhud_campaign > faction_buttons_docker> button_end_turnPlayer mode: the loop on every turn. all_players_ai mode:turn 1 only, later turns play by themselves.4The reportturns played, script_log errors, dumps; thenuser.script.txt put back as it was and the test packremoved, even after a forced stop.Control case: a case known to be good, same command,options included.A crash that comes and goes: at least ten runs pervariant; until then, "lead", not "cause".5next turn
The campaign opens, then the turn loop: the notifications dismissed one by one, then the end of the turn. The report only counts against a control case.

Workshop advice§

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.

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>\data\campaigns\<campaign>\startpos.esf; for us, hlp_data.esf and spd_data.esf in <game>\data\campaign_maps\<map>\. 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\db: 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%\The Creative Assembly\Warhammer3\crash_report\bad_mods_report.txt 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%\The Creative Assembly\Warhammer3\crash_report\bad_mods_report.txt (with the .mdmp dumps next to it), %APPDATA%\The Creative Assembly\Warhammer3\logs\mp_log.txt, 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/enable_console_logging 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
↑ Contents