Skip to content

Repository files navigation

Victoria 2 campaign analyzer

Point it at a folder of saves from one campaign and it builds a single HTML report: every nation's population, literacy, army, navy, industry, technology and trade over the whole game, a political map with every army on it, and a record of every war that was fought.

Point it at a folder of campaigns instead and it reads them all, works out which mod each was played on, and sets the same nation's run in one game beside its run in another.

Works with vanilla and with mods, including total conversions.


Getting started

Double-click vic2saveanalyzer.exe. One window, two tabs, and a bar along the bottom that joins them:

Tab What it is for
Build the report Turn a folder of saves into the report. This is the one most runs are for.
Keep autosaves Run this while you play and every autosave the game writes is copied out before the game's three-deep rotation drops it. A century of monthly autosaves instead of the last three months. See Keeping every autosave.

The bar at the bottom carries the two things that cross between them: Read the kept saves points the report at the folder the keeper is filling, so that path never has to be carried by hand, and Share a link turns a finished report into a URL — see Sending someone a long campaign.

Building the report

The report tab asks for two folders:

Saves folder One campaign's .v2 files — or the folder all your campaigns live in.
Mod folder The mod's own folder, the one with common/, map/ and history/ inside — or Victoria 2/mod itself, in which case each campaign's mod is worked out from its own saves.

A third box says where the report goes. It defaults to Documents/Victoria 2 Save Analyzer, deliberately not inside your saves folder — that folder belongs to the game.

Then press Analyze. The report opens in your browser.

Pressing it again when nothing has changed does not build anything: the run records what each report was made from -- every save and its timestamp (in every campaign, when the folder holds several), the mod, the settings, the code that reads saves and the code that writes reports -- and if all of that is the same, the report already on disk is the one this run would write. That takes under a tenth of a second instead of a few seconds. --rebuild forces it anyway, and any real change -- a save added, a save touched, a setting moved -- rebuilds on its own. A run that could not finish, such as one that found a table open in Excel, leaves nothing behind to skip on.

On a first run the saves box is already filled in with Documents/Paradox Interactive/Victoria II/save games if that folder exists, and Browse for the mod opens straight in Steam/steamapps/common/Victoria 2/mod. After that all three paths are remembered, so the next run is one click.

The box at the bottom shows progress, and there is a tick to stop it opening the report if you would rather find it yourself.

Stop cancels a run. Saves already being read finish first — they are seconds each — and nothing new is started, so pointing it at a folder of two hundred campaigns by mistake costs you a moment rather than a coffee break. Every save read before you stopped stays cached, so starting again picks up where it left off.

Along the bottom is what that cache currently weighs, and a Wipe cache button. Nothing here expires: an entry is keyed to the version of the program that wrote it, so every update leaves the previous generation behind unreadable rather than replacing it, and a long-lived install ends up holding mostly history. Wiping costs one slow run per campaign and touches neither your saves nor your reports.

Several campaigns at once

Point the saves box at the folder your campaigns live in rather than at one of them, and every campaign underneath is read together. Nothing else has to be filled in: campaigns are found by looking, and each one's mod is worked out from its own saves.

Campaigns grouped into folders of their own are found too — the search goes down through subfolders rather than looking only at the children — so a tidy saves directory reads the same as a flat one.

A Campaigns panel then lists what it found, one row each, with the save count and a mod. What a row starts on follows the mod box above it, which has already answered the question one way or the other: name a mod there and every row starts on the mod picked above, so that is what they are read under; point the box at Victoria 2/mod and every row starts on work it out from the saves.

Either way the rest of your installed mods stay on the list, which is the point of a row per campaign: move the one campaign that was played on something else onto it, or send a single row back to being worked out while the others keep the mod you named. Settle the ones you care about and leave the rest — the two are not exclusive.

Doing that is worth knowing about, because two mods built on the same base can agree on every country, every technology and every invention a save records, leaving little to tell them apart. Getting it wrong is not cosmetic: mods that close agree that closely still disagree about unit stats, so the ships in the report would be rated against the wrong rulebook.

The marked row is the main campaign — the one the map, wars and technology tree are about, since those can only be about one game. It starts on whichever has the most saves. The comparison itself covers all of them whichever you mark.

Two campaigns' saves in one folder is the one mistake worth catching, and it is: the global event flags a save carries accumulate as a game runs, so an earlier save's flags are a later save's past, and one holding flags its successors never had is named in the log as possibly from another game.

About the mod folder

Give it one. Without it the report loses the map, the technology tree, the great power ranking and the war goals, and every nation is named by its three-letter tag instead of its real name. The analyzer reads the mod for country names, nation colours, the province map, the tech tree, pop types and the rules behind the mobilization numbers, so a report built without it is a much thinner thing.

For an unmodded game, point it at the Victoria 2 install folder itself.

Reading several campaigns does not change this: name one mod and every campaign under the folder is read under it, which is what you want when they were all played on the same one.

You can also decline to choose. Point the box at Victoria 2/mod — the folder mods live in rather than a mod — and each campaign is matched to one by elimination. A country tag the folder has never heard of, a pop type it does not define, a technology name it lacks, or an invention index past the end of the array it builds each rule it out, because a save cannot hold a name the folder that made it never defined.

The map settles what those cannot, and it settles it by identity rather than by size: a save carries every province the map defines, so the two are the same set or the save did not come from that folder. This is what separates mods sharing a base, where nothing else does. Two PUIR derivatives here agree on all 309 countries, all 150 technologies and all 566 inventions in the same order, and differ by three provinces.

It is worth getting right rather than nearly right. Those same two mods give the cruiser different guns, different evasion and different torpedoes, so reading a campaign under the wrong one of them rates every cruiser 18% low and invents torpedoes it never had. A wrong answer here does not mislabel the report; it changes the numbers in it.

Where several still fit, the one whose own country list the campaign comes closest to exhausting is taken, and the log says the answer was not forced. If nothing fits at all — usually the right mod at a different version — the closest is named, loudly, as not matching. Both are judgements rather than measurements: naming the mod yourself settles it, and doing so reads every campaign in the folder under that one mod.

A mod is read the way the game reads one: file by file, with the mod's copy winning and anything it does not ship taken from the Victoria 2 install underneath. That matters for the partial ones — Divergences of Darkness names 599 of its 658 countries and leaves the rest to the base game, and CE 1v1 ships two localisation files and no pop types at all. It is also where the map and most of the flags come from: a mod rarely ships either.

A mod inside Victoria 2/mod/, where the game keeps it, is read on that install. A copy kept anywhere else, such as a mod unpacked into Downloads, is read on the Victoria 2 this machine has installed through Steam, found in Steam's usual places on Windows, Linux and macOS, including a library on another drive. The log says which install it used. --game-root names one instead: an install bought somewhere other than Steam, or a second copy. With no game found at all, the report has no map and only the flags the mod ships itself.

Saves must be plaintext

Victoria 2 can write compressed saves. If you get "does not look like a plaintext Vic2 save", launch the game in debug mode and re-save. The file gets roughly ten times bigger but becomes readable. Turning debug mode back off and saving again returns it to normal size.

How long it takes

Measured on 103 monthly saves — 3.3 GB:

16 cores 4 cores 2 cores
first run, nothing remembered 3.9 s 5.3 s 8.4 s
again, every save already read 2.0 s 2.0 s 2.0 s
again, nothing changed at all 0.06 s 0.06 s 0.06 s

A thousand monthly saves — 2.4 GB, which is what a century of autosaves looks like — take 12 s the first time, 10 s after that, and 0.06 s when nothing has changed. The cost grows with the number of saves and not faster than that.

What a save was read as is remembered between runs, so only new or changed saves cost anything, and a run with nothing to do hands back the report already sitting there — which is the case that matters, because it is the one you hit every time you press the button twice.

Saves are read on every core the machine can spare, though not to much effect past eight of them: reading a campaign runs out of memory bandwidth before it runs out of cores.

The report is written before the CSV tables are and opens as soon as it exists, so it is on screen before the run has finished.


Using the report

The report is one HTML file. It needs no server and no internet: open it in any browser, keep it, mail it, put it on a stick.

Eight tabs across the top, and they share five habits worth knowing:

Nation pickers. Anything comparing nations uses a searchable dropdown rather than a wall of tags. Type to filter by tag or by name, or use the presets:

  • Players — every nation somebody was playing. Each save marks them itself, so a multiplayer game names all of its players and not only whoever pressed save. Absent from single-player campaigns, where it would be one nation.
  • Great powers — the eight the game itself ranks, at the save being viewed
  • Top 8 by pop
  • All / None

Save pickers. Where a section shows one moment rather than a trend, there is a Save dropdown beside it. The nation picker next to it then offers only the nations that exist in that save — twenty-one in 1908 rather than the hundred and twenty-six the campaign has seen. Step back to 1836 and they all come back.

Sorting. Click any column heading. Click again to reverse it.

Names. Goods, unit types, pop types, technologies and cultures are named the way the mod names them rather than by the key the save stores. IGoR's cattle is Livestock and its aristocrats are Landowners; Divergences of Darkness has no Post-Napoleonic Thought but does have Post-Wenceslian Thought. Without a mod folder there is nothing to read the names out of, so the keys stand in, tidied.

Charts carry a mark at each end of a line and nothing in between, and rule the background at round years rather than at each save. A campaign saved by hand thirty times and one autosaved every month therefore draw the same chart, rather than the second one burying its own lines under nine hundred gridlines and four thousand point markers.


The tabs

Nations

Opens on the deployment map: the political map of the selected save with every army drawn where the game stacks it, sized by brigade count and coloured by its nation.

  • Hover a marker for the province, the nations stacked there, and what they are made of — "Bern, 113 brigades, KUK 85 artillery 71 hussar 14, FRA 28 artillery 28".
  • Click a marker to pin its readout while you look elsewhere.
  • Scroll to zoom on the cursor, drag to pan, double-click to zoom in, Reset for the whole world.
  • Play, and the slider beside it, walk the map through every save in order, so a campaign plays out rather than being read one date at a time. The button beside Play sets the pace — 1×, 2×, 5× — which is the difference between watching a thirty-save campaign and watching a nine-hundred-save one. Drag the slider to move by hand; leaving the tab stops it.
  • Land is shaded by whoever owns it. Ground somebody else is holding is hatched diagonally in the occupier's colour on top, so a siege and an annexation stop looking the same. The stripes never cross a province border, and they cover a third of the ground rather than half, so the owner's colour stays the one the eye lands on. Occupation turns the hatching off.
  • Hover any land and the readout names the province, the nation that owns it, and the nation holding it if that is somebody else. No pattern can say which of two nations owns the ground under a stripe; this can.
  • Narrowing the nation picker strips the map back to the armies you care about. The land stays shaded, so the geography does not move under you.

Below it, the great powers of that save in the game's own rank order, laid out as the game lays them out — first to fourth down the left, fifth to eighth down the right — each with its flag, prestige, craftsmen, factories, brigades and ships.

Compare

Data visualizer: any measure plotted over the whole campaign for the nations you pick. Population, literacy, brigades, ships, factories, prestige, treasury, infamy and about twenty more. Linear/Log switches the vertical scale, which is what makes a small nation and a great power readable on one chart.

Literacy in own states sits beside average literacy and leaves the colonies out. A colony's pops are counted in the national average and drag it down without saying anything about the metropole: in one campaign here Italy reads 46.9% overall and 97.4% at home. It is the same restriction the soldier measure in the Military tab uses, so the two agree about what "our own states" means.

Four of the measures are not quantities but changes in one. Population growth and accepted-culture growth are rates, as a percentage a year, compounded between one save and the one before it. A nation that grew 3% over six months plots at about 6%. Saves less than three months apart are measured across the gap to the next one far enough away instead, because a fortnight of ordinary growth annualises into hundreds of percent and would bury everything else on the chart. Conquest moves population as surely as births do, so a spike is usually a border moving.

Population gain and accepted-culture gain are the same two as counts: how many people, from one save to the next. A rate flatters a small nation -- three thousand people on a hundred thousand outruns a million on eighty million -- and the two orderings disagree, which is the point of having both. In one campaign Burgundy adds 685,000 at 2.4% a year while China adds 400,000 at 1.2%.

A dashed line joins a nation to the one it became, so Prussia's line runs into the North German Federation's and that into Germany's rather than three lines starting and stopping in mid-air.

Below it, Standing: the closing table at the final save for the nations you have selected.

Cross-campaign comparison

Last in the tab, and present only when several campaigns were read together. One nation, one measure, one line per game: how did Italy fare in this campaign against the last one? Every measure the data visualizer offers is here, drawn from the same numbers, so they mean what they mean above.

Only nations that turn up in at least two of the campaigns are offered — a nation in one game has nothing to be set beside. There can be a great many of them, so the picker carries the same search box the Pops, Cultures and Technology pickers do. Whether there are any depends on the mods: two builds of the same total conversion share almost all their countries, while two unrelated total conversions share none at all and the section has nothing to say.

Calendar years / Campaign years decides what the horizontal axis means. On calendar years each line sits where it actually happened, so two games overlap only where they really did. On campaign years every line starts at zero, which is the fair way to ask how two runs developed, since campaigns rarely begin on the same date or run the same length. Under the chart, the last moment every campaign still covers is stated with each one's value there — comparing the closing figures instead would be comparing 1894 against 1874.

Hovering reads every campaign at once. They save on different days, so a value marked · is that campaign's most recent reading at or before the line rather than one taken on it.

A few measures are marked †. Those are not read off a save: they are worked out from a mod's own technologies, inventions and modifiers, so two campaigns on two mods answer them from different rulebooks. They are still worth asking about — a mobilization ceiling is a real fact about a game — so they are shown with that said plainly rather than left out.

Pops

Pops at ⟨save⟩ — every nation with its population, accepted-culture share, literacy, militancy, consciousness, and a column per pop type. The Counts/Shares button switches the pop-type columns between head counts and percentages, which is the difference between "who has the most craftsmen" and "who is the most industrialised".

Click any row to load that nation into the two sections below.

Cultures — every culture in one nation at one save, with its size, its share of the population, and whether the nation accepts it. Cultures are named the way the mod names them, not by the key the save stores: IGoR's China is South Han and North Han, not nanfaren and beifaren. Without a mod folder there is nothing to read the names out of, so the keys stand in.

Population composition — one nation's pop types stacked over the campaign, one bar a year taken from the last save in each. A year is the unit a population is read in, and it is what keeps the chart the same whether the campaign was saved thirty times or every month.

Military

Head to head at ⟨save⟩ compares two sides. Each side takes a group of nations, so USA and Austria-Hungary against Germany is a single comparison.

  • Totals — one pie, one slice per side. Hover a side to break its number down by member nation.
  • Composition — two pies split by unit type. Hover a slice and both sides' counts for that type are reported together with the ratio between them.
  • Army/Navy switches both views between land regiments and naval hulls.
  • With mobilization adds each nation's mobilization ceiling to its brigade count, which is the honest measure of what a nation could put in the field rather than what it has standing today.

Fleet power

Hulls are not interchangeable: five frigates are not a cruiser. In Navy mode each side also carries a fleet power, which sits beside the ship count rather than replacing it — the two disagreeing is the interesting part.

One hull is worth

gun power × hull ÷ (1 − evasion)

which is what falls out of asking who out-damages whom. Two ships trading fire deal damage in proportion to their own gun power and in inverse proportion to the other's hull, and evasion throws away a share of the ticks aimed at it; move each ship's terms to its own side of that comparison and this is what is left. It adds up over a fleet, so a side's total is a number you can divide. The reading is Boltun's, from his guide to navies, and it reproduces his figures exactly: a frigate at 16 per naval point, a man-o'-war at the same, both before any technology.

Stats come from the mod's own units/ files and are upgraded by the inventions each nation actually rolled, so the same hull type is worth more to a nation that researched further — in one campaign here Britain's battleship reads 4,453 and Germany's 4,200. Torpedoes work only against big ships, so where any are carried a second figure is given for a fight against heavy hulls. Composition adds what one hull of each type is worth to each side, and each side's total under its pie. Without a mod folder there are no unit files and the measure is unavailable.

That figure rates the ships as designed, at full strength and no experience, which is what makes it comparable between nations and across a campaign. Underneath it, at current strength is the same fleet as the save finds it: the damage a hull deals runs with its strength, and the damage it takes runs against its experience, so a battered fleet fights below its paper figure and a veteran one above. It matters more than it sounds — in the campaigns here it moves individual navies by anywhere from −34% to +25%, and changes who ranks where. Both are shown rather than one replacing the other, so a fleet somebody has stopped paying for still shows what it would be worth repaired.

Below that, a sortable overview of the nations you select — brigades, ships, army and navy tech counts, soldier pops, soldier pops as a share of population, and the same share counting only soldiers in the nation's own states. A colonial soldier pop raises no brigade, so that last column is the part of the soldier base an army can actually be built on: Britain reads 3.98% on the plain measure and 1.82% on this one. Then a technology matrix listing every army and navy tech in research order with a column per nation. Those tables scroll sideways with the label column pinned, because a column per nation is the point of them.

Fleets

Three views. Compare navies plots one hull type, or all ships, across nations over time; the axis covers the years anybody actually had that hull, so a battleship chart starts at the first one launched instead of spending two thirds of its width on the decades before the type existed. Fleets at ⟨save⟩ is a table of every navy with a column per hull type. Fleet composition stacks one nation's hull types over the campaign, one bar a year. Hull types are read from the save and named the way the mod names them, so mod-added ships appear under their own names.

Wars

Every war in the campaign. The table lists who fought whom, when it started and ended, total casualties, how many battles, how many states changed hands, and how many of its war goals were met. Search by war name or by any nation in it.

Click a war for the detail. It opens as an infobox, the way an encyclopedia sets one out, with the war goals and battles beside it:

  • The name in a box at the top, and under it the flags of each side's principal belligerents facing each other: its war leader, and any great power fighting for it. A mod with no game beneath it has almost no flags, so a nation without one flies its own colour with its tag on it.
  • Date, with how long the war lasted. Result: whether each side's war goals were taken at the peace, as far as the saves can tell. A war that was over before the first save, or is still being fought, is not given a verdict. Territorial changes: every state that changed hands. Battles: how many on land and at sea, how many each side won, and the places fought over most.
  • Belligerents in two columns, defenders left and attackers right, as in the battle tables. The war leader and the great powers come first with large flags, each marked as such with its rank. Nations that joined later are listed apart with the date they joined. A nation knocked out before the end (a separate peace, or annexation) also shows the date it left, which the war's own dates do not say. The engine removes everyone when a war ends, so only an exit earlier than that is shown: of 18,657 recorded removals in one campaign, 17,964 were simply the war finishing.
  • Commanders and leaders: every general and admiral the battles name, on the side they fought for, most battles first.
  • Strength: each nation's brigades and ships at the last save before the war, or at the first save taken during it. Great-power ranks come from the same save. A war fought before the first save has neither.
  • Casualties and losses: what each nation lost in the battles it fought, with each side's total.
  • War goals: every demand the war carried, meaning the one it opened with plus any added while it ran. Taken at the peace compares who held the state before the war against who held it after. Occupied mid-war is a different thing and says so: the claimant held the state by siege at the moment a save was taken, which is a condition during the war, not an outcome.
  • Land battles and naval battles, separately, each sortable by date, by name, or by either side's losses. Every battle shows both commanders and what each side was made of.

Nations are named even when they were gone before the first save, such as Baden or the North German Federation, as long as the mod names them.

Battles carry dates where any save still remembered them; a save keeps dates only on its most recent battles, so older ones are marked undated rather than guessed at.

Technology

The mod's own technology tree for one nation at one save, in the game's five categories, each showing how many of its technologies that nation has. Click any technology for its bonuses and the inventions it unlocks.

Market

Price history for every good over the campaign — real monthly history, not one reading per save, because each save carries three years of it.

Below that, the market snapshot: supply, demand, how each good's price compares with its base price, and two columns about gluts. Unsold is the share of that day's supply that found no buyer at all. Pegged marks a good whose recorded demand runs to something on the order of a billion — a standing order to buy without limit, which some mods hand a nation so that raw materials always sell. Every pegged reading in the campaigns this was checked against sits at exactly five times the good's base cost, the engine's price ceiling, so for those goods neither price nor demand reports on scarcity and only Unsold still does.

Last, Who produces it: for one good at one save, every nation's own contribution to the world supply, biggest first. Summed over every nation it comes back to the market's supply pool, so these are shares of the same quantity the price responds to. That is what names the nations behind an overproduced good. Clicking a row in the table above brings that good's producers up here.


Keeping every autosave

Victoria 2 keeps three. It writes autosave.v2, shifts the last one to oldautosave.v2, shifts that to olderautosave.v2, and drops whatever falls off the end. On monthly autosaves that is a three-month memory of a sixty-four-year game, and there is no setting for it — the depth is not configurable and never was.

The Keep autosaves tab watches the game's save folder while you play and copies each autosave out before the rotation reaches it, into a folder per campaign, named for the date inside the save. Press Start before you play and leave the window open.

It follows a nation through its own history rather than its tag. Sardinia forms Italy and every save after it says ITA, so sorting by tag would cut one campaign in half at the moment it got interesting; campaigns are matched on the event flags a save carries instead, and the folder is retitled SWE-SCA 1836 when a nation is formed. Two runs as the same country from the same start date stay in separate folders for the same reason.

It keeps up with the game, too. Windows reports the save folder changing rather than the folder being polled, so a copy starts within milliseconds: at speed five three autosaves rotate past in a couple of seconds, and a polling version of this lost 59 months of a 1836–1900 campaign.

Keep one save every N months is the lever for how much disk this costs. Every month for a century is about 18 GB for one campaign; every third month is about 6 GB and still four times denser than the game's own yearly autosave. If the machine feels heavy while a long campaign runs, exclude the folder from Windows Search indexing and from your antivirus's real-time scanning — both otherwise read every one of those gigabytes as it lands.


What you get

Everything lands in the output folder. The report is the point; the CSVs are there for when you want to do your own arithmetic.

File Contents
report.html The report. Open it in a browser.
nations_timeseries.csv One row per nation per save. The main table.
prices.csv Monthly world price per good.
market_snapshot.csv Supply, demand and quantity sold per good, per save.
pops_by_type.csv Long format: date, tag, pop type, size.
pops_by_culture.csv Long format, with whether the culture was accepted.
ships_by_type.csv Long format: date, tag, ship type, count, and the same ships at the strength the save finds them in.
brigades_by_type.csv Long format: date, tag, regiment type, count.
technologies.csv One row per nation per technology per save.

The report is one file with everything in it -- the map, the flags, every number -- and it is small enough to send: a hundred monthly saves come to about 1.6 MB. The data inside it is compressed, so any browser from 2023 onwards will open it and older ones will say so rather than showing you an empty page. Nothing needs to be installed and it does not need a web server; open it off the disk. Everything is also on window.campaign if you would rather read it out of the console.

Sending someone a long campaign

A campaign autosaved every month for a century is a different proposition: the same report comes to about 13 MB, which is past what most chat clients will take and an awkward thing to attach to anything. It is still one file and it still opens off a disk, but at that size it wants a link rather than a copy.

--split writes the payload beside the page instead of inside it:

report.html        a few hundred KB -- the page itself
report.data.gz     the campaign

Two files, together about a quarter smaller than the one, because the data no longer pays the third that base64 costs to sit inside HTML. The page loads first and inflates the data as it arrives, so a long campaign stops looking like a browser that has hung.

Share in the bottom bar offers three ways, and the first one needs nothing from anybody:

Show me the file to send Opens the folder, puts the path on your clipboard and tells you the size, with a word about whether it will fit through Discord or mail. For a report of a few dozen saves this is the whole answer.
Upload to a report host One click, no account, a link back — if there is a host to send it to. There is no host set when you install this, because a host is somebody's server and nobody should be signed up to running one by a default. host/ holds one ready to deploy.
Publish to GitHub Pages Puts the report in a repository of your own and turns Pages on, so the link is yours: it keeps working, you can delete it, and nobody else's server holds your campaign. Wants a GitHub token once.

The reason there is no single obvious answer is worth stating, because it looks like an oversight and is not. The free file hosts that take a 20 MB upload without an account nearly all serve an HTML file as plain text or as a download, deliberately — a host that renders strangers' HTML is a phishing site with extra steps. So a link that really opens as a page has to point either somewhere the reader owns, or somewhere somebody chose to run. Both are here, and neither is imposed.

The catch with the split pair is that a browser will not fetch a file sitting next to a page opened from a plain disk path, so the two have to be served. Anything that serves static files will do, and the free one most people already have is GitHub Pages: put both files in a repository, turn Pages on in its settings, and the report is a URL you can give anybody. The page says so itself if it is opened the wrong way, rather than failing silently.

Without --split nothing changes: one file, openable off a disk, which is still the right answer for a campaign of a few dozen saves.


Running it from a terminal

The executable works as a command-line tool when given arguments, and vic2_analyzer.py does the same with Python 3.8 or newer and the scanner built (below). No other dependencies.

vic2saveanalyzer.exe "C:\path\to\saves" --mod-path "C:\path\to\mod"
python3 vic2_analyzer.py "~/Documents/Paradox Interactive/Victoria2/save games"

Useful flags:

Flag What it does
-o, --out Output folder (default vic2_report)
--mod-path The mod or game folder
--game-root The Victoria 2 install a mod kept outside it runs on, when it is not the one Steam installed
--tags ENG FRA … Only keep these nations
--min-pop 500000 Drop microstates
--no-html CSVs only
-j 1 Read saves one at a time instead of on every core
--no-cache Re-read every save from scratch
--map-scale N Map resolution: 2 is the default, 1 is sharper and bigger, 5 is small and blocky
--peek Print the structure of a save and stop
--verify Cross-check the brigades, the ships and the population against a raw scan of the file
--split Write the data beside the page rather than inside it, for hosting: see below
--rebuild Build the report again even when nothing has changed
-q Quiet

There are a few more for interrogating the mobilization numbers — --explain-mob TAG and --explain-mob-pool TAG print exactly which technologies, inventions and modifiers produced a nation's figures.

Two more read the inventions themselves. --inventions TAG prints every invention the last save says that nation has discovered, which is a list you can hold against the game's own technology screen. --check-inventions takes a folder of saves instead and grades the decode against those saves rather than against the mod folder: an invention gated behind a technology should be held almost only by nations that have that technology, so its holders fingerprint the gate it came through and a campaign can mark its own reading. --help lists everything.

Four more read a folder of campaigns rather than one. --cross treats the saves path as a folder of campaign folders; --game-root says where the mods live, so each campaign can be matched to one; --campaign-mod "NAME=PATH" names the mod for one campaign outright and can be repeated, beating --mod-path for the campaigns it names, with anything unnamed still worked out; --primary NAME picks which campaign the rest of the report is about, instead of whichever has the most saves.

vic2saveanalyzer.exe "C:\path\to\campaigns" --cross --game-root "C:\path\to\Victoria 2"

Saves are ordered by their in-game date, so filenames do not matter.

Multiplayer

Every country somebody is playing carries a human = yes marker in its own block of the save, so a multiplayer campaign is read without being told anything: the Players preset in each nation picker and the Player column in the standing table both come from it, and so does the mobilization bonus an uncivilized nation run by a human gets and an AI one does not.

--player-nations TAG TAG overrides that list, for a save whose markers are missing or a campaign where somebody handed a nation over.


Accuracy

Where the game's own numbers can be read out of a save, they are: prestige, the great power ranking, unit counts, populations, prices, war casualties. Where they cannot, the rule was measured against the game rather than guessed, and checked against readings from a hundred test nations plus several campaigns.

Five things worth knowing:

  • Mobilization ceilings are computed, not stored. They match the game exactly in nearly every case tested; the misses are single-brigade rounding at the boundary.

  • The brigade cap — how many standing brigades a nation's soldier pops could support — is computed the same way, and was measured rather than guessed. A soldier pop raises nothing below POP_MIN_SIZE_FOR_REGIMENT and 1 + size / POP_SIZE_PER_REGIMENT at or above it. Two things about that are worth stating because the define names suggest otherwise. The minimum is a gate, never a deduction: a pop of exactly 1,000 raises one brigade and a pop of 3,000 raises two, where subtracting the minimum first would give one. And the colony, non-core and protectorate multipliers scale the step rather than the gate — a colonial pop of 1,000 still raises a brigade. Culture makes no difference at all: pops of a culture the nation rejects were measured raising exactly as many as accepted ones, at every size tested.

    It was settled on two purpose-built test beds, one per mod, each of 72 provinces given a single soldier pop of a known size across sizes either side of every step. The first, on IGoR, covered core, non-core, colonial and non-accepted-culture pops; the second, on Modus Omnino Demens, was needed because IGoR sets its non-core multiplier to 1 and so cannot tell whether a multiplier gates anything. Between them the four multipliers 1, 3, 5 and 8 were all exercised, and all 144 cells agreed with the rule above — including that a colony is charged its own multiplier rather than the colonial and non-core ones compounded, and that is_colonial=1 is the protectorate the defines name.

    Finished campaigns cannot answer any of this: a regiment is not disbanded when its pop shrinks, so counts read from a played save describe the pop's history rather than its capacity, and 38.8% of pops in one late save were holding more brigades than any formula allows. That is also why the reported cap is never below the brigades already standing — a nation over that line is not held to a smaller army, it simply cannot recruit a further one. As an independent check, the great powers in that same save sit at 95–99% of the computed cap.

  • Mobilisation size adds up every source the mod has: technologies, inventions, national values, reforms, event modifiers, the flat penalty an uncivilized nation carries, and triggered modifiers — which are re-evaluated by the game every day and written into the save nowhere, so their conditions have to be read and judged. Where a condition is one the analyzer cannot answer, the modifier is left out rather than guessed at, and --explain-mob TAG names which ones those were along with every source that did count.

  • Inventions are stored as bare numbers — positions in an array the engine builds when it loads and writes down nowhere. That array is rebuilt by reading the mod's inventions/ folder the way the engine reads it, and the result was confirmed against the game: inventions watched as they fired in a live campaign decoded to their exact names, out of index order. Each campaign also grades its own decode without the mod folder having to be trusted, which is what --check-inventions reports.

  • Battle dates are recovered by reading the whole folder, which dates about 90% of battles. The rest are shown as undated rather than filled in with a guess.

If you want the whole story — how the mobilization rule was measured, which models failed, what is still not modelled — it is in INTERNALS.md.


The Rust scanner

Every run is made by scanner/, in Rust: finding the saves, settling the game and the mod, reading every save on every core, the report, the tables, the diagnostics and --cross. The Python is the window, the keeper and the command line, which hand the run to the scanner and pass on what it says. From source, build it once:

cargo build --release --manifest-path scanner/Cargo.toml

No dependencies, so that works on a machine that has never talked to crates.io. Without it a run is refused with that command. build_exe.py requires the binary and carries it inside the executable.

The checks in testkit/ hold it to answers recorded for every case they run -- what was printed, the exit status, every table and what the page carries -- most of them taken from the Python the scanner replaced, which checked out the same, while it was still here to ask:

python3 testkit/all.py "/path/to/saves" --mod "/path/to/mod"
python3 testkit/all.py --update-expected     # after a deliberate change

Building the executable

cargo build --release --manifest-path scanner/Cargo.toml
python build_exe.py

Needs PyInstaller (pip install pyinstaller). Writes dist/vic2saveanalyzer.exe, about 13 MB, no Python needed on the machine that runs it.

Files

app.py The executable: the window, or the command line when given arguments
gui.py The window: the folder pickers and the log box
keeper.py, keeper_gui.py Keeping every autosave, and its tab
publish.py, settings.py Sharing a report, and what the window remembers
vic2_analyzer.py The command line: hands the run to the scanner and relays what it says
run.py Every setting of a run, declared once, and the command line that fills them
fastscan.py Where the scanner is, and how to start it with no window
readfolder.py The Stop button and progress bar, and the scanner's cache folder
mod_reader.py Which folder is a game install, and where a mod may be read from
cross.py Which folders under a folder hold campaigns
savehead.py, dates.py What the head of a save says, for the keeper
scanner/ The Rust that makes every run: reading, the report, the tables
template.py The report's HTML, CSS and JavaScript, built into the scanner
build_exe.py Draws the icon and packs the executable

Licence

GNU Affero General Public License v3.0. The full text is in LICENSE.

In plain terms, you may use this for anything, change it, and pass it on. What you may not do is take it private. If you distribute a modified version, or run one as a service other people use, you have to give them the source under this same licence.

That is the deal in both directions: nobody can wall this off behind a paywall or a login and keep the improvements to themselves, and by the same token anything you build on it stays available to everyone who comes after. Selling copies is not forbidden and never has been under this kind of licence — it is simply futile, because whoever buys one gets the source with it and may give it away.

A note on game files

The analyzer ships no Victoria 2 content. It reads the game and mod files already on your machine, and the reports it generates embed things drawn from them — nation flags, the province map, country and state names. Those belong to Paradox Interactive and to the mod's authors, not to this project and not to you. Keep that in mind before publishing a generated report somewhere public; sharing one with the people you played the campaign with is a different matter entirely.

About

Turns a folder of Victoria 2 saves into one HTML report: every nation's population, industry, military, technology and wars across a whole campaign. Reads vanilla and mods alike.

Topics

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages