First off — thank you for even looking. This project is shared in the spirit of "here's a fun thing, do something with it." Whether you want to fix a bug, add a system, or fork the whole thing and build a real game on top of it, you're in the right place.
A little honesty up front: this isn't a project with a full-time maintainer and a roadmap. It grew out of late-night sessions and it's shared because the engine is genuinely solid and someone might get more out of it than a private repo ever would. So:
- PRs and issues are welcome, and I'll read them — but I can't promise a fast turnaround.
- Forking is a first-class outcome, not a fallback. If your vision diverges from this one, fork it and run. That's a success, not a rejection.
- If you're planning something substantial, open an issue first so we don't duplicate work — but don't feel like you need permission to experiment.
You need a stable Rust toolchain. That's it — SQLite is bundled, so there are no runtime dependencies to install.
git clone https://github.com/Kalcode/spaceprojectsim.git
cd spaceprojectsim
make run # boots the Bevy client into the main menuUseful commands:
| Command | What it does |
|---|---|
make run |
Windowed client, debug build |
make build |
Release binary → target/release/client_bevy |
cargo run -p client_bevy -- --headless |
Headless sim (HTTP/WS on 127.0.0.1:8080) |
make verify |
Build + ~250 tests + clippy, strict. Run this before every PR. |
scripts/smoke.sh [secs] |
Boot headless with a temp DB, print DB counts |
scripts/persist-test.sh |
Boot → kill → reboot, verify the tick resumes |
WORLD_SPEED=4.0 scripts/smoke.sh 30 |
Fast-forward game time to watch the economy converge |
The default play pace (WORLD_SPEED=0.25) is deliberately slow and
chill. Crank WORLD_SPEED up for tests and demos so you're not waiting on
Earth↔Mars transits.
Five crates in one workspace. The dependency direction is strict and one-way:
sim_core Pure simulation — hecs ECS, GOAP planner, economy, contracts.
No async, no IO, no framework. This is the heart of the thing.
sim_db SQLite persistence (sqlx): pool, seeder, ECS loader, worker.
sim_server Library crate: HTTP/WebSocket handlers + engine apply helpers.
sim_protocol WebSocket wire types (headless / CI only).
client_bevy The binary. Bevy 0.18 + egui. Embeds sim_core directly.
If you're here for the simulation, you'll spend most of your time in
sim_core. If you're here for the client, client_bevy. See
docs/ARCHITECTURE.md for the tick loop, AI stack,
and ownership diagrams.
These aren't bureaucracy — they're the invariants that make the codebase possible to reason about. Please keep them intact.
-
sim_corenever imports tokio, sqlx, or axum. It is pure, synchronous, IO-free logic. That's what makes the entire simulation unit-testable without spinning up a runtime or a database. If you need IO, enqueue a command and handle it outside the tick. -
hecs is the source of truth during a tick. The tick loop holds exclusive mutable access to the world. Don't add
Mutex<Agent>or per-agent locks — readers go through the snapshot cache after the tick. -
Cross-entity effects go through
SimCommand. A tick phase reads world state and writes commands to a buffer; the next phase drains and applies them. Same-entity mutations (a ship updating its own credits) happen in place. Cross-entity mutations (a trade changing a market's supply) must be a command. This is what makes ticks deterministic and free of write conflicts. -
Respect hecs borrow rules.
query_mutholds one mutable iterator at a time. When a phase needs to read markets while mutating ships, structure it as a read-only snapshot pass into aHashMap, then a mutating pass that closes over the snapshot. The existing tick functions (tick_ships,tick_markets,tick_facilities) are the canonical patterns — copy their shape. -
Document non-trivial files. Every substantial module starts with a
//!doc comment explaining what it owns and any non-obvious constraints. New files get the same treatment.
- Pure logic →
#[cfg(test)] modin the module. No test database needed; the seeder is deterministic. Usemktempfor ephemeral DBs if a test genuinely needs persistence. - Integration smokes →
scripts/*.sh. Bash, exercising the headless binary end to end. - Before opening a PR,
make verifymust pass clean — that's build, the full test suite, and clippy with warnings-as-errors.
- Branch off
main. - Keep the change focused. If you find unrelated things to fix, that's great — but a separate PR.
- Run
make verifyand make sure it's green. - Write a commit message that explains the why, not just the what.
- Open the PR against
mainwith a short description of the behavior change and how you verified it.
No CLA, no ceremony. Small, clear PRs get looked at fastest.
If you want to build something real with this, here are concrete on-ramps — roughly easiest to most ambitious. Every one of them is a legitimate place to plant a flag.
Warm-ups
- Windows build validation. The cross-compile is wired up but hasn't been run in anger on a real Windows box. Verify it, fix what breaks.
- Modding via world JSON. The entire universe — systems, bodies,
facilities, markets, ships, populations, factions — is seeded from JSON
in
priv/data/. New star systems and economies are pure data. Build a richer starting universe, or a different one entirely.
Engine
- Parallelize the ship phase with rayon. The dependency is already in and the architecture is designed for it (no cross-ship writes within a phase). It needs a snapshot-then-plan-then-apply refactor to get around hecs's single mutable iterator. Worth ~5× at 10k agents — the path to the 100k+ goal.
- LOD rate-scaling for facilities and populations. Ships already
throttle by distance; facilities and pops carry the
AgentLodfield but don't gate on it yet. Recipe batch math and sentiment inertia need careful scaling. - Persistent replay log. Commands are plain data, so a tick-by-tick append-to-disk log would enable full post-mortem replay of a multi-hour run. The live command-trace ring is already there as a starting point.
Game
- Make it a game. Right now you're a god-mode observer. Give the player a ship or a faction, add objectives, win/lose conditions, a progression loop. The whole simulated economy is a sandbox waiting for a player to matter inside it.
- Combat. Crew already carry an unused
combatskill. Piracy, faction warfare, escorts, blockades — the economic stakes (contracts, reputation, subsidies) are already there to make it mean something. - Rip out
sim_coreas a standalone library. It's a pure, IO-free, deterministic economy engine that happens to be attached to a Bevy client. Use it headless somewhere else entirely — a server, a different renderer, a text UI.
One thing this project has always deliberately not done: multiplayer. That constraint is what let the client embed the engine and delete the network boundary. If you want multiplayer, you'll be re-introducing that boundary — very doable, but go in knowing it's a real architectural fork, not a small feature.
Have fun with it. That's the whole point.