Atlas of Bretonnia

Workshop journal · · 9 min read

Building a map expansion, from zero to 100: the Season Expanded build, live

On 25 September I started the Season's natural next step: enlarging its map to all of Bretonnia, without touching the beta. This build happens in public, and this article follows it phase by phase, with our real tools, our real files and our missteps. Enough to understand how a map expansion comes to life, borrow ideas from it, or come and help.

The coast of Lyonesse seen in the working data: sea, green and brown land, and at the bottom right the yellow frame of the Warhammer I map.
The coast of Lyonesse, new land for the expansion, seen in our working data (8 pixels per hex, not the game's rendering; label in French); at the bottom right, the frame of the Warhammer I map.

A living article

Season Expanded is the Season's map enlarged to all of Bretonnia. In the middle, the Warhammer I map, 100% identical. Around it, the expansion's land, drawn from this site's Atlas.

The paper method is told in All of Bretonnia. This is the build: four phases, each with its own check.

Season Expanded: the buildBefore the phasesdone · 25.09, 3:25 – 3:50 pmA copy of the beta's pack, outside the game; the Atlas re-read;the pipeline inventoried; the plan in four phases.Per-map configurationin progress · started 25.09 at 4:45 pmA pipeline that builds two maps: the beta, byte for byte,and Expanded.11 modules wired in; beta map: 0 changes out of 955constants.1Enlarged gridin progress · launched 25.09 at 5:07 pmIn CAIME, 560 × 825 hexes: the Season in the middle, theAtlas around it, off-limits and impassable.Grid laid out; map and campaign declared in the kit at6:08 pm.2The expansion's terrainin progress · started 25.09 at 5:45 pmRelief, ground, forests, rivers, coasts and mountains fromthe Atlas; the Dreaming Wood.Terrain project, natural blend and Dreaming Wood,outside the kit.3The Expanded campaignto comeStartpos, saison_expanded.pack loaded on its own; firstin-game test.4donein progressto come
The build as of 25 September, 6:40 pm. Each phase has its check before the next; the beta is never touched. Times come from the build log.

For each phase: its goal, its steps, its tools and files, its pitfalls, its status. Everything is shown, screenshots and numbers included. Only the pack stays private, like the beta's: it contains Warhammer I files.

Before the phases: a copy and two inventories

On 25 September at 3:25 pm, I wrote to Claude's “Construction” session: make a copy of the pack, call it Expanded, and start drawing the whole expansion. Ten minutes later I added: “I don't want a flat map: the relief, the plains, the forests.”

Four rules frame the whole build:

  • The beta stays untouched. Expanded is a copy, with its own map name, files and pack. Nothing is written into the beta's folders.
  • Warhammer I at 100% in the middle. Around it, the Atlas, the single source: the site and the game show the same map.
  • Land as scenery first: off-limits, impassable, under the fog, like the edges of Immortal Empires. Playable regions come later, one by one.
  • Keys: saison_ for what we create, wh_dlc05_ only for what comes from Warhammer I.

Two research agents read everything first, writing nothing: the Atlas on one side, our terrain pipeline on the other. Good news: the Atlas is already in our hex coordinates, a 560 × 575-hex frame where the Warhammer I map sits at x + 120. Its 32 new regions and 11 seas drop straight onto it.

The expansion's new regions, named, and its seas, in italics, around the yellow frame of the Warhammer I map.
4 pm preview: the towns of the 32 new regions and the names of the 11 seas, over the Atlas relief (labels in French). Inside the yellow frame, on Warhammer I's border, you can already read Château Artois, Uesin, Larret, Sigmarsheim, Grung Zint, Helmgart, La Maisontaal, Karak Azgaraz, Karak Norn.

Hence a surprise: some of the new regions, such as Château Artois, Grung Zint, Helmgart or Karak Norn, already sit inside the Warhammer I frame, on its non-playable scenery border. More on that in phase 2.

The pipeline, though, was written for one map only. A single constant, CARTE, named both the source (Warhammer I's terrain) and the target (the files in the kit). The size, 400 × 440 hexes, was hard-coded in some thirty modules. The four-phase plan came out of that finding.

Phase 1: a pipeline that knows several maps

  • Goal: the same pipeline builds the beta, byte for byte, and the Expanded map.
  • Status: in progress, started 25.09 at 4:45 pm.
  • Tools: Python; carte_config.py, releve_constantes.py.

Before touching a single module, we recorded the values of 952 constants across 30 modules: that's the baseline. Then one module, carte_config.py, took over everything that depends on the map: its key, its campaign, its size, where Warhammer I sits, the work folder, the pack.

An environment variable, SAISON_CARTE, picks the profile. Without it, you get the beta.

One constant, two jobsBEFORECARTE = wh_dlc05_wood_elves_map_1the sourceWH1's terrain, read from thereferencesthe targetthe files written into thekitChanging the target would also have changed the source.AFTER: carte_config.pyCARTE_SOURCE = wh_dlc05_wood_elves_map_1always WH1, never changedCARTE: the profile picked by SAISON_CARTEsaison (default)wh_dlc05_wood_elves_map_1400 × 440 hex(0, 0): offsetsaison_des_revelations.packthe betaexpandedsaison_expanded_map560 × 825 hex(120, 250): offsetsaison_expanded.packthe new mapCheck: 955 constants read from 30 modules, before and after.Beta map: 0 differences. Expanded: 28, all targets; no WH1source.Still to handle: kit files with no map name in them (mountainsdraped on the relief, the ground-variant database, water and snowmaterials). As they stand, Expanded's would overwrite the beta's.
Phase 1. The beta's pipeline read a single constant for both source and target. Now the source stays WH1 and the target follows a profile; the beta's profile keeps every value it had before.

Eleven modules are wired in: the generator, the camera, the ground textures, the lighting, the trees, the rivers, the entities, the mountains… The check compares the constants before and after:

python releve_constantes.py --sortie avant.json
python releve_constantes.py --sortie apres.json --comparer avant.json

Result: 0 differences out of 955 for the beta. For Expanded, 28, all of them targets (map key, kit and workshop folders), no Warhammer I source.

Still to wire in: the water masks, the pack build, the tables and the startpos, the names of the water and snow materials. Then comes the placement step, which will set all of Warhammer I into the enlarged world. The phase's final check will be tougher: the beta's pipeline must produce the same compiled terrain, byte for byte.

Phase 2: the enlarged grid in CAIME

  • Goal: the new map's map.hex. The Season in the middle, the expansion around it, off-limits and impassable; only the Season is playable.
  • Status: in progress; grid laid out at 5:30 pm, map and campaign declared in the kit at 6:08 pm.
  • Tools: CAIME and command-line commands added to our copy; grille_expanded.py, rendu_grille.py.

A bigger grid than planned

The grid was meant to match the Atlas frame: 560 × 575 hexes, the Season at x + 120. At 4:15 pm I settled a question: the Dreaming Wood, our reflection of Athel Loren, goes in the south, mirroring the forest, behind an impassable veil about 26 rows deep.

That reflection is our own addition. According to the Wood Elves army book (8th edition), as cited by the Warhammer Fantasy wiki, it runs along the whole forest: hence its place, reserved from the grid onward. So the game grid is 560 × 825 hexes: the Atlas, plus 250 rows in the south for the Dreaming Wood. The Season sits at (x + 120, y + 250) in it.

The Season Expanded grid(120, 250)WH1 MAP400 × 440 hexes, as isplayable: the SeasonTHE ATLAS: THE EXPANSIONoff-limits, impassable (scenery)DREAMING WOODAthel Loren's mirrored reflectionspace reserved, empty for now↑ veil ≈ 26 rows+120400+40560 hexes west to east+135440+250row 0 in the southThe WH1 map: its playable part keeps its layersThe Atlas, everywhere else north of the bandThe southern band: the future Dreaming Wood560 × 825 hexes; the WH1 map at (x + 120, y + 250)
The grid to scale. The Atlas frame (560 × 575 hexes) keeps its coordinates: the WH1 map sits at x + 120 in it. The 250 rows added in the south push it up by as much: in the grid, it starts at (120, 250). Only its playable part keeps the Season's layers; everything else is off-limits and impassable.

Filling the grid, hex by hex

From the command line, CAIME only knows process and validate. To script the whole thing, I used command-line commands added to our copy of CAIME, not yet offered to its authors: config, create, info, sync-names, import-layer and export-layer. If you work with an AI, that's what lets it build a map end to end, without a single click.

  1. Create the empty map at the right size: create --name saison_expanded_map --width 560 --height 825, then sync-names, which writes the database names into it: 15 land grounds, 6 sea grounds, 42 climates, 12 attritions.
  2. Copy the Season over: 8 layers from the beta (ground, climates, attritions, passable, rivers, roads, beaches, bridges), renumbered by name to the new map's lists, set at (x + 120, y + 250).
  3. Lay down the Atlas everywhere outside the Season's playable part: its ground types and its rivers.
  4. Close everything outside the Season: impassable. The southern band stays empty.
  5. Import and check: import-layer, then export-layer --format png, and a natural-colour render.

The grid's actual commands, at 5:07 pm (the script passes all 8 layers in one go):

CAIME.exe create --name saison_expanded_map --width 560 --height 825
CAIME.exe sync-names --map saison_expanded_map\map.hex
CAIME.exe import-layer --map saison_expanded_map\map.hex
    --layer GroundTypes --file layer_groundtypes.hex_layer
    --layer Rivers --file layer_rivers.hex_layer  …
CAIME.exe export-layer --map saison_expanded_map\map.hex --out export-png
    --layer GroundTypes --layer Impassable --layer Rivers --format png

Why does the Atlas win even inside the Warhammer I frame? We measured it. On the Season's playable part, the Atlas matches our layers at 98.5%. On the scenery border, only at 61%: it draws 25,680 hexes of new regions there, the ones behind the 4 pm surprise. Hence the rule: Warhammer I as is wherever you play, the Atlas everywhere else.

The Season Expanded grid rendered from its CAIME layers: the playable Season in light green in the middle, the off-limits land darkened around it, the sea in blue.
The grid rendered from the layers imported into CAIME (label in French). Light, the playable Season, carried over as is; darkened, everything off-limits and impassable: Warhammer I's border and the Atlas expansion. The southern band, still empty in the CAIME grid, is cropped out here: the diagram above shows it to scale.

The map enters the kit

At 6:08 pm, Expanded came to exist in the kit's database. A script writes its specification (spec_expanded.py → map_spec_expanded.json), then declare_map.py registers it, after a backup: the saison_expanded_map map (560 × 825), the saison_expanded campaign (“The Season of Revelation: Expanded”), its playable area (the Season's, shifted), its roads, and the 61 Warhammer I regions linked to the map.

Those regions keep their wh_dlc05_ keys: the Season's scripts still work on the new map. Every new key was checked to be free before writing.

Still to come, in phase 2b: the new regions and seas (saison_<key>, saison_sea_<key>), then the settlement slots and trade routes (the tables to declare). CAIME screenshots to come.

Phase 3: the expansion's terrain

  • Goal: land that isn't flat. Relief, ground, forests, rivers, coasts and mountains from the Atlas, plus the Dreaming Wood.
  • Status: in progress since 5:45 pm, in the build's sandbox (nothing in the kit or the game).
  • Tools: relief_monde.py (previews), projet_expanded.py (the terrain project), Terry.

A first prototype, at 4:05 pm, started from the Atlas's pseudo-altitude. It topped out far too low: 3 height units, where Warhammer I climbs to 13.8. The session that draws the Atlas gave us the lesson: its alt value is not a height but a drawing class, to be converted with a table calibrated on Warhammer I.

At 5:35 pm, a first assembly of the world, as data: in the middle, the real height of the Season's Terry project; around it, the calibrated Atlas (grassland around 1.6, hills from 2.4 to 6.9, mountains from 3.2 to 10.5). The Dreaming Wood joined it at 5:45 pm.

The terrain project

At 5:45 pm the real terrain began. projet_expanded.py builds Expanded's Terry project from the Season's, read and never written: 10 enlarged rasters (relief, sea floor, colours, corruption, snow, trees…), the tile map, 51,523 shifted object positions. The whole 400 × 440 frame stays Warhammer I's: its mountains are draped on its relief.

At 6 pm, I asked for the Warhammer I map to “flow out naturally, with no border”. Its relief is extended beyond the frame by a smooth fill, then blended into the Atlas's over 12 hexes. The expansion's coasts are smoothed, and the Atlas relief is eroded with fine gullies, like Warhammer I's.

The whole world as data: shaded relief in ground colours, the Season's yellow frame, the veil's mauve band and the violet reflection of Athel Loren on a dark ground.The data, 5:45 pm
Expanded's terrain project in working colours: green shaded relief, the Season's frame, pink patches of corruption, then the veil and the reflection on a dark violet sea.The terrain project, 6:40 pm
The data, 5:45 pm: ground per hex and a shaded relief (labels in French, with the working name “Bois des Rêves”). The terrain project, 6:40 pm: the Terry project's rasters, in working colours; Warhammer I's relief carries on beyond the frame with no sharp border, and pink marks the corruption mask. Neither is a game screenshot yet.

The Dreaming Wood

From 6:30 to 6:40 pm, the Dreaming Wood entered the terrain project: Athel Loren mirrored from Warhammer I, relief, water, ground, trees and winter snow included. A lilac tint, a corrupted magenta edge, a veil of mist; around it, a dark violet sea of aether where the reflection floats like an island. The Slaanesh colours are the project's artistic choice.

The south of the terrain project: at the top, the Season's southern edge; a lilac band, the veil; below, the reflection of Athel Loren, grey-green tinted pink and edged in magenta, on a dark violet sea.
The Dreaming Wood in the terrain project, seen from above (6:40 pm, working colours). At the top, the Season's southern mountains; then the lilac band of the veil of mist; below, the reflection of Athel Loren, edged in magenta, on the sea of aether.

Phase 4: the Expanded campaign

  • Goal: a playable campaign on the new map, alongside the beta's.
  • Status: to come.
  • Tools: RPFM, startpos generation.

The map key, the campaign key and the playable area are already declared (phase 2, 6:08 pm). A campaign knows only one map, through the map_name column of the campaigns table: Expanded's points to its own.

Follow along, and lend a hand with the map

The site's Atlas is the single source: what changes on the map will change in the game. A name that rings false, a misplaced border, a forgotten ruin from the lore? Suggest them on the map, right where they belong.

Later, no rush: a slice of the Reikland at the border, with Helmgart facing Montfort and the Gisoreux Gap; Marienburg, once I've seen it in the game.

This article grows with the build: each phase brings its screenshots, numbers and pitfalls. The tools have their guides: CAIME, Terry, RPFM and the other tools.

Suggest an ideaSee the map