Mods and the Steam Workshop

Modding guide

A mod is a folder of plain text files: spreadsheets, a few JSON files, PNG pictures and small Lua scripts. Nothing to install, nothing to compile. This is the same guide that ships with the game, file by file.

43chapters13,800words0code to compile
Get the kit

Downloads

Both also come with the game, in Primacy_Data\StreamingAssets\ModKit\, and the Mods window opens them for you.

Primacy · modding guide

Making mods for Primacy

A mod is a folder of plain text files: spreadsheets (CSV) you can edit in Excel or any text editor, a few JSON files, PNG pictures and small Lua scripts. There is nothing to install and nothing to compile. You put the folder in your mods folder, switch it on in the game's Mods window, and the game uses it from its next start. When it works, you publish it to the Steam Workshop from the same window.

What a mod can change: every country's start figures, cities (rename, change, remove, add), country names, flags and colours, brand-new countries, who owns each province, province names, tech nodes, Situation Room events, Government policies, new starts (scenarios), icons, fonts, translations, and rules written in Lua. What it cannot change: province shapes, rivers, coasts, terrain, the satellite picture, the tutorial, and game code (.dll files cannot be loaded).

Ten minutes

Your first mod, step by step#

  1. Start the game. On the main menu click Mods.
  2. Click Example mod. The game copies a working example into your mods folder (Documents\My Games\Primacy\Mods\example-mod). It appears in the list, switched off.
  3. Click Open mods folder. Copy the folder example-mod and rename the copy, for example my-first-mod.
  4. Open my-first-mod\mod.json in a text editor (Notepad works). Change "id" to "my-first-mod" and "name" to a name you like. The id must be unique: two mods with the same id cannot run together.
  5. Make one change. For example open tables\countries.csv and change Poland's unemployment: POL,=0.08, means "set it to 8%".
  6. Back in the game, close the Mods window and open it again (it reads the folder again). Switch your mod on and click Apply & restart (hold the button). The game restarts its session with your mod.
  7. Open Mods again. Your mod's row says Running and how many values it changed. If something is wrong it says how many problems its files have; click Mods log to see each one with its file and line.
  8. Start a new game as Poland and look at unemployment. That is a mod.
Before you change anything, click Reference files in the Mods window once. It writes every table with every row, and every code, id and name a mod can use, to Documents\My Games\Primacy\ModDump and opens that folder. Everything in this guide that says "find it in the reference" is in there.
Folders and files

Where everything is#

WhatWhere
Your mods (making and testing)Documents\My Games\Primacy\Mods\<your-mod>\. If your Documents folder is in OneDrive, it is under OneDrive. The Mods window's Open mods folder always opens the right one.
Mods from the WorkshopSteam keeps them in its own folder (steamapps\workshop\content\…). You do not need to touch them: subscribe on Steam and they appear in the Mods window.
The reference (every id, code and name)The Mods window's Reference files writes it to Documents\My Games\Primacy\ModDump\ (from the game as it runs now, your mods included). An unmodded copy ships with the game: in Steam, right-click Primacy, Manage → Browse local files, then Primacy_Data\StreamingAssets\ModKit\reference\.
The example mod and this guidePrimacy_Data\StreamingAssets\ModKit\ in the game's folder (Example mod and Modding guide in the Mods window open them).
The mods logDocuments\My Games\Primacy\mods.log, new each time the game starts. It lists every problem in every mod (file and line), every conflict, what each mod changed, and everything your scripts print. Mods log in the Mods window opens it.
Your mod list and languageKept by the game; change them only in the Mods window.
Never guess an id

Finding ids and names#

Every key a mod file uses comes from a file in the reference folder. Open them in Excel (Data → From Text/CSV, choose UTF-8) or a text editor and search with Ctrl+F.

You needReference fileWhat is in it
A country's code (POL, RUS…)lists\countries.csvcode, name now, the name the game's data uses, kind (Sovereign, Dependency…), whether it is on the map, provinces, capital, population. 250+ countries.
Country figure column namestables\countries.csvThe first line lists every column; each row is a country with its start values. See Country figures for what each column means.
A city's key (POL.lodz)cities\cities.csvEvery city (about 49,500): key, name, lat, lon, population, capital. Search the name, copy the key from the first column.
A province id (UKR-1234)map\provinces.csvEvery province (about 4,200): id, name, owner. Search the province's name or filter by owner.
A tech node idtech\nodes.csvEvery node (625): id, name, cost, year, weight, odds, and where it sits: field, branch, era. Search the name you see in the game's tech tree.
An event idevents\ids.txtEvery Situation Room event: id | family | tone | title.
A policy idpolicies\ids.txtEvery Government policy: id | ministry | name | range and unit (or "Choice").
Words for levers, ministries, voter groups, traits, laws, event families…lists\names.txtEvery word a mod file may use, grouped by where it goes.
Equipment rows and weapon type namesmilitary\equipment.csv, lists\weapon-types.csvEvery country's stock (key, country, type, in service, stored, on order, category); every one of the 4,512 weapon type names with category, year and maker.
Weapon type keys, stats, categories, shapesmilitaryweapon-types.csv, listsweapon-categories.csv, listsweapon-shapes-schools.csvEvery weapon type with its key, generation, ratings, cost, year, maker, shape and the countries that build it; the category ids and what their three ratings mean; the 3D shapes and design schools.
Aircraft and warship numbersmilitaryir-stats.csv, military aval-stats.csvEvery role and generation of aircraft (fighter.1 .. drone.6); every hull class of warship.
Air base keysmilitary\air-bases.csvEvery airfield squadrons can use: key, name, kind, lat, lon, runway, country.
Naval base keys, naviesmilitary\naval-bases.csv, military\navies.csvEvery naval base; every navy's ships by class.
Airport keys, seaport idstransport\airports.csv, transport\seaports.csvEvery civil airport (about 2,600); every seaport on the sea-trade routes.
Icon keyspictures\icons.txtEvery icon key the game draws (hud.economy, hud.bolt…).
Texts to translatelang\en\seen.csvEvery fixed text of the game (windows, hover explanations, events, policies), ready to fill in.
The tech tree in the game shows a node's name, not its id. Search that name in tech\nodes.csv: the id is in the first column of the same row (for example "Mobile Broadband" → comp.net.mobile-broadband).
What helps you

Tools: the Mods window, the console, the log#

The Mods window (main menu → Mods)

  • The list: every installed mod with its picture, version, tags and whether it changes gameplay. Its state line says Running (and how many values it changed), "On after a restart", "Off", "Cannot run: …" with the reason, or the first error in its mod.json.
  • The switch turns a mod on or off. Up / Down set the order (the top mod applies first, a lower mod wins over it).
  • Publish (local mods) uploads to the Workshop; Page (Workshop mods) opens its Workshop page.
  • Language: English and every language an installed mod offers.
  • Example mod, Modding guide, Reference files, Mods log, Open mods folder, Open Workshop.
  • Apply & restart (hold): the game's session starts again with the new list. It is only active when your list differs from what runs now.

The console (press the ` key, left of 1)

CommandDoes
modsLists every installed mod: on, off, blocked (and why), running.
mods checkReads every mod folder again and prints every problem: mod.json errors, warnings, and every error and conflict of the running mods with file and line.
mods logThe last 60 lines of the mods log (scripts' print and log lines too).
mods folderPrints where your local mods folder is.
modsdumpSame as Reference files: writes the reference to ModDump.
modsreloadRestarts the session with your mod files as they are on disk now: edit, reload, look, repeat.

The mods log

14:02:11      Running, in this order: Example mod (example-mod v1.0), My mod (my-mod v0.1); language en
14:02:12 ERROR my-mod: cities/cities.csv line 4: no key "POL.lods" in cities (write "new:POL.lods" to add it)
14:02:12 warn  conflict countries POL unemployment: set by example-mod, my-mod; my-mod wins
14:02:12      my-mod: 12 values changed, 1 problems
14:02:12      Applied in 640 ms
14:30:05      [my-mod] January 2026: Poland's debt is 61%
Required

mod.json#

Every mod folder needs a mod.json at its top. Without it the folder is not a mod.

{
  "id": "polish-golden-age",
  "name": "Polish Golden Age",
  "version": "2.0",
  "game": "0.1+",
  "description": "Poland's rise: a richer start, the Amber Coast and new events.",
  "tags": ["Scenario", "Countries", "Events"],
  "gameplay": true,
  "loadAfter": ["real-balance-2025"],
  "needs": [],
  "dlc": []
}
FieldRequiredWhat it does
idyesThe mod's unique name: 3 to 64 characters, only small letters a-z, digits, - and _. Saves and other mods refer to it, so never change it after you publish.
nameyesThe name players see.
versionno (1.0)Your version, numbers with dots ("1.2", "2.0.3"). Raise it when you publish an update.
gamenoThe game versions it works with: "0.1+" means 0.1 or newer; "0.1" means 0.1.x only; empty means any. A mismatch is a warning, not an error. The game's version is at the bottom left of the main menu.
descriptionnoOne line shown in the Mods window and used as the Workshop description when you first publish.
tagsnoWorkshop tags. Only these exact words count: Scenario, Balance, Overhaul, Graphics, Translation, Fixes, Map, Countries, Cities, Economy, Society, Military, Tech, Policies, Events, Scripts, Space DLC.
gameplayno (true)false marks a looks-only mod (flags, icons, fonts, translations). A looks-only mod can be added to or removed from an existing save; a gameplay mod cannot. Any mod switches achievements off.
loadAfternoIds of mods this one must apply after, when they are on (so it wins over them). Use it when you change another mod's results.
needsnoIds of mods this one cannot run without. If one is missing or off, this mod is blocked and the Mods window says why.
dlcno["space"] if the mod changes Space DLC content. Players without the pack see it blocked. A mod can never unlock a DLC.
A misspelt field (for example "loadafter") is an error, so a typo never silently does nothing. JSON needs double quotes around names and texts.

Other files at the top of a mod

  • preview.png (or .jpg): the mod's picture in the Mods window and on the Workshop. 512×512 or 1024×1024, under 1 MB.
  • workshop.txt: written by the game when you first publish; it holds your Workshop item number. Keep it: it makes your next upload update the same Workshop page.

The folder

my-mod/
  mod.json                 required
  preview.png              optional picture
  tables/countries.csv     country start figures          → Country figures
  cities/cities.csv        cities                          → Cities
  map/countries.csv        country names, new countries    → Country names
  map/owners.csv           who owns which province         → Who owns the land
  map/provinces.csv        province names                  → Province names
  tech/nodes.csv           tech nodes                      → Tech tree
  events/*.json            Situation Room events           → Events
  policies/*.json          Government policies             → Policies
  scenario/start.json      a start of its own              → Scenarios
  military/equipment.csv   weapons each country has        → Equipment
  military/weapon-types.csv each weapon type's stats, new types → Weapon types
  military/air-stats.csv   aircraft numbers by role and generation → Aircraft numbers
  military/naval-stats.csv warship numbers by hull class   → Warship numbers
  military/air-bases.csv   airfields for squadrons         → Air bases
  military/naval-bases.csv naval bases                     → Naval bases and navies
  military/navies.csv      ships by class per navy         → Naval bases and navies
  transport/airports.csv   civil airports                  → Airports and seaports
  transport/seaports.csv   seaports                        → Airports and seaports
  diplomacy/organisations.csv the UN, NATO, the IMF... and new ones → Organisations
  diplomacy/*.csv           trade deals, sanctions, treaties, disputes, bases... → Diplomacy
  military/warheads.csv    nuclear stockpiles          → Diplomacy and warheads
  military/navies.csv      (see above)
  scripts/*.lua            rules in Lua                    → Lua scripts
  pictures/flags/POL.png   a country's flag                → Flags and icons
  pictures/icons/<key>.png an icon                         → Flags and icons
  fonts/*.ttf, *.otf       letters the game lacks          → Fonts
  lang/<code>/language.json + *.csv   a translation       → Translations

Every part is optional except mod.json. A file in a folder the game knows but with a name it does not (for example tables/countrys.csv) is reported in the log, so a typo is found at once.

Writing the files

CSV and JSON rules#

CSV (tables)

  • The first line that is not a comment is the header: column names, separated by commas.
  • One row per line. Cells are separated by commas. Spaces around a cell are ignored.
  • A cell with a comma in it goes in double quotes: "Union of Soviet Socialist Republics, the". A quote inside: write it twice "".
  • Comments: a line starting with #, or space # space after a cell: POL,=0.2 # Poland. A colour like #a8322f is a value, not a comment.
  • Decimals use a dot: 0.25, never 0,25.
  • Save as UTF-8. In Excel: File → Save As → CSV UTF-8. Excel in some languages saves with semicolons: open the file in Notepad and check it uses commas.
  • Empty lines are ignored. A cell left empty keeps the game's value.

JSON (events, policies, scenarios, mod.json)

  • Names and texts in double quotes: "name": "Amber fund".
  • Numbers without quotes: "cost": 0.5. true, false, null without quotes.
  • Lists in [ ], objects in { }, items separated by commas.
  • Allowed in Primacy (not in strict JSON): comments // … and /* … */, and a comma after the last item.
  • A line break inside a text: \n. A quote inside a text: \".
  • Errors give the line: "line 12: ':' missing after "name"".
CSV patches

How tables change values#

A table file does not replace the game's table: it lists only the rows and columns you change. The first column is the key (a country code, a city key, a node id…); the other columns are any of that table's columns, in any order, as few as you like.

CellMeaningExampleResult (game value 0.20)
=x or xset to x=0.250.25
*xmultiply by x*1.10.22
+xadd x+0.050.25
+-xsubtract x+-0.050.15
(empty)keep the value0.20
yes / nofor yes/no columns (capital, dormant)yes
textfor text columns (names): set it; = optional=Leningrad
+A B / -A Bfor list columns (country codes, kinds): add these items / take them out; a plain list replaces the whole list. Adds and removals from several mods all apply (no conflict)+SWE FIN
  • Remove a row (cities only): put remove in the second cell: UKR.pripyat,remove.
  • Add a row (cities only): start the key with new: and fill every needed column: new:POL.amber-port,Amber Port,54.40,18.20,120000,no,3.
  • * and + only work on numbers; text can only be set.
  • A wrong line is reported and skipped; the rest of the file still applies.
How mods combine

Order, conflicts, restarts and saves#

When a mod applies

Once, while the game starts (before the map, borders, cities and names are built). Changing the mod list or the language therefore needs Apply & restart; a running game never mixes two mod lists. modsreload in the console does the same after you edited files.

Order

Mods apply from the top of the list to the bottom, moved only as far as each one's loadAfter needs. A later mod's = wins over an earlier one; * and + stack: VAT 23% with mod A =0.25 then mod B *0.8 gives 20%.

Conflicts

A value two mods both set with = is a conflict. It is not an error: the lower mod wins, and the Mods window and the log say which value, which mods and who won.

Saves

  • A save remembers its mods and their order.
  • A save loads only with the same gameplay mods in the same order. Otherwise the game shows which mods it needs, offers Subscribe to all (Workshop mods) and Use this save's mods (switches your list and loads the save).
  • Looks-only mods ("gameplay": false) can be added or removed at any time.
  • A newer version of the same mod is allowed: Workshop mods update themselves. Keep your updates compatible with saves made with older versions.
tables/countries.csv

Country figures#

Every number of every country at the start of a game: people, health, schools, safety, the economy, the budget and the forces. The key column is iso3, the country code (see lists\countries.csv). A country you change gets these values from day one; the economy, the forces and everything else are built from them.

iso3,population,unemployment,health.beds,militarySpending
POL,*1.05,=0.04,,
HUN,,,+0.5,
ISR,,,,=0.06
Shares are fractions: 0.05 means 5%. Per-person figures say "per 1,000" or "per 100,000". Copy a row from the reference to see real values before you change them.
ColumnWhat it is
dormantyes = not on the map at the start (a nation that can be released later); no = on the map.
populationPeople at the start.
urbanShareShare of people living in towns and cities (0.6 = 60%).
ages.childrenShare of people under working age.
ages.workingShare of people of working age.
ages.retiredShare of people of retirement age. The three ages add up to 1.
education.basicShare of adults with primary school or less.
education.skilledShare of adults with secondary or vocational school.
education.expertShare of adults with university. The three add up to 1.
birthRateBirths per 1,000 people per year.
deathRateDeaths per 1,000 people per year.
lifeExpectancyYears a baby born now would live on average.
participationShare of the working-age population in the labour force.
unemploymentShare of the labour force without work (0.05 = 5%).
health.spendingCurrent health spending as a share of GDP (0.12 = 12%), public and private.
health.stateShareShare of the health spending the state pays (the rest is households and insurance).
health.doctorsDoctors per 1,000 people.
health.nursesNurses and midwives per 1,000 people.
health.bedsHospital beds per 1,000 people.
health.infantDeathsBabies dying before their first birthday, per 1,000 born.
schooling.spendingPublic education spending as a share of GDP (0.05 = 5%); private schooling adds about 15% on top.
schooling.primaryGross enrolment ratios: pupils of any age over the children of that school age (can pass 1).
schooling.secondaryGross enrolment ratio in secondary school (can pass 1).
schooling.tertiaryGross enrolment ratio in universities and colleges (can pass 1).
schooling.literacyShare of adults who can read and write.
schooling.learningYearsLearning-adjusted years of school (World Bank Human Capital Index): years at school x how much is learnt.
safety.spendingPublic order & safety spending (police, fire crews, courts, prisons) as a share of GDP (0.018 = 1.8%).
safety.murdersMurders (intentional homicides) per 100,000 people a year.
safety.assaultsRecorded assaults per 100,000 people a year.
safety.robberiesRecorded robberies per 100,000 people a year.
safety.theftsRecorded thefts and burglaries per 100,000 people a year.
safety.drugOffencesRecorded drug offences per 100,000 people a year.
safety.fraudsRecorded frauds per 100,000 people a year.
safety.policePolice officers per 100,000 people.
safety.prisonersPrisoners per 100,000 people.
safety.organisedCrimeOrganised crime 0..1 (Global Organized Crime Index criminality, 1-10 scaled).
safety.gunsCivilian guns per 100 people.
safety.fireDeathsDeaths from fire, heat and hot substances per 100,000 people a year.
safety.burnedKm2Land burnt by wildfires in an average year, km2.
safety.policingPolicing style: 0 community, 1 standard, 2 iron fist.
safety.gunLawGun ownership: 0 strict, 1 licensed, 2 open.
safety.drugLawDrug policy: 0 prohibition, 1 decriminalised, 2 legal.
safety.sentencingSentencing: 0 rehabilitation, 1 standard, 2 punitive.
leisure.tourismInWhat foreign visitors spend here a year, as a share of GDP (0.05 = 5%).
leisure.tourismOutWhat residents spend on trips abroad a year, as a share of GDP.
leisure.arrivalsForeign visitor arrivals a year per resident.
leisure.householdShareHousehold consumption as a share of GDP.
gdpUsdNominal GDP in US dollars.
gdpPppUsdGDP at purchasing-power parity (international dollars).
priceLevelPrice level against the USA (the PPP ratio): India ~0.3, Switzerland ~1.4.
gdpShares.agricultureShare of GDP from farming, forests and fishing.
gdpShares.industryShare of GDP from industry, mining, energy and building.
gdpShares.servicesShare of GDP from services. The three add up to 1.
employmentShares.agricultureShare of jobs in farming.
employmentShares.industryShare of jobs in industry.
employmentShares.servicesShare of jobs in services. The three add up to 1.
inflationInflation per year (0.03 = 3%).
infrastructureRoads, rail, ports, grid: 0 none.. 1 excellent.
cityLevelHow developed the country's cities look at the start: 0 Informal, 1 Industrial, 2 Emerging, 3 Advanced, 4 Futuristic, 5 Sci-fi (fractions mix the two tiers). Big cities and the capital sit a little above it, small towns below. A city can have its own level in cities/cities.csv.
cityLevelOverrideUsed by the game's own data tools; leave it.
stateOwnershipShare of the economy the state owns at the start (per-sector shares come later).
planningHow much of investment follows the central plan instead of profit: 0 none.. 1 full planning.
revenueShareGovernment revenue as a share of GDP.
debtShareGovernment debt as a share of GDP (1 = 100%).
budgetBalanceGeneral government budget balance in 2024 as a share of GDP: net lending (+ surplus) or borrowing (- deficit).
interestShareNet interest the general government paid in 2024, a share of GDP; -1 = unknown (the model estimates it from the debt).
fxReservesTotal foreign reserves incl. gold at the end of 2024, in US dollars; -1 = unknown (the model assumes three months of imports).
militarySpendingMilitary spending as a share of GDP (0.02 = 2%).
armedForcesArmed forces personnel, active and paramilitary (IISS via the World Bank).
reservistsMilitary reservists: not under arms in peacetime, called up when needed.
airForce.fightersFighters and interceptors (air superiority; generation 4 and up are multirole).
airForce.attackAttack and strike aircraft (bombers not included).
airForce.bombersStrategic bombers.
airForce.attackHelicoptersAttack helicopters.
airForce.helicoptersEvery military helicopter, attack ones included.
airForce.tankersAir-to-air refuelling tankers.
airForce.specialMissionSpecial mission: AWACS, reconnaissance, electronic warfare, maritime patrol.
airForce.transportsTransport aircraft.
cities/cities.csv

Cities#

Rename a city, change its population, make it the capital, set how developed it looks, remove it, or add a new one anywhere on land. The game builds everything else from the list: buildings and night lights, map labels, offices and homes; the city's owner is the province it stands in.

key,name,lat,lon,population,capital,level
RUS.saint-petersburg,=Leningrad,,,,,
RUS.volgograd,=Stalingrad,,,*1.5,,=5
POL.lodz,remove
new:POL.amber-port,Amber Port,54.40,18.20,120000,no,3
ColumnMeaning
keyThe city: its country's code, a dot, and its name in small plain letters with - for spaces (accents removed): "Łódź" → POL.lodz, "Saint Petersburg" → RUS.saint-petersburg. A second city of the same name in the same country gets -2, -3. Always copy keys from cities\cities.csv in the reference. For a new city you choose the key: country code + dot + any name, e.g. new:POL.amber-port. The code decides which country's data the city starts with; its owner is still the province it stands in.
nameThe name shown everywhere (labels, windows, news).
lat, lonPosition in degrees (lat −90..90, north positive; lon −180..180, east positive), as in Google Maps' "lat, lon". Needed for a new city; changing them for an existing city moves it.
populationPeople in the city. It sets how big the city is drawn and how high its label ranks.
capitalyes / no. One capital per country: making a city the capital takes the title from the old one.
levelHow developed it looks at the start, 0 to 5: 0 Informal, 1 Industrial, 2 Emerging, 3 Advanced, 4 Futuristic, 5 Sci-fi (fractions mix two looks). Empty: the game decides from the country.
  • Removed city: its buildings and label go; its people stay in the province's figures. A stadium, holy site, station or airport it had serves the nearest city of the same country.
  • New city: needs name, lat, lon and population. Its people come from its province (the province's total stays the same). Buildings grow by themselves.
  • Adding or removing cities needs a new game (saves remember cities by their place in the list). Renames also work with old saves.
  • A name with letters the game's fonts lack (Москва, 東京) works: the letters come from a fallback font.
map/countries.csv

Country names and new countries#

iso3,name,short,adjective,flag,colour
RUS,Union of Soviet Socialist Republics,Soviet Union,Soviet,flags/SOV.png,#a8322f
KUR,Kurdistan,Kurdistan,Kurdish,flags/KUR.png,#c8a040
ColumnMeaning
iso3A code from lists\countries.csv renames that country. A code the game does not know creates a brand-new country with that code.
nameThe new name shown everywhere: the map, every window, news, Messages, diplomacy. Must not be another country's name. Needed for a new country.
short, adjectiveThe short name and the adjective ("Soviet Union", "Soviet"); scripts can read them (country(code).short).
flagA picture in your mod: flags/SOV.png or pictures/flags/SOV.png. PNG or JPG, 3:2 like the game's flags (for example 300×200).
colourThe map colour: #rrggbb (from any colour picker), e.g. #a8322f.
  • A renamed country keeps all its figures; the game's own data still finds it by its old name.
  • A new country starts with nothing: give it land in map/owners.csv. It builds its economy from the provinces it gets, like a state born from a revolt.
  • Rows apply before owners.csv, so a new country can receive land in the same mod.
map/owners.csv

Who owns the land#

from,to
UKR,RUS          # every province of Ukraine to Russia
BLR,RUS
IRQ-2201,KUR     # one province to the new country Kurdistan
  • from: a country code (every province it owns now) or a province id (one province). Province ids are in map\provinces.csv (column id, with its name and owner).
  • to: a country code, including a new country made in map/countries.csv.
  • Moves run top to bottom, before day 1, with the same annexation wars use: people, farms, industry and cities go with the land.
  • A country left with no land stays in the game (it can come back), like one that lost a war.
  • Province shapes never change: you can only give whole provinces.
map/provinces.csv

Province names#

id,name
RUS-2155,=Leningrad Oblast

The id comes from map\provinces.csv in the reference. Only the name can change; the owner column there is for information (owners change in map/owners.csv). Renames work with old saves.

tech/nodes.csv

Tech tree#

id,name,cost,year,weight,odds
comp.net.mobile-broadband,,*0.9,,,
enrg.fusion.compact-q1,=Compact fusion,,=2040,,

Finding the id: open tech\nodes.csv in the reference and search the node's name as the tech tree shows it. The first column is the id. The columns field, branch and era tell you where it sits (for example "Compute & AI", "comp.net", "The Digital Age").

ColumnMeaning
idThe node, e.g. comp.net.mobile-broadband (field.branch.name).
nameThe name shown in the tree.
costResearch points it takes (above 0). *0.5 halves it.
yearIts plausible year (1800..2300). Before that year, while no country has it yet, research on it is slower the earlier you are: 1 year early ≈ 0.77× speed, 5 years ≈ 0.29×, 10 years ≈ 0.12×. Once any country has it, or from that year on, it goes at full speed. 0 = no such brake (things already invented when the game starts).
weightHow much the node counts: in a country's overall technology level, and in what it costs to catch up on it when others already have it (higher = counts more, costs more to catch up).
oddsOnly for risky breakthroughs (a gamble): the chance one attempt succeeds, 0..1; each failed try adds 0.2, up to 0.95. 0 for ordinary nodes.
field, branch, eraWhere it sits in the tree (for information: they cannot change).
New nodes cannot be added yet, and a node cannot move or change its effects. Change costs, years, names, weights and odds.
events/*.json

Situation Room events#

Any number of .json files in events/. Each has "add" (new events), "change" (the game's or another mod's events), or both.

How events are picked

Each month the game weighs every event that may happen to a country: its weight formula gives a number (0 = cannot happen now). One event is picked with chances in proportion to the weights (an "Opportunity" counts ×1.25). An event does not come back to the same country before its cooldown (months); the player does not get two events of the same family within 75 days. Typical weights: 0.02 (rare) to 0.5 (likely).

{
  "add": [ {
    "id": "pga.vistula_boom",
    "family": "Economy", "tone": "Opportunity", "scope": "Player", "place": "BigCity", "other": "None",
    "countries": ["POL"],
    "cooldown": 48, "days": 7, "big": false, "chainOnly": false,
    "weight": "0.04 + ramp(gdpPerHead, 15000, 40000) * 0.06 * (atWar ? 0.5 : 1)",
    "title": "Factories flock to the Vistula",
    "text": "Three carmakers want to build near {place}. They ask for {money:0.2} in tax breaks.",
    "facts": [ ["Jobs", "{num:900-4000}"], ["Asked for", "{money:0.2}"] ],
    "voices": [ ["Economy minister", "Say yes before Hungary does."], ["Finance minister", "Every factory wants a gift."] ],
    "choices": [
      { "name": "Tax breaks", "about": "Pay to win them", "fx": "cost:0.2 gdp+0.3@24 mood:Business+3@12",
        "riskP": 0.2, "riskText": "One carmaker takes the money and leaves.", "riskFx": "ap-3@6",
        "echoP": 0.5, "echoMonths": 12, "echoEvent": "pga.vistula_second_wave", "echoText": "A second wave of investors calls." },
      { "name": "Let them come", "about": "No money from the state", "fx": "gdp+0.1@24", "hold": false, "notes": ["Slower"] }
    ],
    "none": "mood:Business-2@6",
    "noneText": "Nobody decided. The carmakers went to Hungary.",
    "worldNews": "{country} lands three car factories."
  } ],
  "change": {
    "volcano": { "off": true },
    "hostages_abroad": { "weightMul": 0.5, "cooldown": 60 },
    "quake": { "title": "The ground shakes in {place}", "days": 5 }
  }
}

Every field of an added event

FieldRequiredMeaning
idyesUnique id. Start it with your mod's short name (pga.) so it never clashes with the game's or another mod's (all ids: events\ids.txt).
familyno (Society)Disaster, Economy, Politics, Society, Security, Diplomacy, Military, Science, World.
toneno (Either)Crisis, Opportunity, Either: how the dossier is coloured.
scopeno (Both)Player (only the player's country gets it), Ai (only others: world news), Both.
placeno (Capital)Where it happens ({place}, and where the camera flies): Capital, BigCity, FarmProvince, Port, BorderWithOther, Province, EvHazard, None.
otherno (None)Another country it is about ({other}): None, Neighbour, HostileNeighbour, NeighbourAtWar, Ally, AllyAtWar, Rival, BigPower, Buyer, Victim.
countriesnoOnly these countries can get it (codes). Leave out for every country.
cooldownno (48)Months before it can come again to the same country.
daysno (7)Days the player has to decide (the clock on the dossier).
weightno (0.02)A number or a formula in quotes.
title, textyesThe dossier's headline and story. Templates like {place} are filled in.
factsnoA list of ["label", "value"] pairs shown as figures on the dossier.
voicesnoA list of ["who", "what they say"] pairs: the advisers.
choicesyes (1–4)The options; see below.
none, noneTextnoEffects and the line when the deadline passes with no choice.
bignotrue: a big crisis that pauses the game.
chainOnlynotrue: never rolled by itself; only opened by another event's echo or by a script.
worldNewsnoThe world news line when it happens to another country.

Every field of a choice

FieldMeaning
name, aboutThe button's title and the line under it.
fxEffect words separated by spaces: what the choice does.
riskP, riskText, riskFxThe chance (0..1) it goes wrong, what the player is told, and the extra effects then.
echoP, echoMonths, echoEvent, echoTextA follow-up: chance (0..1), after how many months, the event id it opens (yours or the game's), or only a line in Messages.
holdtrue: the player must hold the button (for choices that cannot be undone).
notesExtra chips on the card, a list of short texts ("Deaths −40%").

Changing an existing event ("change")

FieldMeaning
"off": trueThe event never happens.
"weightMul": 0.5Multiplies its weight (0.5 = half as likely).
"weight": "formula"Replaces its weight with your formula.
"cooldown", "days", "title", "text"Replace those.

Text templates#

WriteBecomes
{country}The country's name.
{place}, {province}The place (city or province, from place), the province.
{other}The other country (from other).
{capital}, {year}The capital's name, the year.
{money:1.2}1.2% of the country's GDP, in dollars ("$9.8 B").
{pop:0.4}0.4% of the place's people, as a number.
{num:900-4000}, {pct:20-60}A number / a percentage in that range (the same each time the dossier is shown).
fx, riskFx, none

Effect words#

Effects are short words separated by spaces: cost:0.2 gdp+0.3@24 mood:Business+3@12. @n at the end means "for n months" (the effect fades over that time); without it, it is immediate or permanent. All numbers use a dot for decimals.

WriteDoesExample
ap±n@mApproval points of the player's government, fading over m months.ap+7@4
mood:Group±n@mA voter group's mood. Groups (no spaces): Workers, Business, Retirees, Families, Experts, Publicstaff, Rural, Young, Savers, Patients, Soldiers, Faithful, Minorities.mood:Rural-3@8
cost:xA one-off cost: x% of GDP from the budget (Emergencies).cost:0.9
gain:xA one-off windfall: x% of GDP.gain:0.5
yearly:x@mx% of GDP a year for m months (a cost; negative = income).yearly:0.4@24
gdp±x@mThe country's output, % for m months.gdp-0.4@3
local±x@mOutput of the event's province only, %.local-3@6
farm±x@m / farmw±x@mHarvests in the province / the whole country, %.farmw-8@12
res±x@mResearch speed, %.res+5@24
mort±x@mDeaths, %.mort+2@6
inv±x@mHow attractive the country is to investors, %.inv-5@12
army±x@mArmy fighting quality, %.army-15@2
inf±nDiplomatic influence points (the player's).inf+20
rel±n:who@mOpinion of others towards the country; who = Other, Neighbours, World, Allies, Donors; fades over m months (default 12).rel+6:donors@12
trust±n:whoTrust of others, same who.trust-4:world
trait:name±nThe leader's reputation: Steady, Iron, Caring, Gambler, Builder, Hawk.trait:caring+1
mark:kind@daysA mark on the map at the event's place for that many days: Rubble, Flood, Ash, Cyclone, Evacuation, Riot, Smoke.mark:rubble@365
pol:id=v / pol:id*vSigns a Government policy (ids in policies\ids.txt) at value v, or its value now times v. It waits and phases in like any policy. The player's country only.pol:incb=20
law:Name=optionSigns an economy law option (names in lists\names.txt, "Laws").law:RentControl=1
med:what±n@mWar medicine: Beds, Deaths, Trains, Fatigue, Backlog, Pension, Abroad, Supply (%).med:beds+20@3
strike:nSettles a staff strike: 0 nurses, 1 doctors, 2 teachers.strike:0
A word the game cannot read is an error with the event id, so check the log after you add events.
weight

Chance formulas#

A formula is a text in quotes. It is worked out for each country each month; the result is the event's weight (0 or less = it cannot happen).

What you can write

  • Numbers: 0.04, 2.5e-3.
  • + - * / and brackets (dividing by 0 gives 0).
  • Comparisons: < > <= >= == != (1 true, 0 false).
  • Logic: && and, || or, ! not, true, false.
  • a ? b : c: b if a is true, else c.
  • ramp(x, from, to): 0 below from, 1 at to and above, a straight line between.
  • clamp(x, lo, hi), min(a, b), max(a, b), abs(x), pow(a, b), if(c, a, b).

Examples

"0.03"                                        always the same
"atWar ? 0.2 : 0"                             only at war
"ramp(unemployment, 0.06, 0.15) * 0.4"        grows with joblessness
"0.05 + (approval < 35) * 0.1"               likelier when unpopular
"coastal && month >= 6 && month <= 9 ? 0.1 : 0" summer, on the coast

Values a formula can read (the country being weighed)

NameMeaning and unit
unemploymentShare of the labour force without work (0.05 = 5%).
inflationPrice rise per year (0.03 = 3%).
growthEconomic growth in percent (2.5 = 2.5%).
debtShareState debt as a share of GDP (1 = 100%).
gdp, gdpPerHeadGDP in dollars; GDP per person in dollars.
approvalThe player's approval, 0..100 (other countries: 50).
foodPriceFood price level: 1 normal, higher when inflation is high.
researchResearch effort, about 0.002 (poor) to 0.04 (rich).
atWar, neighbourAtWar1 if the country / a neighbour is at war, else 0.
exporter, bigCountry, coastal1 or 0: a big exporter; a big country; has a coast.
soldiersMood, workersMood, youngMood, expertsMood, businessMoodThe player's voter group moods, −100..100 (0 for other countries).
tensionTension between faiths, about 0 (calm) to 1.
wearinessWeariness of a campaign against armed groups (0 none).
corruption0..1.
terrorThreat, campaigningThreat from armed groups 0..1; 1 if the country fights a campaign against them.
month, yearThe date: month 1..12, the year.
latThe capital's latitude (negative = southern hemisphere).
player1 if this is the player's country.
policies/*.json

Government policies#

Change the game's policies, or add new sliders. A new policy appears in its ministry in the Government window, is signed like any other, waits, then phases in. Ids: policies\ids.txt.

{
  "change": {
    "incb": { "max": 60, "wait": 2, "name": "Income tax (flat)", "about": "One rate for every worker." }
  },
  "add": [ {
    "id": "pga.amber_fund",
    "ministry": "Industry",
    "name": "Amber Coast fund",
    "about": "State money that makes the coast attractive to investors.",
    "examples": "Like Ireland's 1990s grants",
    "unit": "%", "min": 0, "max": 2, "step": 0.1, "default": 0,
    "wait": 3, "ramp": 12, "stage1": "Parliament", "stage2": "Money flows",
    "levers": [ { "stat": "InvestmentAttractiveness", "perUnit": 1.5 },
                { "stat": "Corruption", "perUnit": 0.5 } ],
    "forecast": "gdp .02 .05 bal -.03 -.01",
    "upBetter": ["Investors come to the coast"], "upWorse": ["Costs the budget"],
    "downBetter": ["Saves money"], "downWorse": ["Fewer new firms"],
    "whoUp": "Business +", "whoDown": "Business -"
  } ]
}

Changing a policy

min, max, step (slider range), wait (months until it starts), ramp (months until full effect), name, about, examples.

Every field of a new policy

FieldMeaning
idUnique id; start it with your mod's short name.
ministryTreasury, CentralBank, Labour, Industry, Energy, Health, Education, Safety, Leisure, Defence, Faith, Sport, Transport, Housing (full list: lists\names.txt).
name, about, examplesWhat the card shows.
unit, min, max, step, defaultThe slider: its unit text, range, step, and the value every country starts with.
wait, ramp, stage1, stage2Months until it starts and until full effect, and the names of the two stages.
leversWhat it does. Each lever moves one economy figure of the country by perUnit % for each unit of the slider: perUnit 1.5 at slider 2 means +3%. Negative perUnit lowers it. The lever names (stat) are in lists\names.txt under "Policy levers": SectorOutput, LaborProductivity, InputUse, LandYield, ExtractionRate, Recovery, ConstructionCost, BuildTime, MaintenanceCost, TradeCost, TariffRevenue, ExportPrice, ResearchSpeed, TechDiffusion, BirthRate, Mortality, MigrationPull, EducationCoverage, Happiness, Pollution, Corruption, Stability, InterestRate, TaxCollection, InvestmentAttractiveness, AccidentChance, SpoilageRate.
forecastWhat the card forecasts per unit, as pairs of low and high: keys gdp (% of GDP), unemp (points), infl (points), bal (budget, % of GDP), debt (% of GDP), le (life expectancy, years), sch (school years), appr (approval points). "gdp .02 .05 bal -.03 -.01".
upBetter, upWorse, downBetter, downWorseLists of short lines: what is better and worse when it goes up or down.
whoUp, whoDownWhich voter groups like it: "Business +, Workers -".
Levers are the same knobs technologies use, so a mod policy acts on the real economy. A policy's real money cost is not automatic: add a cost lever (e.g. TaxCollection with a negative perUnit) or describe it in the forecast.
scenario/start.json

Scenarios#

A scenario is a start of its own. Combine it with map/countries.csv and map/owners.csv for a different world on day one. It shows on the nation pick's GAME SETUP strip. If two running mods have one, the game merges them setting by setting: a setting a mod leaves out keeps what a mod above it gave (a scenario without year keeps another mod's year), a setting both give is the lower mod's (shown as a conflict), and their wars add up.

{
  "name": "The Soviet Union returns",
  "description": "Russia has taken back the Soviet republics. Poland and Finland stand alone.",
  "keepGameWars": false,
  "wars": [ {
    "attacker": "RUS", "defender": "POL", "since": "2024-12-20",
    "held": [ [ [23.5,52.1], [24.2,52.1], [24.2,51.6], [23.5,51.6] ] ],
    "fronts": [ { "name": "Brest", "lon": 23.7, "lat": 52.1,
                  "attackerGroup": "Western group", "attackerDivisions": 6, "attackerArtillery": 1,
                  "defenderGroup": "Eastern command", "defenderDivisions": 4, "defenderArtillery": 1 } ]
  } ]
}
FieldMeaning
name, descriptionShown on GAME SETUP.
keepGameWarstrue (default): the game's own wars of the day (Russia–Ukraine) start too; false: only yours.
yearAn earlier start: the world's year, 1946 .. 2023 (leave it out for the game's 2024). The clock starts on 1 January of the next year ("year": 1985 → 1 January 1986). Every weapon type that entered service after that year leaves the arsenals: each country's stock of it becomes the newest type of the same kind in service by then (its own older versions first, then its family, its design school, its maker), with the same numbers; orders of it are dropped; a kind that did not exist yet (drones in 1960) is removed. Weapon-tree steps of later years are not known to anyone and must be developed again, and the programmes and production lines of 2024 follow the same year. The tech tree too: every technology first made after that year is forgotten by every country, and each field's level falls by what those technologies gave, so economies start weaker (most in electronics, energy, medicine) and grow back as they research them again. Dates of play are shown on the scenario's calendar (the game counts on its own calendar inside, so every system runs as usual); dates of the 2024 data's history keep their real years. Countries, borders and populations stay those of 2024 (change them with the other files), and content tied to dates (5G, new cables, sport tournaments, space missions) comes on its own schedule, that many years early.
wars[].attacker, defenderCountry codes (renamed or new countries too).
wars[].sinceThe date the war began, YYYY-MM-DD (it can be before the game starts).
wars[].heldAreas the attacker already holds: a list of rings; each ring is at least 3 points [lon, lat] in order around the area.
wars[].frontsWhere the forces stand: a point on the line, and each side's divisions (10,000 troops each) and artillery divisions. Armies appear a few kilometres behind it on each side.

An earlier start needs only the year (and usually no wars of today):

{ "name": "Cold War, 1985", "description": "The world of 1985.", "keepGameWars": false, "year": 1985 }

Wars start with the "Start with wars" option on GAME SETUP; off means a world at peace.

Soldiers and aircraft

Armies and squadrons#

A country's forces at the start are built from its figures and its equipment, so you change them in two files:

To changeWhereExample
How many soldiers (armies are formed from them)tables/countries.csv: armedForces (active), reservistsPOL,=300000,=350000
How many aircraft of each role (squadrons are formed from them, at the air bases the country holds)tables/countries.csv: airForce.fighters, airForce.attack, airForce.bombers, airForce.attackHelicopters, airForce.helicopters, airForce.tankers, airForce.specialMission, airForce.transportsPOL,,,=96
Which tanks, jets, ships' weapons, drones they havemilitary/equipment.csvsee Equipment
What a tank, jet, ship or drone can do; new types of your ownmilitary/weapon-types.csvsee Weapon types
How far, fast and hard each kind of aircraft flies and fightsmilitary/air-stats.csvsee Aircraft numbers
Warships' speed, missiles, sensors, armourmilitary/naval-stats.csvsee Warship numbers
Where squadrons can bemilitary/air-bases.csvsee Air bases
Shipsmilitary/navies.csv, military/naval-bases.csvsee Naval bases and navies
Wars and fronts on day onescenario/start.jsonsee Scenarios

A brand-new country made by a mod has no row in tables/countries.csv, so it starts with no forces of its own; it raises them in play like any country. Squadrons only form at air bases inside the country's land on day one.

military/equipment.csv

Equipment#

Every country's weapons at the start: how many of each type it has in service, in storage and on order. Armies, squadrons and fleets take their weapons from this list.

key,country,type,active,stored,ordered
POL.k2-black-panther,,,*1.5,,
UKR.t-64bv,remove
new:POL.leopard-2a7v,POL,Leopard 2A7V,120,0,0
ColumnMeaning
keyThe country's code, a dot, and the type's name in plain letters (POL.k2-black-panther); -2 for a second row of the same type. Copy keys from military\equipment.csv. For a new row choose any key starting with the country code.
countryThe owner's code (needed for a new row).
typeThe weapon type's exact name from lists\weapon-types.csv (e.g. Leopard 2A7V, F-35A Lightning II). Needed for a new row. A type your mod adds in military/weapon-types.csv works too (by its name), and so does a weapon-types key (leopard-2a7v).
active, stored, orderedIn service; in storage (can be brought back); ordered and still to come.
categoryFor information (tank, fighter, frigate…): it comes with the type.
military/weapon-types.csv

Weapon types: stats and new types#

Every tank, armoured vehicle, gun, air-defence system, aircraft, helicopter, drone and warship in the game is a weapon type: 4,512 of them, from the T-55 to designs of the 2060s. This file changes what each type can do, and adds new types of your own: a new bomber, fighter, tank or frigate.

key,name,category,generation,rating1,rating2,rating3,cost,year,maker,school,model,family,builtBy
su-57,,,=5,,=15,,,,,,,,
tu-160,=Tu-160 White Swan,,,,*1.5,,,,,,,,
leopard-2a7v,,,,=12,,,=18,,,,,,
new:tu-260,Tu-260 Storm,bomber,5.2,20,30,10,450,2031,RUS,RUS,Bomber,Tu-260,RUS
new:pzl-hawk,PZL Hawk,multirole,4.4,5,10,0,70,2029,POL,EURO,Fighter,Hawk,POL

The first three lines change the game's own types (Su-57 one generation step up and better weapons; the Tu-160 renamed and its weapons rating half again higher; the Leopard 2A7V better protected and dearer). The last two add new aircraft that Russia and Poland can build from the first day.

ColumnMeaning
keyThe type's name in plain letters: lower case, every run of other characters turned into one - (Su-57 → su-57, F-16C/D Block 50 → f-16c-d-block-50). A second type with the same plain name gets -2. Copy keys from military\weapon-types.csv. For a new type write new: and any key not taken.
nameIts name everywhere in the game. Names must all differ: saves, military/equipment.csv and the arms market find a type by its name. You may rename the game's types; a renamed type is still found by its old name, so older saves and your equipment rows keep working.
categoryWhat it is: an id from lists\weapon-categories.csv (mbt, ifv, spg, sam_long, fighter, multirole, attack, bomber, attack_heli, male_drone, ucav, ddg, ffg, ssn…). Needed for a new type. The game's own types keep their category (armies, squadrons and fleets are built round it): to turn a tank into something else, add a new type.
generationThe most important number: how modern it is. Aircraft and ships 1 .. 6, where for aircraft 1 = Gen 2, 2 = Gen 3, 3 = Gen 4, 4 = Gen 4.5, 5 = Gen 5, 6 = Gen 6 (an F-16C/D Block 50 is 3.2, an Su-57 4.7). Ships: 1 = 1950s, 2 = 1960s-70s, 3 = 1980s, 4 = 1990s-2000s, 5 = 2010s, 6 = newest. Armour and guns 0.2 .. 5 (a T-55 about 1, a Leopard 2A7V 3.4). Decimals are fine. The game allows 0.2 .. 7.
rating1, rating2, rating3How much better (or worse) it is than an average type of its generation, in % from -100 to 100 (the game's own types stay within -20 .. +20). Each category has its own three ratings: the ratings column names them (e.g. Sensors/Weapons/Range for fighters). See the table below for what each one does.
ratingsFor information: the names of the three ratings for its category.
costThe price of one, in US$ million (a Leopard 2A7V 15, an F-35A about 80, a destroyer about 2,000). Building, buying and selling use it.
yearThe year it entered service (1900 .. 2300): shown in windows, and of two types of the same generation a country builds and fields the newer.
makerThe 3-letter code of the country that designed it (SUN is the Soviet Union for old Soviet types).
schoolThe design school it belongs to: US, EURO, RUS, CHN, ISR, KOR, TUR, IND, JPN, IRN, PAK, BRA, UKR, Other (lists\weapon-shapes-schools.csv). Empty for a new type: Other.
modelThe 3D shape drawn on the map: Tank, TrackedIfv, WheeledApc, Fighter, Strike, Bomber, BigJet, Helicopter, Drone, Destroyer, Frigate, Submarine… (all in lists\weapon-shapes-schools.csv). Mods cannot add new 3D models. Empty for a new type: the usual shape of its category.
familyThe family it belongs to (T-72, F-16): shown in windows. Empty for a new type: its name.
builtByThe countries whose factories make it from the start: 3-letter codes separated by spaces (RUS BLR). They can order it on their production lines and sell it to others on the arms market. Empty: nobody builds it (it exists only where military/equipment.csv puts it).

What the three ratings do

Categoryrating1rating2rating3
Tanks, IFVs, APCs, MRAPs, recceProtectionFirepowerMobility: half of it is added to the army's march speed (+20 = 10% faster)
Guns and rocket launchersRangeFirepowerMobility (as above)
Air defence (SAMs, AA guns)ReachFirepowerMobility
Fighters, multirole, attack, bombersSensorsWeapons: multiplies the squadron's ground attack (+30 = 30% harder strikes)Range: multiplies its combat radius (+10 = 10% further)
HelicoptersProtectionWeaponsRange
DronesSensorsWeaponsEndurance
Warships and submarinesDefenceStrikeEndurance: multiplies its days at sea (+25 = a quarter longer)
Support (trucks, tankers, transports, AEW)CapacitySensorsRange

All three also count together as the type's worth: 1 + (rating1 + rating2 + rating3) / 300. An army's fighting power is multiplied by its equipment's worth (three ratings of +30 = 30% stronger), and the AI compares types by it.

How a type's generation becomes real numbers

  • Aircraft: a squadron flies with the numbers of its role at its aircraft's generation from military/air-stats.csv, taken between the two nearest rows (generation 4.6 = 60% of the way from the Gen 4.5 row to the Gen 5 row). Then rating2 and rating3 above. The role comes from the category: fighter → fighter; multirole → fighter or strike; attack → strike; bomber → bomber; male_drone, ucav → drone.
  • Ships: a ship has the numbers of its hull class from military/naval-stats.csv at its generation rounded (4.6 → 5). The hull comes from the category: cv carrier, lhd assault ship, lpd landing ship, cg cruiser, ddg destroyer, ffg frigate, corvette corvette, ssn/ssgn attack submarine, ssk/midget diesel submarine, ssbn missile submarine, mcm minesweeper.
  • Armies: an army's generation is the average of its equipment's. Each step of generation over its country's starting level makes it markedly stronger, and its worth (above) multiplies that.

Giving your new type to countries

  1. Add the type here with new: (name, category, generation, ratings, cost, year at least).
  2. Put it in service: add rows to military/equipment.csv using its name as the type:
    key,country,type,active,stored,ordered
    new:RUS.tu-260,RUS,Tu-260 Storm,16,0,8
    Squadrons, armies and fleets take it from their country's stock at the start (newest first).
  3. Let countries build it: write their codes in builtBy. Others can then buy it from them on the arms market.

New types come after the game's 4,512 and cannot be removed (rows of this file cannot be removed at all: set a type's equipment rows to 0 instead). A save that holds a mod's type needs that mod to be on: without it, those holdings are dropped on loading and the log says how many.

military/air-stats.csv

Aircraft numbers#

How far, how fast and how hard every kind of aircraft flies and fights, by role and generation: 30 rows, fighter.1 (a Gen 2 fighter) to drone.6. The roles are fighter, strike (strike fighters and attack aircraft), bomber, helicopter (attack helicopters) and drone (MALE and combat drones). Generation 1 .. 6 = Gen 2, 3, 4, 4.5, 5, 6.

key,radius,speed,stealth,radar,missileRange,missiles
fighter.5,*1.2,,=0.7,,,
fighter.6,,=1300,,=260,=200,=8
bomber.4,=4000,,,,,
ColumnMeaning
keyRole, a dot, generation 1 .. 6.
role, generation, lowFor information (a helicopter flies low, under long-range radar).
radiusCombat radius in km: how far it strikes and comes back (at least 20). It can move to another base up to 2.5 times that (helicopters 3 times).
speedCruise speed in km/h (at least 50).
airToAirAir-to-air power of one aircraft (a fighter 1; bombers 0).
groundAttackGround-attack power of one aircraft (a strike fighter 1, a bomber 6; each generation adds 10% on top).
stealth0 .. 0.95: how much it shrinks the distance radars see it at (0.6 = seen at 40% of the usual range).
radarHow far its own radar sees an aircraft without stealth, in km.
missileRangeReach of its long-range air-to-air missiles in km (0 = guns and short-range missiles only).
missilesLong-range missiles one aircraft carries.
enduranceHours it can stay in the air on internal fuel (at least 0.2). A patrol stays on station for what the trip out and back leaves.
standoffHow far from its target it releases its weapons, in km (0 = it must fly over the target; bombers of Gen 4.5 and up fire cruise missiles from 300 km).
squadronThe most aircraft in a squadron of this role (1 .. 60). One number per role: set it on any row of the role and it counts for all its generations.
runwayThe shortest runway it needs, in metres (0 = any field).
altitudeCruise altitude in km above the ground.
sizeHow big it looks to radar and the eye (a fighter 1, a bomber 3; at least 0.05).
agilityHow well it turns and dodges (a fighter 1, a bomber 0.25).
hitEaseHow easy it is to hit (a fighter 1, a bomber 1.4; at least 0.1).
hoursOverHours it stays over its target.
turnaroundHours on the ground between sorties (re-arming, refuelling, repairs).
sortiesPerDayThe most sorties one aircraft flies a day (0.1 .. 12).

Changes count for every country. A squadron flying a mix of types averages the numbers of their generations, then applies their weapons and range ratings (Weapon types).

military/air-bases.csv

Air bases#

key,name,kind,lat,lon,runway,country
POL.lask-air-base,=Lask 32nd Tactical Air Base,,,,=3200,
new:POL.amber-coast-air-base,Amber Coast Air Base,military,54.45,18.30,2800,POL
POL.copernicus-wroclaw-airport,remove
ColumnMeaning
keyCountry code + dot + the base's name in plain letters. Copy from military\air-bases.csv.
nameShown on the map and in windows.
kindmilitary or civil (a civil airfield holds only a few squadrons).
lat, lonWhere it is, in degrees.
runwayLongest runway in metres (500..6000): longer runways take heavier aircraft and more squadrons.
countryThe code of the country the base belongs to in the data. In play, a base serves whoever holds its ground.

Squadrons are placed at the start at the bases each country holds; a country with aircraft but no base has nowhere to fly from. Air bases and civil airports are separate lists: this one is for military flying, airports are for passengers and the economy.

transport/airports.csv · transport/seaports.csv

Airports and seaports#

# transport/airports.csv
key,name,lat,lon,country,runways,passengers,military,iata
POL.warsaw-chopin-airport,=Warsaw Okecie,,,,,*1.2,,
new:POL.amber-coast-airport,Amber Coast Airport,54.38,18.47,POL,2,4.5,no,AMB

# transport/seaports.csv
id,name,depth,containers,bulk,oil,lng
gdansk,,=18,*1.5,,,
Airport columnMeaning
keyCountry code + dot + the airport's name in plain letters. Copy from transport\airports.csv.
name, lat, lon, countryIts name, position and country. A new airport's province is the one it stands in.
runways1..6.
passengersMillions a year at the start.
militaryyes: also used as a military airfield.
iataIts three-letter code (shown in windows; optional).
provinceFor information (where it stands).
Seaport columnMeaning
idThe port's id (the sea-trade routes run between these). Seaports can be changed but not added or removed.
nameIts name.
depthWater depth at its berths in metres (up to 40): deeper takes bigger ships.
containers, bulk, oil, lngTraffic a year at the start: containers in millions of TEU; bulk, oil and LNG in millions of tonnes. The berths are built to carry it.
diplomacy/organisations.csv

Organisations#

The international organisations of the start: the UN and its Security Council, NATO, the EU, the IMF, the WTO, OPEC+, BRICS and the rest (27 rows, keys in diplomacy\organisations.csv of the reference). Change their members and rules, remove them, or found new ones.

key,name,kinds,voting,members,suspended,mutual,vetoes
NATO,,,,+UKR GEO,,,
EU,,,,-HUN,,,
IMF,remove
new:WARSAW,Warsaw Pact,defence arms,consensus,RUS POL CZE SVK HUN ROU BGR,,yes,
new:COMECON,Council for Mutual Economic Assistance,trade money,consensus,RUS POL CZE SVK HUN ROU BGR CUB VNM MNG,,no,
ColumnMeaning
keyIts short code (UN, UNSC, NATO, EU, IMF…). A new one: new: + a code of your own, letters and digits (no | or :).
nameIts full name, shown in the Diplomacy window.
kindsWhat it does, a list of: defence and arms (members vote to deploy battlegroups and hold exercises), trade (lower tariffs between members), money (members vote on loans to a member in trouble), move (free movement of people), politics (votes to condemn, suspend and readmit).
votingHow it decides: consensus or unanimity (one no blocks), majority, weighted (by the size of the economy), quotas (by money paid in), veto (a majority, but the vetoes members can block).
members3-letter country codes (lists\countries.csv), the order kept. +SWE FIN adds members, -HUN takes one out, a plain list replaces them all.
suspendedMembers barred from its votes and benefits for now (they stay members).
mutualyes: an attack on one is an attack on all, as in NATO. Members become allies and join each other's defensive wars.
vetoesMembers whose no blocks a vote: the Security Council's five permanent members.
  • The game's own keep their own rules. The UN needs two thirds; the Security Council's resolutions need 9 votes and no veto; the IMF lends to members deep in debt; OPEC+ cuts and raises oil output; NATO and the EU need every member. Your changes to members, name, kinds and voting still apply.
  • A new organisation works by its columns. A defence organisation with mutual yes is an alliance; a trade one lowers tariffs between members; a money one can vote loans.
  • Removing one removes what it does. Without the UN there are no General Assembly votes and no UN sanctions; without the Security Council no resolutions or UN peacekeepers; without the IMF no bailouts; without NATO no NATO battlegroups on the eastern flank.
  • Trade agreements between countries (free trade deals) and defence treaties between two countries are not in this table.
diplomacy/*.csv

The diplomacy of the start#

Besides the organisations, eight more files hold the world's diplomacy on day one. Each is the game's own list row by row (see diplomacy\ in the reference): change a row, remove it (key,remove), or add one (new:key,...). Codes are 3-letter country codes; a sender or treaty party may also be an organisation (EU, UN, NATO). List columns take +codes and -codes.

FileColumnsExample
trade-deals.csvname, type (FTA, CU, EIA, PTA), parties, yearnew:comecon-trade,COMECON trade,PTA,RUS POL CZE HUN,1949
sanctions.csvsender, target, packages (travel arms tech finance energy full), sinceEU.RUS,remove
treaties.csvparty, with, kind (mutual, guarantee, umbrella), year, notenew:RUS.MNG.mutual,RUS,MNG,mutual,1966,Soviet troops in Mongolia
disputes.csvclaimant, holder, area, provinces, severity (1 dormant .. 3 armed)new:TCD.LBY-1,TCD,LBY,The Aouzou Strip,,3
recognition.csvcountry, kind (recognised_by / not_recognised_by), countriesTWN,,,=KOR SAU ZAF PAN
no-relations.csva, b, since (a year or never)new:ISR.RUS,ISR,RUS,1967
bases.csvuser, host, name, lat, lon, kind (air naval army mixed radar), personnelnew:RUS.wunsdorf,RUS,DDR,Wünsdorf,52.17,13.47,mixed,380000
history.csva, b, value (-50..50), years it lasts, textnew:IRN.IRQ,IRN,IRQ,-35,40,The war since 1980

Warheads

military/warheads.csv: the nine nuclear states' stockpiles on day one: key,tactical,strategic,deployedTactical,deployedStrategic,perYear,cap,shownLow,shownHigh. The Soviet Union of 1985: RUS,=29000,=10200,=8000,=9900,=1500,=40000,=33000,=45000.

A start before 2024

A scenario with a year also trims what did not exist yet, by itself: weapons and drones of later years, named warships commissioned later, modern coastal missile batteries, SAMs and aircraft beyond the year's generation, space missions, launch sites, observatories and satellites of later years, and NATO's eastern battlegroups (2017). Wars in scenario/start.json take real dates of that year ("since": "1980-09-22"), and scripts see the shown year (date().year is 1986 in a 1985 world).

pictures/

Flags and icons#

  • pictures/flags/POL.png: replaces a country's flag, named by its code. PNG or JPG, 3:2 (e.g. 300×200).
  • pictures/icons/<key>.png: replaces an icon. Keys are in pictures\icons.txt (e.g. hud.economy.png). Draw it white on a transparent background, square, 128×128: the game colours it like its own icons.
  • At most 4096×4096. A mod with only pictures should be "gameplay": false, so players can add it to old saves.
fonts/

Fonts#

The game's fonts cover the letters of its own text. Letters they lack (a Cyrillic city name, a Chinese translation) come from a fallback font, looked for in this order: .ttf / .otf files in your mod's fonts/ folder, then the fonts Windows has (Arial for Latin, Greek and Cyrillic; Microsoft YaHei, Yu Gothic, Malgun Gothic for Chinese, Japanese, Korean; Nirmala and Leelawadee for Indian scripts and Thai). The map labels copy the letters they need into their own atlas. Ship a font only if Windows' fonts do not have your letters, and only fonts you may share.

lang/

Translations#

my-translation/
  mod.json                 "gameplay": false, "tags": ["Translation"]
  lang/de/language.json    { "name": "Deutsch" }
  lang/de/menu.csv
  lang/de/events.csv       (any number of .csv files)
# lang/de/menu.csv
source,text
New game,Neues Spiel
Load game,Spiel laden
"Mods apply from top to bottom. Any mod turns achievements off.","Mods gelten von oben nach unten. Jeder Mod schaltet Erfolge ab."
  • The folder name is the language code you choose (de, pl, pt-br); language.json gives the name players pick in the Mods window.
  • source is the English text exactly as the game has it, capitals and punctuation included; text is yours. Put a cell with a comma in double quotes. \n is a line break.
  • The full list of the game's texts: lang\en\seen.csv in the reference (windows, buttons, hover explanations, events, policies). Copy it, fill the second column, and remove rows you do not translate.
  • Window captions show in capitals, but you translate them as written in the list.
  • Players pick the language in the Mods window; it applies after Apply & restart.
  • Texts the game builds with numbers inside ("12 provinces") stay English for now; everything fixed is translated.
scripts/*.lua

Lua scripts: how they run#

  • Every .lua file in your mod's scripts/ folder runs once when the game starts (after the mods applied). Files run in name order; they share one set of global variables per mod. Each mod has its own: mods cannot see each other's variables.
  • That first run should only set things up: define functions and register handlers with on(event, function). The game calls a handler when its event happens (a new month, a war…), never every frame.
  • Global variables keep their values between calls for the whole session. They are not saved: after loading a save, scripts start fresh. Work out what you need from the game (country(), date()) rather than counting in variables when it must survive a load.
  • Scripts read the game and ask it to do things through the functions below; they cannot reach anything else (no files, no internet, no other programs).
-- scripts/rules.lua
local warned = false                      -- a variable of this file

on("new_month", function(e)               -- runs every new month
  local pl = country("POL")               -- a table of Poland's figures
  if pl.debtShare > 0.6 and not warned then
    message("Poland's debt", string.format("Debt is %.0f%% of GDP.", pl.debtShare * 100))
    warned = true
  end
end)
Lua in Primacy

The Lua language (what scripts can use)#

Primacy runs standard Lua syntax with the parts below. If you know any Lua (Roblox, WoW, Garry's Mod), this is the same.

Values

nil                    nothing
true, false
42, 3.5, 1e6           numbers (all decimal)
"text", 'text', [[long
text]]
{ 1, 2, 3 }            a list (index from 1)
{ name = "POL", n = 5 } a table with fields
function(x) return x end

Variables

local x = 1          -- this file / this block only
count = 0            -- global (this mod's scripts)
local a, b = 1, 2
t.name = "Poland"    -- set a field
t["name"]            -- read a field
#t                   -- length of a list or text

Operators

+ - * / % ^          arithmetic (% remainder, ^ power)
..                   join texts: "a" .. 1
== ~= < > <= >=    compare (~= is "not equal")
and or not           logic
x = x or 0           default value

Control

if a then ... elseif b then ... else ... end
while cond do ... end
repeat ... until cond
for i = 1, 10 do ... end        -- 1..10
for i = 10, 1, -1 do ... end    -- step
for i, v in ipairs(list) do ... end
for k, v in pairs(tbl) do ... end
break
return value

Functions

local function add(a, b) return a + b end
local sq = function(x) return x * x end
local t = { n = 2 }
function t:twice(x) return self.n * x end
print(t:twice(21))   -- 42
-- a function can return several values:
local function mm(a, b) return a, b end

Libraries

math.floor math.ceil math.abs math.sqrt
math.min math.max math.random math.huge math.pi
string.len string.upper string.lower
string.sub(s, i, j)  string.rep(s, n)
string.find(s, part)
string.format("%s %d %.1f %%", "a", 5, 2.25)
table.insert(t, v)   table.insert(t, pos, v)
table.remove(t)      table.concat(t, ", ")
type(v) tostring(v) tonumber(s)
pairs ipairs next error assert print
Not available: io, os, load, require, dofile, debug, coroutines, metatables, goto, varargs (...). Text methods work on literals and variables: ("x"):upper(), s:sub(1, 3).
on(event, function)

Game events#

Register a handler: on("new_year", function(e) ... end). The handler gets one argument, e, a table with the event's details (read them as e.year, e.attacker…). You may register several handlers for the same event; they run in the order registered.

EventWhenFields of e
game_startA new game reaches its first day (not a loaded save), after the start-date wars began and diplomacy is up.none (e is nil)
new_monthThe first day of every month.e.year, e.month (1..12)
new_year1 January (right after new_month).e.year, e.month (1)
war_declaredAny war begins (also the start-date wars).e.attacker, e.defender (country codes)
peaceAny war ends.e.a, e.b (country codes)
new_dayEvery day at midnight.e.year, e.month, e.day
city_capturedA city changes hands in a war.e.city, e.from, e.to, e.capital
province_capturedA province comes under the other side's control.e.province (id), e.name, e.from, e.to
ceasefireA war freezes into a ceasefire.e.a, e.b
truce_brokenA ceasefire breaks and fighting resumes.e.a, e.b, e.breaker
surrenderedA country surrenders (its terms dictated, or the whole country taken).e.winner, e.loser
strikeAn air, missile, nuclear or chemical strike lands (the first of each attacker, target and kind a day).e.by, e.target, e.kind (air, missile, nuclear, chemical), e.lat, e.lon
treaty_signed, treaty_endedAn agreement is signed, or ends.e.kind (Alliance, FreeTrade, NonAggression...), e.a, e.b
joined_org, left_orgA country joins or leaves an organisation.e.org (key), e.name, e.country
sanctions_changedSanctions between two countries change.e.sender, e.target, e.packages (now in force; "" = none)
vote_decidedA vote ends in the UN, the Security Council or a bloc.e.org, e.motion, e.sponsor, e.target, e.passed
state_visitA state visit ends.e.host, e.guest, e.outcome, e.headline
government_changedA country's government changes (election, coup, a victor's puppet).e.country, e.by
event_opened, event_decidedA Situation Room event opens, or is answered.e.id, e.country, e.place; decided: e.choice (1, 2... or "none"), e.risked
economy_alertA crisis notice: recession, default, bank run...e.country, e.kind, e.value
weapon_developedA weapon-tree step or an arms programme is finished.e.country, e.weapon, e.step or e.programme
arms_deliveredAn arms order is delivered.e.country, e.weapon, e.count
space_newsA space launch, arrival, landing, discovery or loss.e.country, e.kind, e.title, e.mission, e.body, e.good
asteroid_impactAn asteroid or debris hits the Earth.e.lat, e.lon, e.megatons, e.ocean
The game's functions

Every function a script can call#

Countries are always named by their code (a text like "POL"); codes are in lists\countries.csv. A new country made by a mod uses the code you gave it.

country(code) → table or nil

The country's figures right now. Returns nil if there is no such country (check it: local c = country("XYZ") if not c then return end).

FieldMeaning
code, name, short, adjectiveIts code, name now, short name, adjective (from a mod; empty if none).
capitalIts capital's name.
gdp, gdpPerHeadGDP in dollars; per person.
populationPeople.
unemployment, inflation, debtShareShares: 0.05 = 5%.
provincesHow many provinces it holds.
atWartrue if it is at war with anyone.
playertrue if it is the player's country.
approvalThe government's approval 0..100 (the player's country only; nil for others).
local de = country("DEU")
print(de.name, de.gdp, de.unemployment * 100 .. "%")

countries() → list of codes

Every country that holds land now. Loop over it with ipairs:

for _, code in ipairs(countries()) do
  local c = country(code)
  if c.debtShare > 1.2 then log(c.name .. " owes more than its GDP") end
end

player() → code

The player's country code (nil before the player picked one).

date() → table

date().year, date().month, date().day: the game's date.

at_war(a, b) → true/false

Whether two countries are at war with each other: if at_war("RUS", "UKR") then ... end.

wars() → list

Every war: a list of tables { a = code, b = code }.

for _, w in ipairs(wars()) do log(w.a .. " fights " .. w.b) end

province(id) → table or nil

{ id, name, owner } of a province (ids in map\provinces.csv): province("UKR-1234").owner.

provinces(code) → list of ids

Every province a country holds now.

city(name [, code]) → table or nil

The first city with that name (in that country, if you give a code): { name, country, population, lat, lon, capital }. city("Gdansk", "POL").population.

fire_event(id, code) → true/false

Opens a Situation Room event for a country now: the game's (ids in events\ids.txt) or your own (give it "chainOnly": true if it should only ever come from your script). Returns false when it cannot happen there.

message(title [, text])

Shows a card to the player and keeps it in Messages, with your mod's name on it.

sign_policy(id, value) → true, false or a reason

The player's government signs a policy change (ids and ranges in policies\ids.txt). It waits and phases in like one the player signs. Returns a text saying why when it cannot be signed now ("The economy is not running yet.").

declare_war(a, b) → true/false

Country a declares war on country b, with diplomacy's normal rules and consequences.

make_peace(a, b) → true/false

Ends the war between a and b; each keeps the ground it holds. False when they are not at war.

add_sanction(sender, target, packages), lift_sanction(sender, target [, packages])

Imposes or lifts sanctions: packages are any of travel arms tech finance energy full ("arms tech"). Without packages, lift_sanction lifts them all.

sign_treaty(kind, a, b [, years]), end_treaty(kind, a, b) → true/false

Signs or ends an agreement: Alliance, FreeTrade, NonAggression, Guarantee, Basing, VisaFree, Research... (the treaty kinds of the Talks tab).

join_org(org, code), leave_org(org, code) → true/false

Takes a country into an organisation, or out of it at once (join_org("NATO", "POL")). Keys as in diplomacy/organisations.csv.

add_opinion(a, b, value, text [, years])

What a thinks of b: a grievance (below 0) or a friendship (-100..100), with its reason, fading over the years given (10 if none).

give_province(id, code) → true/false

Hands a province (ids: map\provinces.csv) to a country, with its people, cities and industry, like a ceded one.

launch_mission(code, kind, world [, name]) → true or the reason it cannot

Orders a space mission the way the game plans any: launch_mission("RUS", "Outpost", "earth", "Mir"). Kinds: Flyby, Orbiter, Entry, Lander, Rover, Aerial, Sample, CrewFlyby, Crewed, Outpost, Solar, Telescope, Impactor, Relay. Real missions with their own dates go in space/missions.csv (the game's list, row by row; a new row's dates are in the scenario's years).

log(text) and print(...)

Write a line to the mods log (Mods log in the Mods window; mods log in the console), with your mod's id in front. Use them to see what your script does.

on(event, function)

Registers a handler (see Game events). Unknown event names are an error.

Copy and change

Worked examples#

A yearly report on the richest countries

on("new_year", function(e)
  local list = {}
  for _, code in ipairs(countries()) do
    table.insert(list, country(code))
  end
  -- find the top three by GDP (simple selection)
  for n = 1, 3 do
    local best, bestI = nil, 0
    for i, c in ipairs(list) do
      if not best or c.gdp > best.gdp then best, bestI = c, i end
    end
    if best then
      log(string.format("%d. %s: $%.0f B", n, best.name, best.gdp / 1e9))
      table.remove(list, bestI)
    end
  end
end)

Rally when the player is attacked

on("war_declared", function(e)
  if e.defender == player() then
    local me = country(player())
    message("The nation rallies", me.name .. " is attacked by " .. country(e.attacker).name .. ".")
    fire_event("pga.rally_round_the_flag", player())
  end
end)

A crisis that grows with debt, once

local fired = {}
on("new_month", function(e)
  for _, code in ipairs(countries()) do
    local c = country(code)
    if c.debtShare > 1.5 and not fired[code] then
      fired[code] = true
      fire_event("pga.debt_crisis", code)
    end
  end
end)

A war that starts in 2027

on("new_month", function(e)
  if e.year == 2027 and e.month == 3 and not at_war("CHN", "TWN") then
    declare_war("CHN", "TWN")
  end
end)
Safety

Limits and errors#

  • Each call (the first run, each handler call) may take up to 200,000 steps. A script that runs longer is stopped with "the script ran too long", and the game goes on. Loop over countries freely; avoid loops in loops over thousands of things.
  • Calls may nest 120 deep (recursion). Deeper is stopped.
  • An error stops only that call. It goes to the mods log with the file and line: rules.lua:12: a number is needed, not nil. The handler stays registered and runs again next time.
  • Common errors: "cannot index nil 'c'" (a function returned nil: check it), "cannot call nil 'contry'" (a typo in a function name), "'end' expected to close 'if'" (a missing end), "cannot compare nil with number" (a field that does not exist).
Steam Workshop

Publish on the Workshop#

  1. In the Mods window, find your local mod and click Publish (it says Update after the first time).
  2. Title (from mod.json's name), description, what changed in this version, and who can see it: Only me (test first), Friends, Link only, Everyone.
  3. Hold Publish. The upload shows its progress. The first time, Steam may ask you to accept the Workshop agreement on the item's page: until you do, the item stays hidden.
  4. The game writes workshop.txt into your folder; keep it so later uploads update the same page.
  5. The tags come from mod.json's tags (only the Workshop's tags are sent), and "Space DLC" when the mod lists the Space pack.

Players subscribe on Steam; the game shows the mod in their Mods window, switched off, after Steam downloads it.

Mods log

Every error message, and what to do#

MessageMeansFix
mod.json line n: …A JSON syntax error.Check quotes, commas and brackets on that line.
"id" is missing / id … may only use a-z…mod.json's id.3–64 small letters, digits, - or _.
unknown field "x"A misspelt mod.json field.Use the names in mod.json.
needs the mod "x"A mod in needs is missing or off.Subscribe and switch it on.
needs the space DLCThe player does not own the pack.—
two mods use the id "x"Two folders share an id.Change one id.
line 1: the first column must be "key"A table's key column is wrong.Use the key column of that table.
… has no column "x"A misspelt column.Copy the header from the reference.
no key "x" in …The row's key does not exist.Copy the key from the reference; to add a city write new:.
"x" is not a numberA number cell with a comma or text.Use a dot: 0.25.
text can only be set (=)* or + on a text column.Write the new text.
rows of … cannot be removed / does not take new rowsremove or new: where the table does not allow it.Only cities take them.
a new city needs …A new city lacks name, lat, lon or population.Fill them.
this column is for information only / owners change in map/owners.csvYou changed an information column.Leave it empty.
no table reads this fileA file name the game does not know.Check the file name and folder.
cannot read the effect "x"An effect word is wrong.See Effect words.
weight: unknown value "x"A formula name is wrong.See Chance formulas.
no lever "x"A policy lever name is wrong.See lists\names.txt.
no country "x"A code that does not exist.See lists\countries.csv; new countries go in map/countries.csv first.
no province "x"A province id that does not exist.See map\provinces.csv.
flag "x": no such file in the modThe flag path is wrong.Paths start at your mod folder: flags/SOV.png.
conflict … set by a, b; b winsTwo mods set the same value (not an error).Order them, or use loadAfter.
Good to know

Rules and limits#

  • Changing the mod list or the language restarts the game's session.
  • Any mod switches achievements off.
  • Mods cannot unlock DLC.
  • Nothing changes the world at once mid-game: mod policies phase in; scripts act through the game's own systems (events, policies, diplomacy).
  • No code: .dll files are never loaded. Lua does that job.
  • Not moddable: province shapes, rivers, coasts, terrain, the satellite picture and adding new tech nodes. The tutorial only starts with no mods running (it teaches the game as it ships).
  • Do not publish other people's work (art, fonts, music) without their permission, or real-world hate content.
Questions

Questions#

My mod does nothing.

Is it switched on and did you Apply & restart? Open the Mods window: does its row say Running and "N values changed"? If 0 values: open the mods log, every skipped line is there with its reason.

Excel changed my file.

Excel may turn codes into dates or use semicolons. Save as "CSV UTF-8" and check the file in Notepad. Or edit CSVs in a text editor.

Where do I see what a value is now?

Reference files writes every table as the running game has it, your mods included. Compare with the shipped reference (unmodded).

Can my mod change another mod?

Yes: list it in loadAfter (so yours applies after) and change the same rows; needs if yours cannot work without it.

How do I test quickly?

Edit your files, then type modsreload in the console: the session restarts with the files as they are now.

Can a script remember something across saves?

No: scripts start fresh after a load. Work it out from the game's state (the date, a country's figures) each time.

Does my mod slow the game?

Tables apply once at start (a city mod adds about half a second). Scripts run only on their events, with a step limit.