l2_maps.dat — campaign map records#
Summary#
l2_maps.dat is an install asset that stores fixed-size campaign map
records. Each record describes one strategic-map variant: a 64×64 province
cell layout (six byte planes plus derived layer/slot tables) and a separate
0x81×0x41 visible tile-index grid. The client reads records with plain
seek + read; there is no decompression step in the map loaders.
Record count is not stored in the file. The client probes map01.pl8
variants (lotr2_map_load_pl8_tiles, up to 40) and uses the same index for
both PL8 tile sheets and l2_maps.dat records.
File layout#
| Property | Value | Evidence |
|---|---|---|
| Record size | 0x80c1 (32961 bytes) |
lotr2_asset_read_file(..., 0x80c1, ...) |
| Record offset | record_index × 0x80c1 |
Same call sites |
| Header / magic | None observed | Loaders seek directly to index × size |
| Record count | file_size / 0x80c1 |
Implied; validated only by PL8 probe at runtime |
Total file size must be an exact multiple of 0x80c1.
Record layout#
Within one record, cell (x, y) on the 64×64 grid uses linear index
i = y × 0x40 + x (both coordinates 0..63).
| Record offset | Size | Name | Runtime destination |
|---|---|---|---|
0x0000 + i |
1 | flags |
Province cell +1 (DAT_00552181) |
0x1000 + i |
1 | layer_a |
Province cell +2 |
0x2000 + i |
1 | layer_b |
Province cell +3 |
0x3000 + i |
1 | layer_c |
Province cell +4 |
0x4000 + i |
1 | layer_index |
Not stored in cell; drives layer/slot side tables |
0x5000 + i |
1 | province_id |
Province cell +7 |
0x6000 + j |
1 | visible_tile |
Visible grid dword 0xfff0000 + byte |
Check: 6 × 0x1000 + 0x20c1 = 0x80c1.
The visible grid has 0x81 rows × 0x41 columns (j runs 0..0x20c0).
Struct reference: ../../structs/lotr2_maps_dat_record.md.
Decode rules#
Province cell load (lotr2_map_load_from_dat)#
For each of the 4096 cells:
- Copy planes
flags,layer_a,layer_b,layer_c, andprovince_idinto the live 8-byte province cell table (lotr2_map_province_cellstride). - Track
max_province_idas the maximumprovince_idseen whereprovince_id < 0x11(17). - When
layer_index != 0: - If
flags & 0x40: appendprovince_idto layer listlayer_index(lotr2_map_append_province_to_layer). - Else if
flags & 0x80: write province slot pair (lotr2_map_set_province_slot_pair).
Before parsing, the loader clears six 16-entry layer lists and six slot pairs.
Visible grid seed (lotr2_map_seed_grid_from_dat)#
For each visible-grid cell, set:
lotr2_map_tile_index_grid[row, col] = 0xfff0000 | visible_tile_byte
This runs independently of the province-cell parse and uses the same record read at the same index.
Flag bits (plane 0 / cell +1)#
| Bit | Use in code | Confidence |
|---|---|---|
0x20 |
Starting-unit placement cell (lotr2_map_place_starting_units) |
likely |
0x40 |
Layer-list membership when layer_index != 0 |
confirmed |
0x80 |
Province slot pair when layer_index != 0 |
confirmed |
0x10 |
Special-tile collection (lotr2_map_collect_special_tiles) |
likely |
& 0xfd == 0 |
Passability probe (lotr2_map_rect_has_passable_cell) |
likely |
Other bits are used elsewhere but are not written directly from l2_maps.dat
during load.
Load pipeline#
New campaign (lotr2_map_load_new_game) uses map index DAT_0055a364:
- Clear province layers.
lotr2_map_load_from_dat(index)— province cells + layer/slot tables.- Clear highlights; paint visible grid; paint sea tiles.
lotr2_map_load_pl8_province_tiles(index)— companionmap01.pl8art.- Build neighbors, province setup, starting units, army slots, layer chains.
Wizard and battle UI reload dat + grid when the map index changes.
Data model#
../../structs/lotr2_maps_dat_record.md— on-disk record../../structs/lotr2_game_state.md— live province cell fields
Evidence#
| Symbol | Address | Role |
|---|---|---|
lotr2_map_load_from_dat |
0x00477610 | Parse 64×64 planes into province table |
lotr2_map_seed_grid_from_dat |
0x0047781d | Seed 0x81×0x41 tile index grid from +0x6000 |
lotr2_map_load_new_game |
0x00477580 | New-game orchestrator |
lotr2_asset_read_file |
0x004ca01c | Raw seek/read I/O |
lotr2_map_load_pl8_tiles |
0x0047a007 | Count map variants via map01.pl8 probe |
lotr2_map_append_province_to_layer |
0x00433984 | Layer list side effect |
lotr2_map_set_province_slot_pair |
0x004b1e40 | Slot-pair side effect |
Evidence layer: ../../architecture/map/MODULE.md.
Confidence#
likely — record size, plane offsets, and copy rules are direct from
decompilation. Upgrade to confirmed by dumping a retail l2_maps.dat and
matching decoder output to in-game province layout for several indices.
Interop notes#
- Install asset only — not appended to saves (contrast
castles.datsiege slice in save files). - Multiplayer — wizard province-pack sync reloads dat at the negotiated map index (see networking wizard opcodes).
- Skirmish — single-battle mode uses embedded scenario tables, not
l2_maps.dat(../../skirmish/army-and-scenarios.md).
Tooling#
Decode an external copy of the asset:
python3 tools/dump_l2_maps_dat.py /path/to/l2_maps.dat --record 0
python3 tools/dump_l2_maps_dat.py /path/to/l2_maps.dat --json
Open questions#
- Exact semantic names for
layer_a..layer_cplanes beyond copy targets - Meaning of every flag bit in plane 0 not yet tied to a named behavior
- Whether retail installs always ship exactly as many records as PL8 variants
- Full runtime use of province cell bytes
+0,+5,+6not written by dat load