# Task 5: Data Model & Output Contract (Tile, Region, Map, ASCII) **Depends on:** Task 2, Task 3 ## Context Files - `.dev/2026-05-28-procgen-map-core/spec/design.md` — §3 (`Map` contract), §4.3 (Tile), §4.5 (Region & connectivity), §4.9 (ASCII renderer). - `reikhelm-core/src/geometry.rs` — `Point`, `Rect`. - `reikhelm-core/src/grid.rs` — `Grid`. - `reikhelm-core/src/lib.rs` — append module declarations here. ## Files - Create: `reikhelm-core/src/region.rs` — `RegionId`, `RegionKind`, `Region`, `Edge`, `ConnGraph`. - Create: `reikhelm-core/src/map.rs` — `Tile`, `Map`. - Create: `reikhelm-core/src/ascii.rs` — ASCII renderer for `Map`. - Modify: `reikhelm-core/src/lib.rs` — append `pub mod region;`, `pub mod map;`, `pub mod ascii;`. ## What to Build The semantic data layer and the project's central output contract, `Map` (spec §3). `Map` is plain data describing *what is where* — **no colors, sprites, or rendering info**. Every type derives `serde::{Serialize, Deserialize}` (spec §3) so a `Map` can round-trip to JSON. The ASCII renderer (spec §4.9) lives in core because it has no external deps and is the workhorse for downstream tests. Note the `Edge.at` field is `Option` (refines spec §4.5's `Point`): the door location is unknown until `DoorPlacer` runs, so `MstConnect` creates edges with `at: None`. ## Interface Dependencies - **Consumes:** `Point`, `Rect` (Task 2), `Grid` (Task 3). - **Produces:** - `enum Tile { Wall, Floor, Door }` — `Default` = `Wall`; derives `Copy, Eq`. - `type RegionId` as a newtype `RegionId(usize)`. **Invariant:** `RegionId(n)` always equals the index `n` of that region in `ctx.regions`/`Map::regions`. Regions are append-only — never removed or reordered — so this id-as-index relationship holds for the life of the map (edges and lookups rely on it). - `enum RegionKind { Room, Corridor }` (extensible). - `struct Region { id: RegionId, kind: RegionKind, bounds: Rect, cells: Vec }` — **fields `pub`** (read across modules by passes and viz). - `struct Edge { a: RegionId, b: RegionId, at: Option }` — **fields `pub`** (constructed by `MstConnect`, `at` written by `DoorPlacer`, read by viz). - `struct ConnGraph { edges: Vec }` — `new`, `add_edge(a, b) -> ()` (pushes `at: None`), `edges() -> &[Edge]`, **`edges_mut() -> &mut [Edge]`** (so `DoorPlacer` can populate `at`), `neighbors(RegionId) -> impl Iterator`. - `struct Map { tiles: Grid, regions: Vec, graph: ConnGraph, seed: u64, width: u32, height: u32 }` — fields `pub` (the output contract, spec §3). - `impl Map { fn to_ascii(&self) -> String; fn print_ascii(&self); }` — `Wall` → `#`, `Floor` → `.`, `Door` → `+`, one row per line. ## Tests - A small hand-built `Map` serializes to JSON and deserializes back equal (serde round-trip). - `ConnGraph::add_edge` stores `at: None`; `neighbors` returns both endpoints' counterparts. - `Map::to_ascii` on a hand-built 3×3 map with a known tile layout produces the exact expected string (including a door `+`). ## Success Criteria - [ ] All types exist with the signatures above and derive serde; `Map` carries no rendering data. - [ ] `Edge.at` is `Option`. - [ ] ASCII output matches the documented glyph mapping. - [ ] Module declarations added to `lib.rs`. - [ ] All tests pass: `cargo test -p reikhelm-core` (region/map/ascii) ## Commit `"feat(core): data model + Map output contract + ASCII renderer"`