Tooling for Project Zomboid Build 42 mod development: parse the game's own script data, index it, scaffold a mod, and validate one against reality before you ship it.
The premise is that PZ fails silently on names that do not exist. A loot-list
key with a typo never fires. A craftRecipe output that references a
nonexistent item can abort world load with nothing useful in the log. Most mod
bugs are not logic bugs — they are a string that does not match anything in the
game, and nothing tells you.
pzkit builds an index from your installed copy of the game and checks your mod's references against it.
git clone https://github.com/knoxy4/pzkit
cd pzkit
pip install -e .Python 3.11+. The core has no dependencies — it is stdlib only. Optional
extras: pip install -e ".[mcp]" for the MCP server, ".[semantic]" for the
LanceDB layer, ".[dev]" for ruff and pytest.
Point it at your game's script directory and build the index:
export PZ_SCRIPTS_DIR="/path/to/ProjectZomboid/media/scripts"
pzkit index buildThen ask it things:
pzkit index query --consumes Base.Nails # recipes that consume nails
pzkit index query --produces Base.Plank # recipes that make planks
pzkit index query --sprite carpentry_01_16 # what uses this spriteAnd validate a mod:
pzkit validate /path/to/YourModOr check a single name before you commit it:
python3 tools/pz-bible/build_refs.py --game "$PZ_DIR" --out tools/pz-bible/refs
python3 tools/pz-bible/pz_verify.py item Base.Axe # OK
python3 tools/pz-bible/pz_verify.py any OnZombieSpawn # NOT FOUND, with suggestionsValidation covers mod layout, script DSL syntax, references against the vanilla index, translation files, and Lua (via luacheck if present). Stages that cannot run skip loudly — a missing index or a missing Lua toolchain reports as SKIPPED, never as a silent pass. Green means checked.
| Path | What |
|---|---|
pzkit/vanilla_index.py |
SQLite index over parsed B42 scripts. Walks media/scripts/**/*.txt, normalizes into tables, adds FTS. |
pzkit/scripts_parser.py |
Parser for PZ's script DSL — items, recipes, blocks, nested props. |
pzkit/validator.py |
Layout, syntax, references, translations and Lua checks. |
pzkit/refcheck.py |
Type-aware reference resolution, including bare-name keyed fields (GrantedRecipes, XPBoosts, trait lists) that naive checking misses. Surfaces index staleness so you never validate against an out-of-date snapshot. |
pzkit/scaffold.py |
New-mod scaffolding in the B42 layout. |
pzkit/testrig.py |
Boot-test harness — does the mod actually load? |
pzkit/semantic_index.py |
Optional fuzzy search, embedding via a local Ollama. Never a metered API. |
pzkit/mcp_server.py |
MCP server, so an AI assistant can query the index instead of guessing names. |
tools/probe/ |
Read-only probes over vanilla data: the global API surface, level-0 craft recipes, timed actions, the foraging schema, radio data, icon and model inventories. |
tools/art/ |
Icon pipeline. PZ inventory icons are 32x32 and most art dies at that size; these build at 64, downscale, and show you the result at true size on a dark panel before you believe it. |
tools/pz-bible/ |
Ground-truth name checking. build_refs.py extracts every string-keyed name from your install (~110k from vanilla, ~25s); pz_verify.py checks candidates against them. See its README. |
tools/publish/ |
Steam Workshop publishing driven by one config file: metadata, staging from a git ref, first publish with id write-back, and pushes. See its README. |
tools/ws_deploy.ps1 |
Standalone one-command Workshop publish, for a single mod folder with no config. Never stores or reads a password — SteamCMD caches credentials after one interactive login. |
tools/hallucination_sweep.py |
Standalone reference sweep for script references to things that do not exist. |
Everything is environment variables; nothing is hardcoded to a machine.
| Variable | Default | What |
|---|---|---|
PZ_SCRIPTS_DIR |
— | Your game's media/scripts directory. Required for indexing. |
PZKIT_DB |
data/vanilla.db |
Where the SQLite index lives. |
PZKIT_LANCE_DIR |
— | LanceDB directory for the semantic layer. |
OLLAMA_URL |
http://127.0.0.1:11434 |
Local embedding endpoint. |
COMFYUI_URL |
http://127.0.0.1:8288 |
Local image generation, for the art helpers. |
PZ_TEXTURES_DIR |
— | Default textures directory for tools/art/ scripts (each also takes a path argument). |
PZKIT_MOD_PREFIX |
(none) | Require every mod id and folder to start with this. Unset by default — a shared namespace is a project convention, not a Build 42 rule. scaffold and validate both honour it. |
PZ_BIBLE_MIN_SIM |
0.62 |
Similarity floor for pz_verify.py suggestions. |
PZ_LUACHECKRC |
.luacheckrc |
Where tools/probe/pzglobals.py writes its generated globals list. |
The boot-test rig assumes a WSL2 install with the dedicated server under
~/pzserver. Override any of it:
| Variable | Default | What |
|---|---|---|
PZRIG_WSL_DISTRO |
Ubuntu |
Distro name, used to build the \\wsl.localhost\... path Windows reaches the rig through. |
PZRIG_WSL_USER |
$USER |
Linux user whose home holds the rig. |
PZRIG_WSL_HOME |
/home/$USER |
Overrides the two above outright. |
PZRIG_SERVER_DIR |
~/pzserver |
The dedicated server install. |
PZRIG_CACHEDIR |
~/pzjarvis-rig |
Isolated -cachedir so the rig never touches a live server. |
PZRIG_CTL |
~/pzjarvis-ctl |
Control and log files, deliberately outside the cachedir. |
PZRIG_SERVERNAME |
pzjarvis-testrig |
Server name the rig boots under. |
Both the index and the pz-bible name lists are built from your install, on
purpose. This repo ships no extracted game data at all: name lists go stale the
moment the game updates, and a stale list that looks authoritative is worse than
no list — it produces confident wrong answers in both directions. Rebuild after
every game update; refcheck will tell you when the index is older than the
build it is checking against.
The two layers answer different questions. pzkit index parses script data into
a queryable database — what consumes this item, what produces it, what uses this
sprite. tools/pz-bible answers the narrower one that causes most silent
failures: does this name exist at all, across 33 kinds including enums, events,
translation keys and tile sheets that never appear in script files.
- pz-sprite-forge by Leeheejin — models custom PZ tiles in Blender with the projection and lighting measured from the game's own art. Separate MIT project, not affiliated with this one, and worth your time if you make furniture or props.
Issues and pull requests welcome. Two things to know:
- No metered APIs. Everything here is meant to run in CI and on a nightly schedule. A tool that costs money per invocation stops getting run, so it stops being trusted. Local models are fine; hosted inference is not.
- Fail loudly. A check that cannot run reports SKIPPED. Nothing in this codebase is allowed to pass silently when it did not actually verify anything — that is the exact failure mode the whole project exists to fix.
MIT. See LICENSE.
Project Zomboid is a trademark of The Indie Stone. This project is an independent, unofficial modding tool and is not affiliated with or endorsed by The Indie Stone.