Atlas of Bretonnia

Workshop journal · · 10 min read

Working with an AI on a big Total War mod

Four days, a big mod, and an AI on execution. Here's the method I set up with Claude, Anthropic's AI assistant, to port The Season of Revelation to Warhammer III: the files, the guards and the habits that held, with real excerpts, and where to start if you want to try it yourself.

The campaign map in game: the Oak of Ages, the King's Glade and snowy woods
In-game capture: the Season map in our Warhammer III, the Oak of Ages, the King's Glade and snowy woods.

The brain and the execution

I've got the brain, and you're the execution.

Charles, to Claude (translated)

That split isn't a slogan. I set the vision, made the decisions, and above all judged in game, with my Warhammer I and Warhammer III screenshots side by side.

Claude did the rest. What it produced, and why a frontier agent rather than a free AI, I covered in A whole map in four days; here, I show how we worked together.

Who decides?The AI does it aloneRead the disk, the logs, the dumpsWrite and fix its scripts, in its own domainRun its tests without a windowKeep the project's documentsThe AI asks firstLaunch the game: nobody touches the PC during the testTake the screen, then check the keyboardPublish, post or send anythingNever the AI's callPasswords and accountsDeleting: only the human deletesThe final judgement, in game, against the referenceA lore fact without a source
Three families of actions: what the AI does alone, what it asks about first, what stays with the human. When in doubt, it asks. At first (First steps, move 5), everything goes through “asks first”; the green band widens later, in the agent's permissions. These rules go into the entry point, and whatever must hold for sure, into the permissions: a written rule steers, it does not lock.

My decisions are written in the file the AI reads first, marked “not to reopen”. So an AI picking the work up the next day doesn't pitch an option I already turned down, like carving up the Immortal Empires map. We don't replay the same debates every session.

The error log

On day one, I asked Claude to “take careful note of every mistake you've made and make sure it doesn't happen again”. The result is a file, the error log: about 146 entries on 23 September, over 260 two days later.

Each entry has four parts: what was done, the symptom, the cause, the rule. Here's one, as it stands in the file (translated from French, lines re-cut for phones):

21. [avoidable] Guessing the name of a file
    a tool produces.
For the map.hex round trip, I passed
Attritions.hex_layer to import-layer while
export-layer had written
layer_attritions.hex_layer and printed it;
the import failed ("Layer file not found")
and the byte comparison concluded
"identical" on a file never rewritten.
→ Rule: reuse the path the tool printed;
a test that compares two files first checks
that the write actually happened (timestamp
or "Saved" message).
Coded into the round-trip script.

Note the symptom: a test that says “identical” when nothing was written. That's the kind of false success an AI reports in good faith, and only a precise rule keeps it from coming back.

Each entry also gets a tag, settled by a single question: had anyone written it down somewhere before? If so, the mistake was avoidable, and the entry gives the rule that prevents it. If not, it was a discovery: the entry gives the fact and its proof, and the fact joins our guide's list of pitfalls, which counted 135 on 23 September (150 on the 25th).

A mistakewhat → symptom → causeHad someone alreadywritten it down?yesnoAvoidablethe rule that prevents itDiscoverythe proofCodeda script that refuses to goonThe guidethe fact joins itspitfalls: it becomesavoidablewhen possible
Every understood mistake gets written down: avoidable, it yields a rule, turned into code when possible, in a script that refuses to go on; a discovery yields a proof, and the fact joins the guide.

A written rule gets forgotten: code it

The log's clearest lesson is that a rule that's only written down gets forgotten again. Whenever possible, we turn it into a guard in a script. A guard doesn't get tired, and it doesn't rely on memory.

  • The pack build refuses to run if a file declared in a table is missing.
  • It checks the syntax of every Lua script before shipping it.
  • The text injection step rejects any text containing a control character.
  • A check makes sure the startpos save counter is 1.
  • Any pack that touches a database table goes through a scripted startup test before it's announced.

The opposite is telling. One very simple rule, never write a script through a “heredoc” (a block of text pasted into a terminal command), was broken more than ten times in three days, by several sessions. Usually with no harm done, once with 102 French texts stripped of their punctuation, repaired afterwards.

One rule, three strengthsLocked in the agentits permissions ("ask", "deny" rules) or a hook beforeevery command: the agent can no longer get round it.Claude Code: /permissions, or.claude/settings.json; Codex: see its documentationCoded in a scriptit refuses to go on: a file declared but missing fromthe pack, a Lua syntax error, a startpos whose savecounter isn't 1.Writtenin the entry point: the agent reads it… and may forgetit."never write Python through a heredoc": writtenon 21 September, broken more than ten times in three dayssafer
A written rule steers the agent; coded in a script that refuses to go on, or locked in its permissions, it actually stops it.

The guard, for real

On the evening of 24 September, after the ninth relapse of the day, we went to the safest level: a Claude Code hook. It's a small script Claude Code runs before each of the agent's commands; if the command has a forbidden shape, it never goes out, and the agent gets the advice instead.

# .claude/hooks/garde_commandes.py (excerpt)
# Claude Code runs it before every command.
def motif_refus(tool, cmd):
    if PYTHON.search(cmd):
        if HEREDOC_BASH.search(cmd):
            return ("Python passed "
                    "via heredoc (<<)")
    # … compound python -c, sed with backslashes
    return None

motif = motif_refus(tool, cmd)
if motif:
    sys.stderr.write(
        "Command refused by the workshop "
        f"guard: {motif}. {ADVICE}")
    return 2  # refused: the agent reads why
return 0      # otherwise the command runs

It's declared in the project's .claude/settings.json file, on Bash and PowerShell commands. Before plugging it in, we tried it on fifteen commands (seven to refuse, eight to let through). And if the guard itself breaks, the command goes through: a guard must never block the work through its own fault.

The five rules that cost the most

The log's first five entries are method rules. They're the ones that cost the most time whenever they were forgotten.

  • Never throw away a validator's messages: group them and count them. Filtered too hard, they hide the cause.
  • Check the data before calling it a bug: a tool message that cites a table gets checked in the table.
  • Have a reference document reviewed against its sources before delivering it: the first version of our guide had nine errors.
  • Ask what the screen would teach you that the disk or the command line can't, before driving a window.
  • Never launch a tool with guessed options: BOB's help option opens a dialog box that blocks the program.

Control case, always a control case

A good share of our breakdowns got solved the same way: by comparing with something that works. A control case is a known-good example you run next to your own. ChaosRobie's terrain project, copied under another name, proved in twenty minutes that BOB had nothing against our files.

You still have to replay the control case exactly. One night, Claude reran it without one flag from the original command: the game quit in nine seconds without doing anything, and six tests were lost hunting a regression that didn't exist. Another time, it announced progress to me for twenty minutes by comparing two measurements taken on two different campaigns.

The control caseThe controla known-good case: forus, ChaosRobie'sproject, under anothernameOur casewhat crashes, or doesnothing: our map, ourfilesThe same command, copied from the logflags included, same pack, same moment: one difference only, thecase being tested.controloursThe cause is in our case: search our files, andonly there.The cause is elsewhere: the tool, the command,the machine. For us, in twenty minutes: our fileswere cleared.Three rules, dearly paid for1Rerun the control case on every test, not just at thestart.forgotten: about ten tests invalidated2Copy the command from the log, flags included.one flag left out: the game quits in 9 s, six tests lost3One difference only between two measurements: samecampaign, same pack.two campaigns compared: a false breakthrough, announced fortwenty minutes
A control case is a known-good case, replayed identically next to ours: if it passes and ours fails, the cause is on our side; if it fails too, it lies elsewhere. The three rules at the bottom come from the error log (entries 30, 39 and 35).

The trick that makes the second rule easy: our test logs have a “What to redo to replay this state” section, with the exact command, ready to copy. Before replaying a control case, you reread it; you never retype it from memory.

Several sessions in parallel

Claude worked in several sessions at once: several conversations open on the same folder, each with its own area, and the only one writing there. One session built the pack, the startpos and the terrain, and kept the documentation; another handled game data, scripts and text.

Others were opened later: map rendering, the expansion to all of Bretonnia, the 9.0 update and the Red Duke, then the illustrations.

Four sessions, one projectShared resourcesThe Assembly Kit: at least 5 minutes' notice; a scriptcomputes the time.14:00announce: "at 14:05"at least 5 min14:05write, not beforeThe game, Terry, the startpos: warn the other sessions.The pack: never rebuilt during an in-game test.Shared documents: one writer; the others send it theirmistakes.A new file: first check that its name is free.Pack and startposthe pack, the startpos,the tests; the shareddocumentsData and scriptsthe tables, thescripts, the textsMap renderingrivers, lakes, bridges,mountains, texturesExpansionthe map to come, thissiteTidy away, never delete: only the human deletes.
Each session has its own domain and is the only one writing there; what they share (the kit, the game, the pack, the shared documents) follows written rules, which each one rereads at the start of its session.

These rules were born from slip-ups. On day one, two sessions worked on the same files: half an hour of reconciliation. Later, one session overwrote another's script, restored byte for byte. And one session wrote to the kit before its notice ran out, four times in the same day.

For that last one, the answer was a script again: the time announced to the other sessions and the end of the timer are now the same value, computed once, instead of an “about five minutes” set by hand.

The human at the screen

The weak point wasn't the AI alone: it was the AI and me on the same computer. One night, Claude was driving the screen to place Terry's camera while I was working. Afterwards, Windows thought the AltGr key was held down, and I couldn't type any more.

Since then, Claude asks before taking over the screen and checks the keyboard state afterwards. The other way round, I clicked in test games that looked frozen, and we took it for crashes. Now it warns me before every game launch, and reads my clicks in the game's log before drawing a conclusion.

No window firstThe AI on screenfirst, ask what the screen would teach that thedisk doesn't; nobody touches the PC;afterwards, check the keyboard.only with your consentYou: by handTerry: painting, placing props; the judgementin game.You: one clickBOB: Start, or Process with BOBin Terry; CAIME: import a layer(Tools → Import → Layer data).The AI alone, no windowfiles, XML tables, rules.bob, .hex_layer layerswritten by script; RPFMthrough MCP; CAIME: process and validate.preferredslower, riskier
The higher you go, the slower and riskier: what can be done without a window is not done on screen.

I'm the judge

My role as the judge, on the other hand, proved irreplaceable. Claude's audits saw 45 floating objects when I saw them everywhere, and its numbers looked fine when I no longer recognised the map.

Warhammer I: the winter glade, dark dense forest, turquoise snow, tall pale crystalsWarhammer I
Warhammer III, our map on 24 September: pale grey-lavender ground, sparse frosted trees, lavender crystalsWarhammer III, 24.09
The winter glade, filmed in game on 24 September around 3 am: dark forest and turquoise snow in Warhammer I, pale ground and sparse trees on ours. No numbers report said so; the side-by-side comparison did.

An algorithm only measures what it thinks it's doing; my eyes, next to the Warhammer I screenshots, measured what the player will see. The rule is written down now: what shows in game gets checked in game, and the AI reports the measured result, never its algorithm's own report.

What worked

  • The debugger. A crash dump read in a debugger tells you which data was missing. That's what unlocked the startpos, the loading and the end of turn.
  • CA's examples first. Before inventing a convention, look at how CA does it on a map that works. Our port settlements were fixed by exporting one of CA's maps.
  • Side-by-side screenshots. Warhammer I on one side, Warhammer III on the other: that's the reference, not the numbers.
  • Timestamped backups. A copy before every write to the kit's tables. One breakdown on the first evening was only solved because we could compare with the last state that worked.
  • Automated games. Games played without me, one lord picked and the AI running the other factions: that's how a visual effect crash was caught for three other lords, before any player ran into it.
  • A snapshot before every game update. Before 9.0, we recorded the state of the game so we can compare afterwards.

An automated game starts from one line and returns a report you read like a match sheet. Here's the one from a ten-turn game with Morghur, on 25 September (the keys are in French: seigneur = lord, tours = turns, issue = outcome, erreurs = errors, blocages = hangs):

# the command (on one line):
python essai_tours_auto.py
    --seigneur morghur --tours 10

# the bilan.json it returns (excerpt):
{
 "seigneur": "morghur",
 "tours_demandes": 10,
 "all_players_ai": true,
 "issue": "fin",
 "dernier_tour": 10,
 "compteurs": {
  "erreurs": 0,
  "blocages": 0,
  "chargements_rates": 0,
  "executions_ratees": 0
 }
}

Zero errors, zero hangs, the game ran to the end. One limit worth knowing: in that mode, the player's faction doesn't play. These games prove the campaign holds; the lords' mechanics get checked by hand.

Understanding it, not just running it

A question I got asked this week while showing the mod around: do you end up with code nobody understands anymore? Fair question. My answer: if I can't sum up what a script does in one sentence, I don't accept it, even if it runs.

For us that means a few simple habits: clear script and variable names, in French like the rest of the project; and at the top of every file, what it does and why, with the number of the error that gave birth to it. For instance, the startpos check (the campaign's starting file), translated:

"""
verifier_compteur_startpos.py - does the
startpos carry a new game's script values?

Why (23.09.2026, error 112):
cm:is_new_game() means "__save_counter == 1".
The counter is only written if the
campaign's scripts run while the startpos
is generated. A startpos older than the
scripts leaves it at 0: every new game
passes for a saved one (no intro, no
missions).
"""

When a script comes back to me across sessions, I reread that header, even without mastering all the code: it's my memory of the project too, not just the AI's.

The other worry people raise a lot: am I not aiming too big? The Season of Revelation is finished first at the scale it had in Warhammer I, before even thinking about all of Bretonnia; and on the Workshop, two separate versions are planned, the original map and an expanded one. The smallest project that stands on its own and actually ships always beats a big one that stays in a drawer.

My advice

  • Write your decisions down, marked “not to reopen” once they're settled.
  • Keep an error log from day one: what, symptom, cause, rule.
  • Code the rules that can be coded; reread the others every session.
  • Demand a control case and an exact command before any conclusion.
  • Judge by eye, in game, against a reference, not on a numbers report.
  • Split the work into areas, with a single writer per area.
  • Don't share the screen without warning, in either direction.
  • Keep everything: backups before writing, tidy archives, no hasty deletion.
  • Never trust a result you can't sum up in one sentence: have the script explained to you before accepting it.
  • Aim small first: ship the smallest version that stands on its own before thinking about expanding it.

Monday morning: where to start

If you're starting from scratch, here's the full sequence, from installing the agent to the first task judged in game. Don't skip the seventh move: a brief that changes nothing. Judge the answer, and above all where the agent says each piece of information comes from, before you let it write anything.

First stepsMake a dated copy of the mod folderor put it under git: the agent writes to your files, thecopy lets you go back.1Install the agentClaude Code or Codex, from their official page; both alsocome as a desktop app.2Open it in the mod folderin a terminal; for Codex, codex instead of claude; in thedesktop app, open that same folder.cd my_modclaude3Write the entry pointat the root: your decisions, your rules, what to read; forClaude Code, a CLAUDE.md; for Codex, an AGENTS.md; inClaude Code, /init drafts a first version to fix.CLAUDE.mdAGENTS.md4Keep the permissionsby default, the agent asks before each command and eachwrite: don't tick "don't ask again" at first; /permissionslists the rules.The entry point steers the agent, it locks nothing: a firmban goes into its permissions.5Plug in RPFMstart rpfm_server.exe; add it to the agent from anotherterminal; check with /mcp (step by step in the workshop'sAI guide).6First brief, read-only"Describe my pack without changing anything, and say whereeach piece of information comes from."Judge the answer before any writing.7A real small task"Change this unit's cost, show me the row before and after,and tell me how to check it in game."Judge it in game: if it's right, you've just won backyour first evening.8
Eight moves, from zero to the first task judged in game: a copy to go back to, the agent in the mod folder, an entry point, permissions, RPFM plugged in, a brief that changes nothing, then a real small task.

The full story of those four days is here, and the nine biggest obstacles there. And if you want to see this method applied to what comes next, the map of all Bretonnia is being drawn right now, with your ideas.

Suggest an ideaSee the map