# Dorf A Dwarf Fortress-inspired simulation game built in Rust with Bevy. The focus is on a fully data-oriented chunk engine: procedural terrain, hierarchical pathfinding, GPU-baked sprite rendering, and dynamic chunk loading/unloading — all designed to scale to a full DF-style world with z-levels, entities, and simulation. This is an **engine foundation**, not a complete game. Many features (crafting, needs, combat, world history) exist only as stubs or are entirely absent. The chunk rendering and pathfinding systems are production-quality; the simulation layer above them is early. ## Architecture ``` src/ ├── camera.rs # Panning camera, z-level control (Q/E, scroll) ├── config.rs # GameConfig resource ├── constants.rs # Tile size, pathfinding constants ├── entities/ # Dorfs, pigs, rabbits — ambulatory entities │ ├── sentient/ │ ├── livestock/ │ └── shared_*/ # Ambulatory, pathfinding systems ├── game.rs # ZIndex resource, frame timing ├── main.rs # Bevy app bootstrap └── world/ ├── chunks/ # ChunkMap, chunk loading/unloading, connectivity ├── generation/ # Terrain blobs, forestry, foliage (stub), fauna (stub) └── tiles/ # TileMap, TileRegistry, rendering, visibility ``` ### Chunk Architecture - **World chunks**: 8×8 tiles (`CHUNK_SIZE = 8`, `CHUNK_SIZE_TILE = 128px`) - **Render chunks**: 32×32 tiles (`CHUNK_TILES = 32`, `512px`). One render chunk = 4×4 world chunks. This mismatch is intentional — a single GPU texture covers multiple world chunks, which means chunk unloading must mark the render chunk dirty (not the world chunk). - **Z-levels**: 5 below, 15 above, 21 total (`Z_BELOW = 5`, `Z_ABOVE = 15`). Terrain is rendered as quilted sprites — one texture per z-level, all stacked in the shader. - **Loading**: `setup_initial_chunks` runs at `Startup`, emits `GenerateChunkEvent` for all chunks in `initial_chunk_radius`. `handle_chunk_events` processes events and fires terrain/forestry/foliage/fauna generation events. - **Unloading**: `unload_chunk(chunk_pos)` is the canonical function. Currently disabled — `dynamic_chunk_unloading_system` is a no-op stub. To enable: implement a system that diffs wanted chunks (player/NPC interest radius) against `ChunkMap.loaded_chunks` and calls `unload_chunk` for each evictable chunk. ### Rendering Pipeline 1. `handle_tile_occlusion_updates` — raycasts from each tile upward to compute `visible_range` bitmask (which z-levels can see it) 2. `build_quilted_terrain_sprites` — GPU bake: tiles bucketed by `(render_chunk, z_level)`, blitted into pooled pixel buffers, spawned as `TerrainSprite` entities 3. `update_tile_visibility` — toggles `Visibility` on sprites based on current `ZIndex` (camera z-level) ### Pathfinding Three tiers, gated by distance: - **Tier 1** (<4 chunks): synchronous A* on `TileMap` - **Tier 2** (4–8 chunks): provisional path + full path via async queue - **Tier 3** (>8 chunks): hierarchical chunk graph → async segmented A* `PathfindingBenchmark` resource tracks sync vs async performance per frame. ### Entity Indexing Static terrain entities (floor tiles, fixtures, trees) are indexed in `chunk_entity_index: HashMap>` at spawn time. When a chunk unloads, the index is retrieved and entities are despawned. This is **O(entities in chunk)**, not O(total entities). Mobile entities (dorfs, pigs, rabbits) are **not** in `chunk_entity_index`. They are spawned once at game start and persist. They will need per-chunk lifecycle management once fauna generation is implemented. ## Controls | Key | Action | |-----|--------| | Click + drag | Pan camera | | Scroll | Pan camera | | Q / E | Move camera z-level down / up | | Mouse wheel | Pan camera | ## Configuration `config.toml`: - `initial_chunk_radius`: chunks loaded around origin at startup (default: 7 → 15×15 = 225 chunks) - `spawn_counts`: dorfs, pigs, rabbits spawned at game start `tiles.toml`: tile registry — floor tiles, fixture tiles. Each tile has `id`, `astar_weight`, `can_stand_in`, `can_stand_on`, `transparent` flags. ## Status ### Implemented - [x] Procedural terrain (blob-based, simplex noise heightmap) - [x] Tile registry (`tiles.toml`) with A* weights - [x] Floor tiles, fixture tiles, item tiles - [x] Z-levels with quilted GPU sprite rendering - [x] Tile occlusion / visibility raycasting - [x] Forestry: tree trunks + leaves spawned per chunk - [x] Dorfs, pigs, rabbits spawning at startup - [x] Hierarchical async pathfinding (3 tiers) - [x] Chunk loading infrastructure - [x] Chunk unloading infrastructure (`unload_chunk`, `dynamic_chunk_unloading_system` stub) - [x] Camera panning + z-level control - [x] GPU texture pooling (pixel buffer reuse across bakes) - [x] Render chunk dirty-tracking for incremental rebakes ### Stubs (exist as empty functions, wired into schedules) - [ ] Foliage generation per chunk (`generate_chunk_foliage`) - [ ] Fauna spawning per chunk (`generate_chunk_fauna`) - [ ] Weathering and precipitation (`generate_chunk_weathering_and_precipitation`) ### Not Started - [ ] Crafting, stockpiles, resource gathering - [ ] Needs, mood, stress systems - [ ] Job assignment and task automation - [ ] Building and construction (digging/mining) - [ ] Combat and squad management - [ ] Item persistence and inventory - [ ] Save/load world state - [ ] World history and procedural storytelling - [ ] UI (menus, z-level overlay, workshop assignment) - [ ] Dynamic chunk loading (player/NPC-centered, not just radius-at-startup) ## Building ```sh cargo build # dev profile, ~1.2K warnings (pre-existing) cargo run # dev mode ``` `[profile.dev.package."*"] opt-level = 3` gives near-release perf in dev builds.