A pre-alpha, server-authoritative grand-strategy sandbox inspired by Victoria II.
Victoria-Like is an open-source simulation about industrial society: POPs earn wages, buy goods, suffer shortages, switch jobs, and create political pressure through material conditions. The .NET server owns all world state; Unity presents it; the codebase is structured so you can read, run, and modify the simulation without reverse-engineering it.
Status: See docs/current_status.md.
The project is inspired by the GVGOAT, Victoria 2:
- POPs are the heart of society. Income, needs, literacy, militancy, consciousness, and promotion/demotion all run per POP group.
- Politics emerges from material conditions. Reform pressure is a downstream metric of unmet needs and POP attitudes, not a hand-scripted event.
- Markets, production, war, and budgets are linked and endogenous. A factory shortage moves prices, which moves POP cash, which moves militancy, which moves reform pressure.
Additional principles:
- The simulation is meant to be explainable. Server-side explain/preview endpoints exist so the question "why did this price change?" has an answer.
Unlike Victoria II, though:
- This architecture is online-native Server-authoritative, deterministic, snapshot-recoverable, command-validated, WebSocket-broadcastable. Victoria II was a beloved single-player game; this is built like a small live service.
See docs/for-victoria-2-fans.md for more.
- Server-authoritative fixed-tick simulation (1 in-game day per tick).
- Persistence + restart recovery on PostgreSQL, Redis health checks.
- REST API + WebSocket updates + admin and explanation endpoints.
- Three scenarios shipped:
tiny-2country,phase1-albion-server, andmedium-8country. - xUnit coverage of loaders, simulation stages, command handling, persistence, explanation, and invariants.
- NBomber + fake-client soak/load harnesses.
- Unity v2 inspection UI for countries, provinces, POPs, market prices, treasury, tax rate, RGO output, and factories.
- POPs with needs, purchasing, employment, unemployment, literacy, militancy, consciousness, and promotion/demotion.
- RGO, factory, and artisan production feeding national markets.
- Market prices, shortages, taxation, budget spending, treasury changes.
What's not ready: full historical scenarios, deep diplomacy / spheres / crises, polished Unity UX, balanced economy, and the WebSocket integration on the client side (the server broadcasts; the Unity client still polls REST for most views). See docs/current_status.md and the trackers under docs/status/.
Prerequisites
- .NET SDK 10.0.106 or compatible feature roll-forward
- Docker with Docker Compose
- (Optional) Unity 2023 LTS for the client — server is fully usable via
curlwithout it
Run the Albion demo scenario
make up # start PostgreSQL + Redis
make run-albion # reset world, load Phase 1 Albion demo, start tickingThe server listens on http://localhost:5001. In another terminal:
curl http://localhost:5001/health
curl http://localhost:5001/api/world/countries
curl http://localhost:5001/api/world/provincesOther ready-to-run scenarios: make run-tiny (12-province test) and make run-medium (8-country stress slice).
Stop everything
make downFull setup walkthrough: docs/quickstart.md. Server-side REST/WebSocket reference: server/README.md.
dotnet test server/VictoriaLike.Server.slnTests are pure C# — they do not require a running server, Docker, or any network. Touching the network from a test is against the architecture rules (docs/architecture.md).
The Phase 1 server demo scenario is server/content/scenarios/phase1-albion-server.json: one playable country, a few provinces, staple goods, industrial inputs, POP groups, stockpiles, and visible economic pressure. It is intentionally small — the goal is to show the bones of the simulation, not to ship a finished game. See docs/demo_script.md for the demo flow.\
make up pulls and starts Postgres and Redis via Docker Compose, make test-connections confirms both are reachable, and make run-albion resets the world, seeds the Albion scenario, and starts the fixed-tick loop, ending on Now listening on: http://0.0.0.0:5001. Raw cast: demo_server_setup.cast.
Killing and restarting the server process — without a world reset — proves persistence: it logs World restored from snapshot: tick 875 and picks the simulation back up exactly where it left off. The second half resets into make run-medium to show the same server on the larger 8-country scenario. Raw cast: demo_server_resume.cast.
curl /health, then curl /api/world/countries | jq, then watch -n 1 "curl -s .../countries | jq ." — the treasury moves every tick as POP wages, taxes, and spending settle. Raw cast: demo_second_terminal.cast.
A tour of the read-only world endpoints: /api/world/summary, /countries, /provinces, /market (per-good price/supply/demand), and /events — the server's own auto-generated alerts for a treasury deficit, a fish shortage, rising unemployment, and militancy — plus /buildings/queue, /armies, and /wars. Raw cast: demo_second_terminal_world_state.cast.
/api/admin/summary (tick timing, health checks, snapshot history), /api/admin/market, /api/admin/tick-profile, and /api/admin/countries/{id} — the operational view used for debugging the simulation rather than playing it. Raw cast: demo_second_terminal_admin_views.cast.
/api/explain/good/grain, /api/explain/good/iron, /api/explain/country/{id}/budget, and /api/explain/province/{id}/employment — each returns a factors list (supply vs. demand, price pressure, tax rates, spending) backing the headline number. This is the "why did this change?" answer the architecture is built to support — see Preview is not authority. Raw cast: demo_second_terminal_explain_endpoints.cast.
Same explain API, scoped to one POP group: grab a province ID, pull a popId out of /api/world/provinces/{id}/inspect, then call /api/explain/pop/{id}/needs. Raw cast: demo_second_terminal_explain_endpoints_pop_needs.cast.
Grab a country and province ID from the list endpoints, then drill in: /countries/{id}/inspect, /provinces/{id}, /provinces/{id}/inspect, /countries/{id}/budget-preview, and /provinces/{id}/construction-options. Preview endpoints are read-only — they never mutate world state. Raw cast: demo_second_terminal_grab_ids_then_inspect.cast.
The Unity v2 client is presentation-only: it inspects the server-owned Albion world state over REST while the simulation ticks. Sorry for not just using a screen recording. Source movie: demo_unity_client.mov.
server/
src/
VictoriaLike.Core/ # Deterministic simulation and domain logic (pure C#)
VictoriaLike.Server/ # ASP.NET Core API, WebSocket, persistence, auth, health
tests/
VictoriaLike.Core.Tests/ # xUnit simulation and server-adjacent tests
VictoriaLike.LoadTest/ # Fake-client harness
VictoriaLike.NBomberLoadTest/
content/ # Scenarios, goods, balance CSVs
client-unity/v2/ # Current Unity client (inspection-first)
docs/ # Architecture, scope, status, design, roadmap, modding
devlog/ # Curated development journal (history, not docs)
reviews/ # Technical audits and test reports
infra/ # Local infrastructure and soak scripts
scripts/ # Deployment helpers (see docs/deployment/)
The server is the single source of truth. Unity is presentation and input only.
- No authoritative gameplay logic on the client.
- Every player action becomes a server-validated command.
- Preview/explain endpoints are advisory — they cannot mutate world state.
- Simulation is deterministic: same seed + command replay = same world state.
- Randomness is seeded and logged via Serilog; no hidden RNG.
VictoriaLike.Coreis pure C# — tests must not touch the network.- Durable truth is refreshable state — anything a player must see after reconnect lives in server state or a fetchable DTO.
Full rules: docs/architecture.md.
You don't need to understand the whole engine to help. Good first contributions include scenario data, goods/building content, focused simulation tests, Unity UI polish, balance notes, and explanation text.
Start here:
- CONTRIBUTING.md — coding guidelines and PR expectations
- docs/good-first-issues.md — curated, scoped starter tasks with file pointers and acceptance criteria
- docs/modding_scenarios.md and docs/modding_goods.md — adding content without engine knowledge
- ROADMAP.md — where the project is heading and where contributors can plug in
Please read CODE_OF_CONDUCT.md and report security issues per SECURITY.md.
The NBomber harness lives at server/tests/VictoriaLike.NBomberLoadTest. To run a standard soak:
- In one terminal, bring up dependencies and the server:
make up make run-medium # matches the harness's built-in albion-player/bretoria-player credentialsrun-albionandrun-tinyalso work, but their scenarios don't contain both default accounts — pass--auth-users=1(albion) or a--credentials-filematching that scenario's players, or you'll see spurious401s from the missing account. - In a second terminal, run the soak profile against it:
For a two-player peaceful soak instead:
dotnet run --project server/tests/VictoriaLike.NBomberLoadTest -- \ --profile=soak --duration=1800 --warmup=30dotnet run --project server/tests/VictoriaLike.NBomberLoadTest -- --profile=two-player-soak
- Let it run to completion — the harness prints
Reports saved in folder: "..."and exits0on success. - Review the NBomber report in that
reports/folder alongside the server terminal's logs for errors or tick drift.
Recording: a shortened soak run (60s duration, standard profile) against make run-medium — click to expand (~500 KB)
dotnet run --project server/tests/VictoriaLike.NBomberLoadTest -- --profile=soak --duration=60 --warmup=10 against make run-medium — subscribers connect, authenticated clients log in and submit commands, and the run ends with NBomber's scenario/status-code tables and a Reports saved in folder line. The two 401s in the table are the harness's intentional stale-token check, not failures. Raw cast: demo_soak_test.cast.
Knobs (--duration, --warmup, --total-users, --auth-users, --command-interval, --command-mix={peaceful|full}) are documented in infra/README.md. That file also documents the sampled wrapper (infra/run-nbomber-soak-with-sampling.sh), which automates steps 1–4 above in a single command and additionally samples server RSS and Postgres write throughput into a generated soak_test_report.md.
Current proven envelope: 40 fake clients × 1 hour on the tiny economy with 0 errors / 0 tick drift. Everything beyond that is unclaimed — see docs/status/known_scale_limits.md.
A self-contained Oracle Cloud Free Tier deployment handoff lives at docs/deployment/oracle-cloud.md. It is not yet executed — scripts and a runbook are checked in (scripts/deploy-oracle.sh, scripts/Caddyfile.template, scripts/setup-systemd-unit.sh) and reviewed, but no host has been provisioned. A contributor picking up cloud deployment should start there.
MIT — see LICENSE.









