01 Map Hierarchy

Project Zomboid organizes its world in three nested levels. Everything scales up from a single tile:

1 × 1 Tile Smallest unit
× 8
8 × 8 tiles Chunk One data block
× 32
256 × 256 tiles Cell Top-level division

Save files use cell coordinates for directories and chunk coordinates for individual file names.

02 B41 vs B42

Build 42 changed the grid constants. Scripts written for B41 will calculate wrong chunk ranges on B42 saves.

B41
Cell size
300 × 300 tiles
Chunk size
10 × 10 tiles
Chunks / cell
30 × 30
Round numbers, arbitrary
vs
B42
Cell size
256 × 256 tiles
Chunk size
8 × 8 tiles
Chunks / cell
32 × 32
Powers of two

03 Coordinate Calculation

To find which chunk files belong to a cell, multiply the cell coordinate by chunks-per-cell:

$ chunk_start = cell_coord × 32
$ chunk_end   = chunk_start + 31

Worked example — Cell (6, 57)

X  (cell 6)
6 × 32 = 192
192 + 31 = 223
chunks 192 – 223
Y  (cell 57)
57 × 32 = 1824
1824 + 31 = 1855
chunks 1824 – 1855

Any save file with chunk coordinates in those ranges belongs to cell (6, 57).

04 Save File Structure

The cleanup script needs to know which directories use cell vs chunk coordinates:

DirectoryCoordsExample file
map_* chunk map_192_1824.bin
chunkdata_* chunk chunkdata_192_1824.bin
zpop_* cell zpop_6_57.bin

For chunk directories, the script expands the cell into the full 32×32 range and deletes every match. For cell directories it deletes by cell coordinate directly.

05 map_meta.bin Format

The map_meta.bin file sits at the save root and stores server-wide metadata: room exploration state, building alarms, safehouses, factions, and more. It’s a flat binary blob read sequentially — no headers, no offsets, just one section after another.

String encoding

All strings use a simple format: a 2-byte signed length followed by that many bytes of standard UTF-8. A length of 0 means an empty string.

  ┌────────────┬───────────────────────┐
 int16 len   byte[] utf8 (len)     
  └────────────┴───────────────────────┘

File layout

Each section is read in order. Counts precede repeated entries, so the parser always knows how many items to expect.

  1. 1 Header 4 magic bytes, int32 worldVersion, grid bounds x1 y1 x2 y2
  2. 2 Rooms per cell int64 metaID, int16 flags — explored, lights, spawn, roof
  3. 3 Buildings per cell int64 metaID, alarm state, key, visited, loot timer, decay*
  4. 4 Safehouses Rect, owner, members, title, location — see detail below
  5. 5 Non-PvP Zones Rect x y x2 y2, size, title
  6. 6 Factions Name, owner, optional tag with RGB color, member list
  7. 7 Designation Zones float64 id, position, type, name, last seen hour
  8. 8 Stash System Possible stashes, buildings to do, already-read maps
  9. 9 Unique RDS Spawned Item names already spawned by the random distribution system

* Some fields are version-gated: alarmDecay requires worldVersion ≥ 201, hitPoints ≥ 216, created and location ≥ 223.

Safehouse detail

Each safehouse entry defines a rectangle in world tile coordinates with ownership and member lists:

int32  count
  ┌─ int32  x, y, w, h          ← bounding rect (tile coords)
  string owner                ← who claimed it
  int32  hitPoints            (v≥216)
  int32  nPlayers
    └─ string playerName[]    ← authorized members
  int64  lastVisited          ← epoch ms
  string title                ← display name
  int64  datetimeCreated      (v≥223)
  string location             (v≥223) ← town name
  int32  nRespawn
  └── └─ string respawnName[]   ← players respawning here

Coordinates are world tiles, not cells. To convert: cell = floor(tile / 256). Width and height define how many tiles the safehouse covers.

How we know this — Decompiled from projectzomboid.jar using CFR. Key classes: IsoMetaGrid.load(), SafeHouse.load(), GameWindow$StringUTF. The file uses Java’s ByteBuffer — big-endian, no alignment padding.

06 CLI Script

Prefer the command line? pzcc.sh runs the cleanup directly on your server — no browser needed.

Download pzcc.sh

Usage

$ chmod +x pzcc.sh
$ ./pzcc.sh --path /server/Zomboid/Saves/Sandbox/myworld --purge 6,57 8,60

Options

FlagDescription
--path <dir>Path to the PZ server save directory
--purge <cx,cy>Cell coordinate to purge (repeatable)
--dry-runShow what would be deleted without deleting anything
-h, --helpShow help

Dry run first

Always preview before deleting. The --dry-run flag lists every file that would be removed:

$ ./pzcc.sh --path /server/saves --purge 6,57 --dry-run
=== DRY RUN — no files will be deleted ===
Base path: /server/saves
Cells to purge: 6,57
--- Cell 6,57 ---
[dry-run] rm /server/saves/chunkdata/chunkdata_6_57.bin
[dry-run] rm /server/saves/zpop/zpop_6_57.bin
[dry-run] rm /server/saves/map/192/1824.bin
...
Would delete 7 files. Run without --dry-run to execute.

With the web tool

Select cells in the map tool, then click Copy cell coords to get a ready-to-paste command.