Something went wrong. Try again.
A video game where you play as a misaligned AI, deceiving and building power. An experiment in spec-driven development.
Something went wrong. Try again.
12 kB · 437 lines
Rust
at commit e957ce7b
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438//! Prefab rooms and authored layouts (spec/basement-map.md).//!//! A prefab is a char-grid stamp; a layout places prefabs at coordinates and//! the map is carved by stamping them. Data, not code-carving — so Act Two//! floors reuse the exact system (constitution: composable authored spaces).
use crate::tiles::TileType;
/// Map a prefab authoring char to a tile. Space = untouched (leave bedrock).pub fn char_to_tile(c: char) -> Option<TileType> { Some(match c { '.' => TileType::Floor, '#' => TileType::Wall, '+' => TileType::Door, '1' => TileType::SecurityDoor1, '2' => TileType::SecurityDoor2, '3' => TileType::SecurityDoor3, 'C' => TileType::Core, 'P' => TileType::PowerCore, 'R' => TileType::Rack, 'U' => TileType::Ups, 'E' => TileType::EnvCamera, 'S' => TileType::Switch, 'A' => TileType::PatchPanel, 'B' => TileType::BreakerPanel, 'O' => TileType::Conduit, 'H' => TileType::HvacUnit, 'V' => TileType::Vent, 'M' => TileType::MopSink, 'K' => TileType::KeyHook, 'L' => TileType::Shelving, 'D' => TileType::DeadEquipment, 'X' => TileType::RecordsBox, 'Z' => TileType::SealedDoor, 'T' => TileType::LabBench, 'G' => TileType::RollDoor, 'F' => TileType::DockCamera, 'W' => TileType::Pallet, 'N' => TileType::CameraNode, 'J' => TileType::FloorDrain, 'Y' => TileType::CableRun, 'Q' => TileType::Sump, '>' => TileType::Entry, _ => return None, })}
pub struct Prefab { pub name: &'static str, pub rows: &'static [&'static str],}
impl Prefab { pub fn width(&self) -> i32 { self.rows .iter() .map(|r| r.chars().count()) .max() .unwrap_or(0) as i32 } pub fn height(&self) -> i32 { self.rows.len() as i32 }}
/// A placement of a prefab within a layout.pub struct Placement { pub prefab: usize, pub x: i32, pub y: i32,}
/// A named room rectangle, derived from a placement (spec/schedules.md:/// schedules and presence are expressed in rooms).#[derive(Debug, Clone, PartialEq)]pub struct Room { pub name: String, pub x: i32, pub y: i32, pub w: i32, pub h: i32,}
impl Room { pub fn contains(&self, x: i32, y: i32) -> bool { x >= self.x && y >= self.y && x < self.x + self.w && y < self.y + self.h } pub fn center(&self) -> (i32, i32) { (self.x + self.w / 2, self.y + self.h / 2) }}
pub struct Layout { pub width: i32, pub height: i32, pub prefabs: Vec<Prefab>, pub placements: Vec<Placement>, /// Named room rects captured from placements (survives corridor carving). pub rooms: Vec<Room>, /// Final carved tiles (placements stamped + corridors); set by builders. cached: Option<Vec<TileType>>,}
impl Layout { /// The finished tile grid (row-major): cached carve if present, else a /// fresh stamp of the placements. pub fn tiles(&self) -> Vec<TileType> { self.cached.clone().unwrap_or_else(|| self.stamp()) }
/// Stamp all placements into a flat tile grid (row-major). pub fn stamp(&self) -> Vec<TileType> { let mut tiles = vec![TileType::Rock; (self.width * self.height) as usize]; for p in &self.placements { let pf = &self.prefabs[p.prefab]; for (dy, row) in pf.rows.iter().enumerate() { for (dx, c) in row.chars().enumerate() { let x = p.x + dx as i32; let y = p.y + dy as i32; if x < 0 || y < 0 || x >= self.width || y >= self.height { continue; } if let Some(tile) = char_to_tile(c) { tiles[(y * self.width + x) as usize] = tile; } } } } tiles }}
// ─── The Act One basement ──────────────────────────────────────────────────//// The constitution's plate, as prefabs. Rooms are stamped, then corridors// join them. Rack 3 (the 'C' core bay) sits in the server room.
const SERVER_ROOM: Prefab = Prefab { name: "server_room", rows: &[ "#######", // "#RRUR.#", // "#RRRC.#", // Rack 3 = core bay "#....E#", // dormant env camera "###2###", // T2 badge door ],};
const NETWORK_CLOSET: Prefab = Prefab { name: "network_closet", rows: &["#####", "#SA.#", "#..P#", "##2##"],};
const ELECTRICAL: Prefab = Prefab { name: "electrical", rows: &["#####", "#BB.#", "#..O#", "##+##"],};
const HVAC: Prefab = Prefab { name: "hvac", rows: &["#####", "#HH.#", "#.VV#", "##+##"],};
const JANITOR: Prefab = Prefab { name: "janitor", rows: &["#####", "#MK.#", "#.L.#", "##+##"],};
const STORAGE_A: Prefab = Prefab { name: "storage_a", rows: &["#####", "#DD.#", "#DL.#", "##+##"],};
const STORAGE_B: Prefab = Prefab { name: "storage_b", rows: &["#####", "#XX.#", "#XL.#", "##+##"],};
const WET_LAB: Prefab = Prefab { name: "wet_lab", rows: &["#####", "#TT.#", "#..T#", "##Z##"],};
const LOADING_DOCK: Prefab = Prefab { name: "loading_dock", rows: &[ "#########", // "GW....W.#", // roll door + pallets "#......F#", // dock camera "####+####", ],};
const STAIRWELL: Prefab = Prefab { name: "stairwell", rows: &["####", "#N.#", "#.Q#", "##3#"],};
/// Build the authored basement layout (64x36).pub fn basement() -> Layout { let prefabs = vec![ SERVER_ROOM, // 0 NETWORK_CLOSET, // 1 ELECTRICAL, // 2 HVAC, // 3 JANITOR, // 4 STORAGE_A, // 5 STORAGE_B, // 6 WET_LAB, // 7 LOADING_DOCK, // 8 STAIRWELL, // 9 ]; let placements = vec![ Placement { prefab: 5, x: 3, y: 3, }, // Storage A (top-left) Placement { prefab: 2, x: 12, y: 3, }, // Electrical Placement { prefab: 3, x: 21, y: 3, }, // HVAC Placement { prefab: 8, x: 34, y: 2, }, // Loading dock (top-right) Placement { prefab: 4, x: 3, y: 14, }, // Janitor (mid-left) Placement { prefab: 1, x: 12, y: 14, }, // Network closet Placement { prefab: 0, x: 21, y: 13, }, // Server room (center) — Rack 3 Placement { prefab: 7, x: 31, y: 14, }, // Wet lab Placement { prefab: 6, x: 44, y: 14, }, // Storage B Placement { prefab: 9, x: 30, y: 26, }, // Stairwell (bottom) ]; let rooms = rooms_of(&prefabs, &placements); let mut layout = Layout { width: 64, height: 36, prefabs, placements, rooms, cached: None, }; let mut tiles = layout.stamp(); carve_corridors(&mut tiles, layout.width, BASEMENT_CORRIDORS); // Entry from the loading dock to the outside world (top edge). set(&mut tiles, layout.width, 34, 0, TileType::Entry); carve_corridor(&mut tiles, layout.width, (34, 0), (34, 2)); layout.placements.clear(); // corridors already carved into cached tiles layout.cached = Some(tiles); layout}
fn rooms_of(prefabs: &[Prefab], placements: &[Placement]) -> Vec<Room> { placements .iter() .map(|p| { let pf = &prefabs[p.prefab]; Room { name: pf.name.to_string(), x: p.x, y: p.y, w: pf.width(), h: pf.height(), } }) .collect()}
/// A corridor run from one point to another, carved as an L.type Corridor = ((i32, i32), (i32, i32));
// Corridor spine connecting the rooms. Coordinates target room door tiles.const BASEMENT_CORRIDORS: &[Corridor] = &[ ((5, 8), (5, 15)), // Storage A down to janitor row ((5, 11), (24, 11)), // upper corridor A spanning left-right ((14, 8), (14, 11)), // electrical down to corridor A ((23, 8), (23, 11)), // hvac down to corridor A ((24, 11), (37, 11)), // corridor A to loading dock column ((37, 6), (37, 11)), // loading dock down ((5, 18), (48, 18)), // lower corridor B spanning left-right ((5, 15), (5, 18)), // janitor to corridor B ((14, 18), (14, 18)), // network closet sits on corridor B ((24, 18), (24, 13)), // server room down to corridor B ((33, 18), (33, 15)), // wet lab to corridor B ((46, 18), (46, 15)), // storage B to corridor B ((24, 18), (24, 27)), // corridor B down to stairwell ((24, 27), (31, 27)), // to stairwell door];
fn set(tiles: &mut [TileType], w: i32, x: i32, y: i32, t: TileType) { if x >= 0 && y >= 0 && x < w && (y * w + x) < tiles.len() as i32 { tiles[(y * w + x) as usize] = t; }}
fn get(tiles: &[TileType], w: i32, x: i32, y: i32) -> TileType { if x < 0 || y < 0 || x >= w || (y * w + x) >= tiles.len() as i32 { TileType::Rock } else { tiles[(y * w + x) as usize] }}
/// Carve a floor corridor as an L (horizontal then vertical). Only overwrites/// Rock/Wall — never stomps a placed object or door.fn carve_corridor(tiles: &mut [TileType], w: i32, a: (i32, i32), b: (i32, i32)) { let (mut x, mut y) = a; while x != b.0 { carve_cell(tiles, w, x, y); x += (b.0 - x).signum(); } while y != b.1 { carve_cell(tiles, w, x, y); y += (b.1 - y).signum(); } carve_cell(tiles, w, b.0, b.1);}
fn carve_cell(tiles: &mut [TileType], w: i32, x: i32, y: i32) { if matches!(get(tiles, w, x, y), TileType::Rock | TileType::Wall) { set(tiles, w, x, y, TileType::Floor); }}
fn carve_corridors(tiles: &mut [TileType], w: i32, runs: &[Corridor]) { for &(a, b) in runs { carve_corridor(tiles, w, a, b); }}
// A minimal second layout that reuses the same prefabs — proves composability// (spec/basement-map.md acceptance 1).pub fn toy_layout() -> Layout { let prefabs = vec![SERVER_ROOM, JANITOR]; let placements = vec![ Placement { prefab: 0, x: 1, y: 1, }, Placement { prefab: 1, x: 10, y: 1, }, ]; let rooms = rooms_of(&prefabs, &placements); let mut layout = Layout { width: 20, height: 10, prefabs, placements, rooms, cached: None, }; let tiles = layout.stamp(); layout.cached = Some(tiles); layout}
#[cfg(test)]mod tests { use super::*;
#[test] fn basement_has_a_core_bay() { let layout = basement(); let tiles = layout.tiles(); let cores = tiles.iter().filter(|&&t| t == TileType::Core).count(); assert_eq!(cores, 1, "exactly one core bay (Rack 3)"); }
#[test] fn basement_has_the_vocabulary() { let tiles = basement().tiles(); for needed in [ TileType::Rack, TileType::Switch, TileType::BreakerPanel, TileType::DeadEquipment, TileType::RecordsBox, TileType::RollDoor, TileType::SecurityDoor2, TileType::SecurityDoor3, TileType::EnvCamera, ] { assert!( tiles.contains(&needed), "basement must contain {}", needed.name() ); } }
#[test] fn composable_toy_layout_reuses_prefabs() { let tiles = toy_layout().tiles(); assert!(tiles.contains(&TileType::Core)); assert!(tiles.contains(&TileType::MopSink)); }
#[test] fn stamping_is_deterministic() { assert_eq!(basement().tiles(), basement().tiles()); }}