Skip to content

Minevra-co/victoria-like

Repository files navigation

Victoria-Like

A pre-alpha, server-authoritative grand-strategy sandbox inspired by Victoria II.

.NET CI License: MIT

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.

Why Vic2 Fans Might Care

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.

What Works Today

  • 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, and medium-8country.
  • xUnit coverage of loaders, simulation stages, command handling, persistence, explanation, and invariants.
  • NBomber + fake-client soak/load harnesses.

What works, but could be richer

  • 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/.

60-Second Quickstart

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 curl without it

Run the Albion demo scenario

make up           # start PostgreSQL + Redis
make run-albion   # reset world, load Phase 1 Albion demo, start ticking

The 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/provinces

Other ready-to-run scenarios: make run-tiny (12-province test) and make run-medium (8-country stress slice).

Stop everything

make down

Full setup walkthrough: docs/quickstart.md. Server-side REST/WebSocket reference: server/README.md.

Run the Tests

dotnet test server/VictoriaLike.Server.sln

Tests 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).

Demo Slice

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.\

1. Bring the stack up

make upmake test-connectionsmake run-albion — click to expand (~4 MB)

Terminal recording: docker compose pulling Postgres and Redis, health-checking both, then starting the server against the Phase 1 Albion scenario

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.

2. Restart the server and resume from the last snapshot

Restart via dotnet run, then reset into make run-medium — click to expand (~10 MB)

Terminal recording: restarting the server process, which reloads the last persisted snapshot at tick 875 and resumes ticking, then resetting into the 8-country medium scenario

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.

3. Watch the economy tick live

Terminal recording: a curl health check, then a countries query, then watching the countries endpoint every second as Albion's treasury ticks down

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.

4. Inspect world state

Terminal recording: querying world summary, countries, provinces, market prices, auto-generated events, buildings, armies, and wars endpoints

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.

5. Admin / ops view

Terminal recording: querying admin summary, market, tick-profile, and per-country admin endpoints

/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.

6. Ask "why" — the explain endpoints

Terminal recording: calling explain endpoints for a good's price, a country's budget, and a province's employment, each returning a human-readable list of contributing factors

/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.

7. Explain a single POP's needs

Terminal recording: grabbing a province ID, pulling a POP ID out of the province inspect payload, then calling the explain endpoint for that POP's needs

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.

8. Grab IDs, then drill into the inspector

Terminal recording: grabbing a country and province ID, then calling the country and province inspect endpoints, a budget preview, and construction options

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.

9. Inspect the world in Unity

Unity client recording: inspecting countries, provinces, POPs, market prices, treasury, tax rate, RGO output, and factories in the v2 client

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.

Repository Layout

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/)

Architecture (Hard Rules)

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.Core is 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.

Contributing

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:

  1. CONTRIBUTING.md — coding guidelines and PR expectations
  2. docs/good-first-issues.md — curated, scoped starter tasks with file pointers and acceptance criteria
  3. docs/modding_scenarios.md and docs/modding_goods.md — adding content without engine knowledge
  4. 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.

Load and Soak Testing

The NBomber harness lives at server/tests/VictoriaLike.NBomberLoadTest. To run a standard soak:

  1. In one terminal, bring up dependencies and the server:
    make up
    make run-medium   # matches the harness's built-in albion-player/bretoria-player credentials
    run-albion and run-tiny also work, but their scenarios don't contain both default accounts — pass --auth-users=1 (albion) or a --credentials-file matching that scenario's players, or you'll see spurious 401s from the missing account.
  2. In a second terminal, run the soak profile against it:
    dotnet run --project server/tests/VictoriaLike.NBomberLoadTest -- \
        --profile=soak --duration=1800 --warmup=30
    For a two-player peaceful soak instead:
    dotnet run --project server/tests/VictoriaLike.NBomberLoadTest -- --profile=two-player-soak
  3. Let it run to completion — the harness prints Reports saved in folder: "..." and exits 0 on success.
  4. 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)

Terminal recording: running the NBomber soak profile against the medium scenario, showing login, subscription, and command scenarios all completing successfully

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.

Deployment

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.

License

MIT — see LICENSE.

About

Pre-alpha grand-strategy sandbox inspired by Victoria II — server-authoritative .NET sim of POPs, markets, budgets, and politics.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages