reikhelm/.dev/2026-05-28-procgen-map-core/tasks/05-data-model.md
Parley Hatch 08e99e38a3 chore: scaffold reikhelm cargo workspace (core + viz crates)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 20:56:24 -06:00

3.4 KiB
Raw Blame History

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.rsPoint, Rect.
  • reikhelm-core/src/grid.rsGrid<T>.
  • reikhelm-core/src/lib.rs — append module declarations here.

Files

  • Create: reikhelm-core/src/region.rsRegionId, RegionKind, Region, Edge, ConnGraph.
  • Create: reikhelm-core/src/map.rsTile, 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 whereno 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"