1. Directory structure
The pack mirrors the game's resource tree exactly. To override res://assets/sprites/insects/fly.png, place your file at assets/sprites/insects/fly.png.
themes/<your-theme-id>/
manifest.json required
preview.png recommended, list cover
assets/
sprites/
ant1.png ant2.png ant3.png 4 caste textures
goldant.png gold mutation
p.png carried crumb
ant_head.png visiting friend
nest.png nest_path.png nest skin
insects/<id>.png + .tpsheet 9 insects
foods/<id>.png + .tpsheet food stages
ui/icons/*.png card & resource icons
data/*.csv data tables, optional
translations/messages.csv text overrides, optional
Scope
Only assets/, data/, and translations/ are read. A pack cannot override scripts or scenes. The override map is built by walking the disk, so path traversal is not possible.
2. manifest.json
{
"id": "wolf_pack",
"name": "Wolf Pack",
"name_i18n": { "zh": "狼群", "ja": "狼の群れ" },
"desc": "Replaces the ant colony with a wolf pack.",
"desc_i18n": { "zh": "把蚁群换成狼群。" },
"version": "1.0.0",
"author": "your name",
"theme_type": "beast",
"requires": { "game_version": "0.8" },
"tags": ["wolf", "beast"],
"contents": ["data", "translations", "insects"]
}
idmust match the folder name exactly, or the theme is skipped.nameis required and must be plain text, not a translation key. The theme list shows every installed pack, but an inactive pack's translations aren't loaded, so a key would resolve to nothing there. Usename_i18n/desc_i18n, falling backzh_TW→zh→name.requires.game_versionis actually enforced: a pack declaring a newer version than the running game will not load (still listed, but selecting it shows a notice). Omit it to mean 'compatible'.theme_type,tags, andcontentsare metadata only for future Workshop filtering; the current version does not read them.
3. Data tables
Data tables are replaced whole, not merged row by row. Copy the game's entire data/ file and edit it; missing rows or columns get rejected by validation.
All-or-nothing
Every CSV runs the same validation as the base tables (types, ranges, cross-table references). A single error rejects the whole pack and reverts to the default theme, listing the first few errors in game. There is no per-table fallback: mixing a pack's insect table with the default food table would point cross-references at ids that don't exist, which is worse than refusing to load.
4. What pauses achievement sync
The check is field-level: only edits to gameplay-affecting fields pause Steam achievement and stat uploads. Local achievements still unlock and stay visible, nothing already unlocked is rolled back, and switching back to the default theme resumes syncing.
Cosmetic allowlist: safe to edit
| Table | Columns you may change |
|---|---|
skins.csv | entire table |
ant_castes.csv | name texture display_width* |
insects.csv | name sheet icon display_width* |
foods.csv | name tint_r tint_g tint_b icon |
upgrades.csv | name desc icon |
achievements.csv | name desc subject icon icon_locked |
rare_elements.csv | name icon source order icon_texture |
resources.csv | name icon order icon_texture |
upgrade_categories.csv | name order page_type icon |
game_params.csv | all shadow_* and lift_* keys |
* display_width may vary between 1/3× and 3× the base value — a different species is a different size. Beyond that range it counts as gameplay-affecting.
Anything else pauses syncing: adding or removing rows, changing ids or headers, and values like hp, speed, costs, or target. The official Wolf Pack only touches allowlisted columns, which is why it stays cosmetic and keeps achievements flowing — copy its approach.
Note that insects.texture (the static image) is not allowlisted: an insect's swarm radius comes from scanning that image's opaque edge, so replacing it changes combat geometry. Replace the sheet atlas instead.
5. Resources & upgrade pages
Beyond numbers and art, two structural things can change — this is what separates a new setting from a reskin.
Custom resource axes
Starch/sugar/protein are not hard-coded: replace data/resources.csv wholesale to get meat/pelt/bone instead. Columns are id,name,icon,order,initial,icon_texture. id is a save key, so use only [a-z0-9_]; initial is the starting stock (affects difficulty, so it pauses achievement sync). You are not limited to three resources.
On a cross-screen expedition to a friend running a different pack, any resource in their reward that your pack doesn't know is ignored — nothing crashes, but you don't receive it either.
Custom upgrade pages
The number, names, and order of upgrade pages come from data/upgrade_categories.csv, with columns id,name,order,page_type,icon:
upgrade_categories.csv
id,name,order,page_type,icon
den,ui.tab.nest_upgrade,0,generic,res://assets/ui/icons/yichao_1.png
wolf,ui.tab.ant_upgrade,1,generic,res://assets/ui/icons/mayi_1.png
food,ui.tab.food_upgrade,2,food,res://assets/ui/icons/shiwu_1.png
beast,ui.tab.beast_upgrade,3,generic,res://assets/ui/icons/mayi_2.png
- The
categorycolumn inupgrades.csvmust reference anidfrom this table; a typo is caught by validation instead of silently vanishing. page_type:genericis a normal upgrade page;foodmarks the food page (with feeding selection and the unlock area) and there can be only one.- A category absent from this table is removed; a category with no upgrades under it also doesn't render.
- A new category must ship its own translation (like
ui.tab.beast_upgradeabove). The Achievements, Workshop, and Settings tabs are program shell and aren't affected by this table.
Effect columns for new upgrades
A new upgrade must fill all six effect_* columns in upgrades.csv, or its card shows a blank effect line (an empty effect_key is a validation error):
| Column | Meaning |
|---|---|
effect_key | Translation key for the effect text, containing a {0} placeholder |
effect_unit | absolute / percent (decimal to %) / multiplier / minutes (seconds to minutes) / unlock (no number, gate only) |
effect_base | Value at level 0. A number, or a key name from ant_params / combat_params / game_params |
effect_per_level | Increment per level; also accepts key names |
effect_max | Cap; leave empty for none |
effect_decimals | Decimal places to display |
Displayed value = base + level × per_level, then converted per unit. Reference key names instead of copying numbers so a balance change can't leave the same value diverging in two places; a misspelled key is reported by validation.
6. Text overrides
Unlike data tables, text is overridden per key: list only the keys you want to change and the rest fall through to the base translations.
The header must be exactly keys,zh,en,ja,ko,fr,de,zh_TW,ar,it,es (column names are locale codes). Wrap values containing commas in double quotes, and escape an inner quote by doubling it: "".
7. Art rules
- Any size works: ants, insects, and the nest are normalized by target display width ÷ actual image width, so new art needs no config change. But
p.png(the carried crumb) scales by a fixed factor, so stay close to the original size. - Replace atlases as a pair (
.tpsheet+.png) — both files must be in your theme. The.tpsheetis TexturePacker JSON; if the frame layout is unchanged you can reuse the original and swap only the image. - Theme art is loaded as external files without engine import, so only PNG is supported, and VRAM use is higher than for compressed in-package textures.
- Interface chrome such as window buttons is deliberately not themeable: it belongs to the program shell, not the game world.
8. Install paths
Themes live in themes\ next to keyarium.exe, one subfolder per theme. The Workshop tab's 'Open theme folder' button opens whichever location is actually in effect.
| Location | Purpose | Priority |
|---|---|---|
keyarium.exe beside: themes\<id>\ |
Player install location; bundled official themes live here too | Medium |
%APPDATA%\Godot\app_userdata\Keyarium\themes\<id>\ |
Fallback when the game directory isn't writable (e.g. installed under Program Files) | High |
| Repo themes/<id>/ | Editor-only, for development | Low |
For the same id the fallback location wins, which lets you iterate on an official theme locally without touching shipped files. Switching themes restarts the game: on confirm it saves your progress and relaunches itself. The choice lives in user://theme.json, separate from saves — clearing your save does not reset the theme, because a theme is a display preference, not progress.
9. Local validation
You don't need to relaunch into the game to test a pack — run exactly the same validation from the command line:
keyarium.exe -- --validate-theme <your-theme-id>
It prints how many files you override, which data tables, whether Steam achievement sync is affected, and every error individually (table + entry + reason). Exit code 0 on success, 1 on errors, so you can wire it straight into your own build script. Inside the editor use godot --path . -- --validate-theme <id>.
Bundled themes must be loose files next to the exe, not packed into the pck: external art is read through the filesystem and cannot see resources inside a pck.
10. Known limits
Ant castes (niches) are fixed at four. You can rename, re-skin, and rebalance them, but not add or remove. The four slots are weak / mainline / strong / ranged — the Wolf Pack's cub, grey, alpha, and black wolves map onto them exactly. Loosening this would require changing both the save format and the cross-screen network protocol, so it's deferred.
Likewise a pack cannot add new achievement stat dimensions (stats are produced by the engine), but it can reuse existing ones with different copy and target values.
Start from a template
Two bundled packs are worth copying. wolf_pack/ is the official Wolf Pack: data and copy are done, and it only touches allowlisted columns, so achievement sync keeps working — use it as your template. verify/ is a framework test pack covering hatch interval, three nest strings, and a recolored fly atlas, to exercise the data + text + art path end to end (it changes hatch interval, which is gameplay-affecting, so it pauses sync).