47 lines
3.4 KiB
Markdown
47 lines
3.4 KiB
Markdown
# 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<T>`.
|
||
- `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<Point>` (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<T>` (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<Point> }` — **fields `pub`** (read across modules by passes and viz).
|
||
- `struct Edge { a: RegionId, b: RegionId, at: Option<Point> }` — **fields `pub`** (constructed by `MstConnect`, `at` written by `DoorPlacer`, read by viz).
|
||
- `struct ConnGraph { edges: Vec<Edge> }` — `new`, `add_edge(a, b) -> ()` (pushes `at: None`), `edges() -> &[Edge]`, **`edges_mut() -> &mut [Edge]`** (so `DoorPlacer` can populate `at`), `neighbors(RegionId) -> impl Iterator<Item = RegionId>`.
|
||
- `struct Map { tiles: Grid<Tile>, regions: Vec<Region>, 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<Point>`.
|
||
- [ ] 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"`
|