Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 utilities
  • map: hex-grid primitives and map storage
  • rules: rules-as-data definitions and loaders
  • sim: deterministic systems and fixed-step world
  • ai: intent generation and policies
  • io: serialization and persistence
  • viz: 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)), with z = -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)

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.


  1. 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 SizeDimensionsPlayers
Duel44x262–4
Tiny60x384/6
Small74x466/10
Standard84x548/14
Large96x6010/16
Huge106x6612/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.