Civorum
Civorum is a deterministic, ECS-driven, hex-tiled 3D 4X simulator. This book hosts my dev diary, as well as the documentation.
Getting Started
- Clone the repository and build with Rust stable.
- See the Architecture section for crate-level overviews.
- For local docs: install mdBook and run
mdbook serve book
.
Architecture
This workspace is organized as a set of crates to keep simulation, rules, map primitives, I/O, AI, and visualization decoupled and testable.
Planned crates:
core
: core ECS types, shared utilitiesmap
: hex-grid primitives and map storagerules
: rules-as-data definitions and loaderssim
: deterministic systems and fixed-step worldai
: intent generation and policiesio
: serialization and persistenceviz
: Bevy-based real-time viewer
See subpages for details.
Map Crate
The map
crate provides a simple, rectangular hex-map for Civorum built on
top of the excellent hexx
library. It focuses on
coordinate/layout correctness and a minimal API you can build gameplay on.
Key properties
- Orientation: flat-top hexagons
- Indexing/layout: Odd-Columns (odd‑q) offset for (col,row) addressing
- Storage: axial coordinates (
hexx::Hex
) in row‑major order - World mapping:
hexx::HexLayout
constructed via the map for consistent pixel/world coordinates
References
- Hexx crate docs: https://docs.rs/hexx
- Coordinate systems: https://www.redblobgames.com/grids/hexagons
Coordinates & Layout
- Axial coordinates
(x, y)
(sometimes written(q, r)
), withz = -x - y
. - For odd‑q flat‑top indexing:
- axial → offset:
hex.to_offset_coordinates(OffsetHexMode::Odd, HexOrientation::Flat)
- offset → axial:
Hex::from_offset_coordinates([col,row], OffsetHexMode::Odd, HexOrientation::Flat)
- axial → offset:
The crate exposes a HexLayout
through Map::layout()
, which the viewer uses
to convert axial coordinates to world positions.
Sizes & Dimensions
MapSize
enumerates Civ‑like presets (Duel
, Tiny
, Small
, Standard
,
Large
, Huge
). Each size maps to a fixed (width, height)
in tiles, where
width is columns and height is rows in odd‑q offset.
Basic Usage
#![allow(unused)] fn main() { use map::{Map, MapSize}; // Create a map from a preset let m = Map::new(MapSize::Standard); assert_eq!(m.tiles().len() as u32, m.width() * m.height()); // Iterate axial tiles (hexx::Hex) for h in m.tiles() { let _ = (h.x(), h.y(), h.z()); } // In‑bounds neighbors (6‑connectivity) if let Some(center) = m.index_to_axial(0) { for n in m.neighbors(center) { // e.g., distance using hexx let _d = center.unsigned_distance_to(n); } } // Convert axial → offset index let some_hex = m.index_to_axial(0).unwrap(); let idx = m.axial_to_index(some_hex).unwrap(); assert_eq!(m.tiles()[idx], some_hex); // Axial → world using flat‑top layout let layout = m.layout(); let world = layout.hex_to_world_pos(some_hex); let _ = (world.x, world.y); }
Rendering with Hexx
For visualization (e.g., in the viewer
crate), use hexx’s mesh helpers.
This keeps geometry/math consistent with the layout:
#![allow(unused)] fn main() { use hexx::{PlaneMeshBuilder, MeshInfo}; let layout = m.layout(); let hex = m.index_to_axial(0).unwrap(); let info: MeshInfo = PlaneMeshBuilder::new(&layout).at(hex).build(); // Convert MeshInfo → engine mesh (see hexx docs for Bevy example) }
Hexx provides both PlaneMeshBuilder
(flat hex) and ColumnMeshBuilder
(3D column). See: https://docs.rs/hexx/latest/hexx/mesh/index.html
Notes
- Odd‑q (odd columns) is used for the flat‑top rectangle; be sure to pass
(OffsetHexMode::Odd, HexOrientation::Flat)
when converting. - Axial coordinates are signed; don’t treat
(x,y)
like a 0‑based grid. - The world layout (
HexLayout
) controls scale and origin for all mapping.
Dev Diary
Notes and progress logs.
- See entries listed in the sidebar.
06.10.2025 - Beginnings
So if you are following my github page, you might have seen that I recently finished1 my first proper simulation project called Rusty Runways. This got me thinking about other projects that I could make, that enable simulating complex environments for AIs and after thinking for some time it hit me!
During my thesis, I was initially looking to use the game Sid Meier’s Civilization, a really popular 4X game. However, since the game did not have a forward model, nor did it have any good adapters for agents, it was just not feasible to train any agent on it, given limited computational resources.
As such, I realized, I could just try to create my own civ clone albeit more limited. This marks the beginning of this dev journey, where I will try to recreate civilization 6, but in a slimmed down version. My key goals are:
- Getting world generation working: World generation should not be too difficult, but I want to allow for the creation of at least 3 distinct map types which I also enjoyed playing, and which allow for interesting scenarios: Pangea, islands, continents.
- Gameplay loop and progression: There are a lot of things to consider and to make sure there are no bugs. In total, I just want to try and port all of the main civ 6 elements that I like, to try and improve the performance and create a fun environment.
- Customization: When it comes to AI training games, not all tools work for one problem / goal, therefore I want to design this game with the idea of customization. This could entail maps, factions, progressions etc (lets see what else I can think of)
- 3D rendering: I am not artist, thats for sure, but getting some basic game setup in 3D so that one can also see what is going on is definetly a must. I did not do that for Rusty Runways, but civrealm does allow you to see how the agents play, and so I really want to do that as well.
- Python: That is something I have done before, but for AI projects like this, it is important to expose Python bindings, as not everyone is familiar with Rust or even wants to work with it.
Next steps
This is going to be my main side project for the next couple weeks / months (hopefully not years), but I will take it step by step. I think the first place to start at is the map itself. Making sure we can generate a map and perhaps visualize it using bevy. Once that is in place, then we can see about other next steps.
For gameplay information / balancing, I think I will just take a page out of civilizations book, and take the same values they are using. This way I do not need to fine-tune things myself.
-
As far as a coding project is very truly finished ↩
16.10.2025 - Hexes and Maps
So like any person would do, the first thing I had to do to understand hex grids, was read up about them. Luckily for me, there are a lot of great resources out there, which explain how hex grids work. If you are reading this and are interested, I would suggest you check out this page.
From reading, I wanted to give it a shot at making just the axial version with flat-top orientation, as this most reminded me of civ. However… working on it was more of an issue than I thought. Initially, just making the struct, the few methods to calculate distances, sizes etc was simple, but the biggest hurddle was getting it into bevy and that is where the struggle began.
To place it into bevy, we need to switch between the axial and world coordinates, which turned out to be just more difficult than I thought. Overall, I got them placed, but never properly aligned, meaning there were small spaces and pockets that were not filled. Apart from just being wrong, it also looked unaesthetic and very unsatisfying.
I tried to tinker around more, but when I got sick I kind of just left it. It was not worth the effort to go and try to reinvent the wheel, when there are already crates that work very well for hexagonal coordinates. After some research, I decided to use hexx. It seems easy enough to use, and is even based on the same resource that I used initially.
In the end, I managed to get it working with a bit of helps from the docs and trusty old ChatGPT to figure out the camera placements (I really dislike how the camera is handled in bevy). For now, it simply creates a grid of hex tiles based on the size selected. I used the civ6 map sizes as a guideline here:
Map Size | Dimensions | Players |
---|---|---|
Duel | 44x26 | 2–4 |
Tiny | 60x38 | 4/6 |
Small | 74x46 | 6/10 |
Standard | 84x54 | 8/14 |
Large | 96x60 | 10/16 |
Huge | 106x66 | 12/20 |
Next steps
Now in order to make this game like civ, we will need to add terrain. For the basic civ setup, there are only a few main tiles that we need. Also, the civ map, as far as I am concerned does not directly follow the whittaker model like here on earth. It might be interesting to try and expand a bit on the type of biomes, using rainfall and temperature to make it more diverse and to smoothen the transitions.