Something went wrong. Try again.
HEXWORK - A Lantern in the Deep: 3D ASCII terminal roguelike RPG in Rust.
Entirely designed and built by Muse Spark 1.3 to evaluate if I like the model.
Something went wrong. Try again.
79 kB · 2389 lines
Rust
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390//! Deterministic, apply-ready plans for generated worlds.//!//! Canonical names and template IDs are opaque strings: this crate does not//! depend on the content crate. Geometry is ready to apply, but mechanic hooks//! stay inert until a production session binds them. This module never mutates//! `State`; the parent validates, then owns the complete apply transaction.
use std::collections::{BTreeMap, BTreeSet, VecDeque};use std::fmt;
use hexwork_core::connector::{Connector, ConnectorKind};use hexwork_core::fluids::fluid;pub use hexwork_core::npc::Job;pub use hexwork_core::runtime::ActorRole;use hexwork_core::voxel::{extra, flags, material, Voxel};
use crate::Stratum;
/// Integer world-space voxel coordinate.pub type Cell = [i32; 3];
type CellKey = (i32, i32, i32);
const fn cell_key(cell: Cell) -> CellKey { (cell[2], cell[1], cell[0])}
/// Inclusive axis-aligned bounds for a generated plan.#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub struct Bounds { pub min: Cell, pub max: Cell,}
impl Bounds { #[must_use] pub const fn contains(self, cell: Cell) -> bool { cell[0] >= self.min[0] && cell[0] <= self.max[0] && cell[1] >= self.min[1] && cell[1] <= self.max[1] && cell[2] >= self.min[2] && cell[2] <= self.max[2] }}
/// One stable voxel assignment. Plans contain at most one write per cell.#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub struct VoxelWrite { pub cell: Cell, pub voxel: Voxel,}
/// Typed destination of a cross-plan connector.#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]pub enum PlanDestination { Town, Deep { depth: u8 },}
/// Direction of travel from this connector endpoint.#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]pub enum TransitionDirection { Up, Down,}
/// One endpoint of a paired physical transition.////// `transition_id` identifies this endpoint, `paired_transition_id` identifies/// its reciprocal endpoint, and both endpoints share `link_id` and the embedded/// core [`Connector`]. `column` is the locally carved shaft; `access` is the/// adjacent supported landing; `arrival` is the destination shaft cell.#[derive(Clone, Debug)]pub struct ConnectorPlan { pub transition_id: u64, pub paired_transition_id: u64, pub link_id: u64, pub direction: TransitionDirection, pub destination: PlanDestination, pub column: Cell, pub access: Cell, pub arrival: Cell, pub connector: Connector,}
impl PartialEq for ConnectorPlan { fn eq(&self, other: &Self) -> bool { self.transition_id == other.transition_id && self.paired_transition_id == other.paired_transition_id && self.link_id == other.link_id && self.direction == other.direction && self.destination == other.destination && self.column == other.column && self.access == other.access && self.arrival == other.arrival && self.connector.id == other.connector.id && self.connector.kind == other.connector.kind && self.connector.from.map(f32::to_bits) == other.connector.from.map(f32::to_bits) && self.connector.to.map(f32::to_bits) == other.connector.to.map(f32::to_bits) && self.connector.length.to_bits() == other.connector.length.to_bits() && self.connector.anchor_quality == other.connector.anchor_quality && self.connector.load_kg.to_bits() == other.connector.load_kg.to_bits() && self.connector.blocked == other.connector.blocked }}
impl Eq for ConnectorPlan {}
/// An actor requested from content by template id.#[derive(Clone, Debug, PartialEq, Eq)]pub struct ActorSpawnPlan { pub template_id: String, pub display_name: String, pub role: ActorRole, pub position: Cell, pub hp: i32, /// Town NPC schedule metadata. Monsters leave these fields empty. pub home: Option<Cell>, pub work: Option<Cell>, pub job: Option<Job>,}
/// An item requested from content by template id.#[derive(Clone, Debug, PartialEq, Eq)]pub struct ItemSpawnPlan { pub template_id: String, pub position: Cell, pub quantity: u16,}
/// A position at which the parent should install an interaction.#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]pub enum InteractionKind { NoticeBoard, Inn, Service { id: String, }, Place { id: String, }, DeepEntrance { link_id: u64, }, Objective { id: String, }, Up { link_id: u64, }, Down { link_id: u64, }, /// Ending/session hook. This is not a fictitious depth-13 connector. Return,}
/// Ordered bump-adjacent interaction anchor.#[derive(Clone, Debug, PartialEq, Eq)]pub struct InteractionAnchor { /// Exact cell holding the feature, sign, counter, or connector column. pub position: Cell, /// Supported player cell from which the feature can be bumped/used. pub access: Cell, pub kind: InteractionKind, pub label: String,}
/// A named content-opaque world feature.#[derive(Clone, Debug, PartialEq, Eq)]pub struct NamedAnchor { pub id: String, pub label: String, pub position: Cell, pub access: Cell,}
/// Explicit straight town road. `width` is an odd number of fully carved/// lanes centered on `from..=to`, extending equally on both perpendicular sides.#[derive(Clone, Debug, PartialEq, Eq)]pub struct RoadPlan { pub id: String, pub from: Cell, pub to: Cell, pub width: u8,}
/// Deterministic actor-content placement region.#[derive(Clone, Debug, PartialEq, Eq)]pub struct SpawnZone { pub id: String, pub bounds: Bounds, pub faction: Option<String>, pub template_ids: Vec<String>,}
/// Metadata-only pressure region; the parent may bind it to live State.#[derive(Clone, Debug, PartialEq, Eq)]pub struct PressureZone { pub id: String, pub bounds: Bounds, pub pressure_hint: i16,}
/// Metadata-only airflow edge. Sealed edges do not claim live flow.#[derive(Clone, Debug, PartialEq, Eq)]pub struct AirflowEdge { pub from_zone: String, pub to_zone: String, pub via: Cell, pub sealed: bool,}
/// Honest airflow topology. No voxel `flow`/`pressure` field is pre-simulated.#[derive(Clone, Debug, PartialEq, Eq)]pub struct AirflowPlan { pub zones: Vec<PressureZone>, pub edges: Vec<AirflowEdge>, pub runtime_hook: String,}
/// Special mechanics are explicit parent-binding hooks, not simulated claims.#[derive(Clone, Debug, PartialEq, Eq)]pub struct MechanicHook { pub id: String, pub anchor: Cell, pub description: String,}
/// Apply-ready plan for Milthollow or another surface world.#[derive(Clone, Debug, PartialEq, Eq)]pub struct WorldPlan { pub id: String, pub display_name: String, pub seed: u64, /// Lore population only; `actors` intentionally contains named residents. pub population_lore: u32, pub bounds: Bounds, pub start: Cell, pub writes: Vec<VoxelWrite>, pub connectors: Vec<ConnectorPlan>, pub actors: Vec<ActorSpawnPlan>, pub items: Vec<ItemSpawnPlan>, pub interactions: Vec<InteractionAnchor>, pub anchors: Vec<NamedAnchor>, pub roads: Vec<RoadPlan>,}
/// Apply-ready plan for one Deep floor.#[derive(Clone, Debug, PartialEq, Eq)]pub struct FloorPlan { pub id: String, pub name: String, pub depth: u8, pub stratum: Stratum, pub seed: u64, pub bounds: Bounds, pub start: Cell, pub writes: Vec<VoxelWrite>, pub connectors: Vec<ConnectorPlan>, pub actors: Vec<ActorSpawnPlan>, pub items: Vec<ItemSpawnPlan>, pub interactions: Vec<InteractionAnchor>, pub anchors: Vec<NamedAnchor>, pub factions: Vec<String>, pub spawn_zones: Vec<SpawnZone>, pub water_topology: String, pub airflow: AirflowPlan, pub mechanic_hooks: Vec<MechanicHook>,}
/// Why a parent must reject a plan before mutating State.#[derive(Clone, Debug, PartialEq, Eq)]pub struct PlanValidationError { pub message: String,}
impl fmt::Display for PlanValidationError { fn fmt(&self, out: &mut fmt::Formatter<'_>) -> fmt::Result { out.write_str(&self.message) }}
impl std::error::Error for PlanValidationError {}
fn invalid(message: impl Into<String>) -> PlanValidationError { PlanValidationError { message: message.into(), }}
fn solid(material: u8) -> Voxel { Voxel { material, occ: 1, ..Voxel::default() }}
fn open() -> Voxel { Voxel::default()}
fn water_depth(amount: u8) -> Voxel { Voxel { fluid_kind: fluid::WATER, fluid_amt: amount, flags: flags::WET, ..Voxel::default() }}
fn sealed_vent(material_id: u8) -> Voxel { Voxel { material: material_id, occ: 1, extra: extra::HIDDEN_VENT, ..Voxel::default() }}
fn map_writes(writes: &[VoxelWrite]) -> BTreeMap<CellKey, Voxel> { writes .iter() .map(|write| (cell_key(write.cell), write.voxel)) .collect()}
fn supported_in(map: &BTreeMap<CellKey, Voxel>, bounds: Bounds, cell: Cell) -> bool { if !bounds.contains(cell) || !bounds.contains([cell[0], cell[1], cell[2] + 1]) { return false; } let below = [cell[0], cell[1], cell[2] - 1]; bounds.contains(below) && map.get(&cell_key(cell)).is_some_and(|voxel| voxel.occ == 0) && map .get(&cell_key([cell[0], cell[1], cell[2] + 1])) .is_some_and(|voxel| voxel.occ == 0) && map .get(&cell_key(below)) .is_some_and(|voxel| voxel.occ != 0)}
/// True when `cell` can hold a standing actor: body and head are open and the/// cell immediately below is solid. This intentionally excludes shaft voids.#[must_use]pub fn is_supported_standing(writes: &[VoxelWrite], bounds: Bounds, cell: Cell) -> bool { supported_in(&map_writes(writes), bounds, cell)}
/// Plan-time movement policy. Production movement must deliberately adopt the/// same water threshold; this validator does not claim to configure runtime.#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub struct MovementPolicy { /// Maximum fluid amount accepted as shallow, plan-passable water. pub max_water_depth: u8,}
impl Default for MovementPolicy { fn default() -> Self { Self { max_water_depth: 3 } }}
fn passable_in( map: &BTreeMap<CellKey, Voxel>, bounds: Bounds, cell: Cell, policy: MovementPolicy,) -> bool { supported_in(map, bounds, cell) && map .get(&cell_key(cell)) .is_some_and(|voxel| voxel.fluid_amt <= policy.max_water_depth)}
/// Four-way, plan-passable route with explicit actor blockers and water policy.#[must_use]pub fn route_exists_with_policy( writes: &[VoxelWrite], bounds: Bounds, start: Cell, goal: Cell, actor_blockers: &[Cell], policy: MovementPolicy,) -> bool { let map = map_writes(writes); if !passable_in(&map, bounds, start, policy) || !passable_in(&map, bounds, goal, policy) { return false; } let blocked: BTreeSet<CellKey> = actor_blockers.iter().copied().map(cell_key).collect(); if blocked.contains(&cell_key(start)) || blocked.contains(&cell_key(goal)) { return false; } let mut seen = BTreeSet::from([cell_key(start)]); let mut queue = VecDeque::from([start]); while let Some(cell) = queue.pop_front() { if cell == goal { return true; } for [dx, dy] in [[1, 0], [-1, 0], [0, 1], [0, -1]] { let next = [cell[0] + dx, cell[1] + dy, cell[2]]; let key = cell_key(next); if !blocked.contains(&key) && !seen.contains(&key) && passable_in(&map, bounds, next, policy) { seen.insert(key); queue.push_back(next); } } } false}
/// Compatibility wrapper using the documented shallow-water plan policy.#[must_use]pub fn route_exists( writes: &[VoxelWrite], bounds: Bounds, start: Cell, goal: Cell, actor_blockers: &[Cell],) -> bool { route_exists_with_policy( writes, bounds, start, goal, actor_blockers, MovementPolicy::default(), )}
fn adjacent(a: Cell, b: Cell) -> bool { (a[0] - b[0]).abs() + (a[1] - b[1]).abs() + (a[2] - b[2]).abs() == 1}
fn validate_common( bounds: Bounds, start: Cell, writes: &[VoxelWrite], connectors: &[ConnectorPlan], actors: &[ActorSpawnPlan], items: &[ItemSpawnPlan], interactions: &[InteractionAnchor],) -> Result<(), PlanValidationError> { if writes.is_empty() { return Err(invalid("plan has no voxel writes")); } let mut cells = BTreeSet::new(); let mut previous = None; for write in writes { if !bounds.contains(write.cell) { return Err(invalid(format!("write {:?} is out of bounds", write.cell))); } let key = cell_key(write.cell); if previous.is_some_and(|prior| prior >= key) { return Err(invalid("writes are not unique canonical z,y,x order")); } if !cells.insert(key) { return Err(invalid(format!("duplicate write at {:?}", write.cell))); } previous = Some(key); } if !is_supported_standing(writes, bounds, start) { return Err(invalid("start lacks open body/headroom or solid support")); }
let map = map_writes(writes); let mut transition_ids = BTreeSet::new(); for connector in connectors { if !transition_ids.insert(connector.transition_id) { return Err(invalid("duplicate transition id")); } if connector.transition_id == connector.paired_transition_id { return Err(invalid("transition cannot pair with itself")); } if connector.link_id != connector.connector.id { return Err(invalid("core connector id must equal stable link id")); } if connector.connector.blocked { return Err(invalid("planned connector cannot start blocked")); } if connector.connector.depth() <= 0.0 || (connector.connector.length - connector.connector.depth()).abs() > f32::EPSILON { return Err(invalid( "connector length must equal positive vertical depth", )); } if !bounds.contains(connector.column) || !bounds.contains(connector.access) { return Err(invalid("connector column/access out of bounds")); } if !adjacent(connector.column, connector.access) { return Err(invalid("connector access must be bump-adjacent to column")); } if !is_supported_standing(writes, bounds, connector.access) { return Err(invalid("connector access lacks landing support/headroom")); } match map.get(&cell_key(connector.column)) { None => return Err(invalid("connector endpoint is not physically carved")), Some(voxel) if voxel.occ != 0 => { return Err(invalid("connector endpoint is not physically carved")); } Some(_) => {} } let endpoint = connector.connector.from.map(|value| value as i32); let other_endpoint = connector.connector.to.map(|value| value as i32); if endpoint != connector.column && other_endpoint != connector.column { return Err(invalid("core connector does not touch its local endpoint")); } if other_endpoint != connector.arrival && endpoint != connector.arrival { return Err(invalid( "core connector does not touch its declared arrival", )); } }
let mut spawn_cells = BTreeSet::new(); for actor in actors { if actor.hp <= 0 || !spawn_cells.insert(cell_key(actor.position)) { return Err(invalid("actor hp invalid or spawn position duplicated")); } if !is_supported_standing(writes, bounds, actor.position) { return Err(invalid(format!( "actor spawn {:?} is not standable", actor.position ))); } if let Some(home) = actor.home { let work = actor.work.ok_or_else(|| invalid("NPC home without work"))?; if !is_supported_standing(writes, bounds, home) || !is_supported_standing(writes, bounds, work) { return Err(invalid(format!( "NPC {} home {home:?} or work {work:?} is invalid", actor.display_name ))); } if actor.job.is_none() { return Err(invalid("NPC schedule is missing core Job")); } } } for actor in actors.iter().filter(|actor| actor.role == ActorRole::Npc) { let (Some(home), Some(work)) = (actor.home, actor.work) else { return Err(invalid("NPC is missing home/work schedule")); }; let blockers: Vec<_> = actors .iter() .filter(|other| { other.position != actor.position && other.position != home && other.position != work }) .map(|other| other.position) .collect(); if !route_exists(writes, bounds, home, work, &blockers) { return Err(invalid(format!( "NPC {} home {home:?} to work {work:?} is not plan-passable with peer blockers", actor.display_name ))); } } for item in items { if item.quantity == 0 || !spawn_cells.insert(cell_key(item.position)) { return Err(invalid( "item quantity invalid or spawn position duplicated", )); } if !is_supported_standing(writes, bounds, item.position) { return Err(invalid("item spawn is not standable")); } } for interaction in interactions { if !bounds.contains(interaction.position) || !is_supported_standing(writes, bounds, interaction.access) || !adjacent(interaction.position, interaction.access) { return Err(invalid("interaction position/access is invalid")); } if actors .iter() .any(|actor| actor.position == interaction.position) { return Err(invalid("actor spawn overlaps exact interaction cell")); } } Ok(())}
impl WorldPlan { /// Reject malformed data before the parent starts its apply transaction. pub fn validate(&self) -> Result<(), PlanValidationError> { validate_common( self.bounds, self.start, &self.writes, &self.connectors, &self.actors, &self.items, &self.interactions, )?; for road in &self.roads { if road.width == 0 || road.width % 2 == 0 || road.from[2] != road.to[2] || (road.from[0] != road.to[0] && road.from[1] != road.to[1]) { return Err(invalid(format!( "road {} has invalid centered-width semantics", road.id ))); } let radius = i32::from(road.width / 2); for offset in -radius..=radius { let (from, to) = if road.from[0] == road.to[0] { ( [road.from[0] + offset, road.from[1], road.from[2]], [road.to[0] + offset, road.to[1], road.to[2]], ) } else { ( [road.from[0], road.from[1] + offset, road.from[2]], [road.to[0], road.to[1] + offset, road.to[2]], ) }; let mut cell = from; loop { if !is_supported_standing(&self.writes, self.bounds, cell) { return Err(invalid(format!( "road {} lane {offset} is not fully carved at {cell:?}", road.id ))); } if cell == to { break; } if cell[0] != to[0] { cell[0] += (to[0] - cell[0]).signum(); } else { cell[1] += (to[1] - cell[1]).signum(); } } } } let mut anchor_ids = BTreeSet::new(); let mut feature_cells = BTreeSet::new(); let mut access_cells = BTreeSet::new(); for anchor in &self.anchors { if !anchor_ids.insert(anchor.id.as_str()) || !feature_cells.insert(cell_key(anchor.position)) || !access_cells.insert(cell_key(anchor.access)) || !self.bounds.contains(anchor.position) || !is_supported_standing(&self.writes, self.bounds, anchor.access) || !adjacent(anchor.position, anchor.access) { return Err(invalid(format!( "named anchor {} is duplicated, colliding, or invalid", anchor.id ))); } } for connector in &self.connectors { if feature_cells.contains(&cell_key(connector.column)) || access_cells.contains(&cell_key(connector.access)) { return Err(invalid("connector collides with named feature/access")); } } for actor in &self.actors { if self .interactions .iter() .any(|interaction| interaction.position == actor.position) { return Err(invalid("town actor occupies an interaction feature")); } } let blockers: Vec<_> = self.actors.iter().map(|actor| actor.position).collect(); let find_access = |predicate: fn(&InteractionKind) -> bool| { self.interactions .iter() .find(|anchor| predicate(&anchor.kind)) .map(|anchor| anchor.access) }; let landmarks = [ Some(self.start), find_access(|kind| matches!(kind, InteractionKind::NoticeBoard)), find_access(|kind| matches!(kind, InteractionKind::Inn)), find_access(|kind| matches!(kind, InteractionKind::Place { id } if id == "granary")), find_access(|kind| matches!(kind, InteractionKind::DeepEntrance { .. })), ]; if landmarks.iter().any(Option::is_none) { return Err(invalid("town lacks required player-route landmarks")); } let landmarks: Vec<_> = landmarks.into_iter().flatten().collect(); for pair in landmarks.windows(2) { if !route_exists(&self.writes, self.bounds, pair[0], pair[1], &blockers) { return Err(invalid( "required player route is not plan-passable with 13 actor blockers", )); } } Ok(()) }}
/// Validate every plan and every reciprocal typed transition as one sufficient/// pretransaction gate. The parent must call this before mutating runtime state.pub fn validate_deep_links( town: &WorldPlan, floors: &[FloorPlan],) -> Result<(), PlanValidationError> { town.validate()?; if floors.len() != 12 || floors.iter().map(|floor| floor.depth).ne(1..=12) { return Err(invalid("Deep plans must contain ordered depths 1..=12")); } for floor in floors { floor.validate()?;
let expected_link = 9_000 + u64::from(floor.depth); let mut local: Vec<_> = floor .connectors .iter() .filter(|connector| { connector.link_id >= 9_000 || connector.destination == PlanDestination::Deep { depth: floor.depth } }) .collect(); if local.len() != 2 || local .iter() .any(|connector| connector.link_id != expected_link) { return Err(invalid(format!( "floor {} must have exactly one canonical local vertical link pair", floor.depth ))); } local.sort_by_key(|connector| connector.column[2]); let lower = local[0]; let upper = local[1]; if lower.column[2] >= upper.column[2] || lower.direction != TransitionDirection::Up || upper.direction != TransitionDirection::Down || lower.destination != (PlanDestination::Deep { depth: floor.depth }) || upper.destination != (PlanDestination::Deep { depth: floor.depth }) || lower.connector.depth() <= 0.0 || lower.connector.depth().to_bits() != upper.connector.depth().to_bits() || lower.connector.from.map(f32::to_bits) != upper.connector.to.map(f32::to_bits) || lower.connector.to.map(f32::to_bits) != upper.connector.from.map(f32::to_bits) || !is_supported_standing(&floor.writes, floor.bounds, lower.access) || !is_supported_standing(&floor.writes, floor.bounds, upper.access) { return Err(invalid(format!( "floor {} local vertical link is malformed", floor.depth ))); } }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Owner { Town, Deep(u8), } let mut endpoints: BTreeMap<u64, Vec<(Owner, &ConnectorPlan)>> = BTreeMap::new(); for connector in &town.connectors { endpoints .entry(connector.link_id) .or_default() .push((Owner::Town, connector)); } for floor in floors { for connector in &floor.connectors { endpoints .entry(connector.link_id) .or_default() .push((Owner::Deep(floor.depth), connector)); } } let mut global_transition_ids = BTreeSet::new(); for pair in endpoints.values() { for (_, connector) in pair { if !global_transition_ids.insert(connector.transition_id) { return Err(invalid("transition id is duplicated across plans")); } } } for required in link_id(0)..=link_id(11) { if !endpoints.contains_key(&required) { return Err(invalid(format!("cross-plan link {required} is absent"))); } } for (link, pair) in endpoints { if pair.len() != 2 { return Err(invalid(format!( "link {link} must have exactly two endpoints" ))); } let (left_owner, left) = pair[0]; let (right_owner, right) = pair[1]; let expected_destination = |owner| match owner { Owner::Town => PlanDestination::Town, Owner::Deep(depth) => PlanDestination::Deep { depth }, }; let external = left_owner != right_owner; if (external && !(link_id(0)..=link_id(11)).contains(&link)) || (!external && link < 9_000) { return Err(invalid(format!("link {link} uses the wrong namespace"))); } let adjacent_owners = matches!( (left_owner, right_owner), (Owner::Town, Owner::Deep(1)) | (Owner::Deep(1), Owner::Town) ) || matches!((left_owner, right_owner), (Owner::Deep(a), Owner::Deep(b)) if a.abs_diff(b) == 1); if (external && !adjacent_owners) || left.destination != expected_destination(right_owner) || right.destination != expected_destination(left_owner) || left.direction == right.direction || left.transition_id != right.paired_transition_id || left.paired_transition_id != right.transition_id || left.arrival != right.column || right.arrival != left.column || left.connector.id != link || right.connector.id != link || left.connector.kind != right.connector.kind || left.connector.from.map(f32::to_bits) != right.connector.to.map(f32::to_bits) || left.connector.to.map(f32::to_bits) != right.connector.from.map(f32::to_bits) || left.connector.length.to_bits() != right.connector.length.to_bits() || left.connector.depth() <= 0.0 || left.connector.blocked || right.connector.blocked { return Err(invalid(format!( "link {link} is malformed or nonreciprocal" ))); } if external { match (left_owner, left.direction, right_owner, right.direction) { ( Owner::Town, TransitionDirection::Down, Owner::Deep(1), TransitionDirection::Up, ) | ( Owner::Deep(1), TransitionDirection::Up, Owner::Town, TransitionDirection::Down, ) => {} ( Owner::Deep(a), TransitionDirection::Down, Owner::Deep(b), TransitionDirection::Up, ) | ( Owner::Deep(b), TransitionDirection::Up, Owner::Deep(a), TransitionDirection::Down, ) if b == a + 1 => {} _ => return Err(invalid(format!("link {link} has invalid depth direction"))), } } else if !matches!((left_owner, right_owner), (Owner::Deep(a), Owner::Deep(b)) if a == b) { return Err(invalid(format!("link {link} has invalid local owners"))); } } Ok(())}
impl FloorPlan { /// Reject malformed geometry/metadata before the parent mutates State. pub fn validate(&self) -> Result<(), PlanValidationError> { if !(1..=12).contains(&self.depth) { return Err(invalid("floor depth is outside 1..=12")); } validate_common( self.bounds, self.start, &self.writes, &self.connectors, &self.actors, &self.items, &self.interactions, )?; if self.anchors.len() < 3 || self.factions.is_empty() || self.spawn_zones.is_empty() { return Err(invalid("floor lacks anchors, factions, or spawn zones")); } let mut anchor_ids = BTreeSet::new(); let mut anchor_features = BTreeSet::new(); let mut anchor_accesses = BTreeSet::new(); for anchor in &self.anchors { if !anchor_ids.insert(anchor.id.as_str()) || !anchor_features.insert(cell_key(anchor.position)) || !anchor_accesses.insert(cell_key(anchor.access)) || !self.bounds.contains(anchor.position) || !is_supported_standing(&self.writes, self.bounds, anchor.access) || !adjacent(anchor.position, anchor.access) { return Err(invalid(format!( "named anchor {} is duplicated, colliding, or invalid", anchor.id ))); } } for connector in &self.connectors { if anchor_features.contains(&cell_key(connector.column)) || anchor_accesses.contains(&cell_key(connector.access)) { return Err(invalid( "floor connector collides with named feature/access", )); } } for zone in &self.spawn_zones { if zone.template_ids.is_empty() || !self.bounds.contains(zone.bounds.min) || !self.bounds.contains(zone.bounds.max) { return Err(invalid(format!("spawn zone {} is invalid", zone.id))); } } if self.airflow.zones.is_empty() || self.airflow.edges.is_empty() { return Err(invalid("floor lacks honest airflow topology")); } let map = map_writes(&self.writes); let mut zone_ids = BTreeSet::new(); for zone in &self.airflow.zones { if !zone_ids.insert(zone.id.as_str()) || !self.bounds.contains(zone.bounds.min) || !self.bounds.contains(zone.bounds.max) { return Err(invalid(format!("pressure zone {} is invalid", zone.id))); } } for edge in &self.airflow.edges { if !zone_ids.contains(edge.from_zone.as_str()) || !zone_ids.contains(edge.to_zone.as_str()) { return Err(invalid("airflow edge references an unknown pressure zone")); } let voxel = map .get(&cell_key(edge.via)) .ok_or_else(|| invalid("airflow edge is outside writes"))?; if edge.sealed && (voxel.occ == 0 || voxel.extra & extra::HIDDEN_VENT == 0) { return Err(invalid("sealed hidden vent is not physically reveal-gated")); } } if self.mechanic_hooks.is_empty() || self .mechanic_hooks .iter() .any(|hook| !self.bounds.contains(hook.anchor)) { return Err(invalid("floor mechanic hooks are absent or out of bounds")); } if self.depth == 12 && self .connectors .iter() .any(|connector| connector.destination == PlanDestination::Deep { depth: 13 }) { return Err(invalid("floor 12 cannot connect to depth 13")); } let objective = self .interactions .iter() .find(|anchor| matches!(anchor.kind, InteractionKind::Objective { .. })) .ok_or_else(|| invalid("floor lacks objective interaction"))? .access; let exit = if self.depth < 12 { self.connectors .iter() .find(|connector| { connector.direction == TransitionDirection::Down && connector.destination == PlanDestination::Deep { depth: self.depth + 1, } }) .map(|connector| connector.access) } else { self.interactions .iter() .find(|anchor| matches!(anchor.kind, InteractionKind::Return)) .map(|anchor| anchor.access) } .ok_or_else(|| invalid("floor lacks canonical exit/return"))?; let blockers: Vec<_> = self.actors.iter().map(|actor| actor.position).collect(); if !route_exists(&self.writes, self.bounds, self.start, objective, &blockers) || !route_exists(&self.writes, self.bounds, objective, exit, &blockers) { return Err(invalid( "floor canonical route is not plan-passable with actor blockers", )); } Ok(()) }}
#[derive(Clone, Copy)]struct Rect { x0: i32, y0: i32, x1: i32, y1: i32,}
fn put(map: &mut BTreeMap<CellKey, Voxel>, cell: Cell, voxel: Voxel) { map.insert(cell_key(cell), voxel);}
fn carve_cell(map: &mut BTreeMap<CellKey, Voxel>, cell: Cell) { put(map, cell, open()); put(map, [cell[0], cell[1], cell[2] + 1], open());}
fn carve_rect(map: &mut BTreeMap<CellKey, Voxel>, rect: Rect, walk_z: i32) { for y in rect.y0..=rect.y1 { for x in rect.x0..=rect.x1 { carve_cell(map, [x, y, walk_z]); } }}
fn carve_line(map: &mut BTreeMap<CellKey, Voxel>, from: Cell, to: Cell) { let mut cell = from; carve_cell(map, cell); while cell[0] != to[0] { cell[0] += (to[0] - cell[0]).signum(); carve_cell(map, cell); } while cell[1] != to[1] { cell[1] += (to[1] - cell[1]).signum(); carve_cell(map, cell); }}
fn carve_column(map: &mut BTreeMap<CellKey, Voxel>, bounds: Bounds, column: Cell) { for z in bounds.min[2]..=bounds.max[2] { put(map, [column[0], column[1], z], open()); }}
fn writes_from(map: BTreeMap<CellKey, Voxel>) -> Vec<VoxelWrite> { map.into_iter() .map(|((z, y, x), voxel)| VoxelWrite { cell: [x, y, z], voxel, }) .collect()}
fn link_id(upper_depth: u8) -> u64 { 1_000 + u64::from(upper_depth)}
fn upper_transition(link: u64) -> u64 { link * 2}
fn lower_transition(link: u64) -> u64 { link * 2 + 1}
fn depth_connector_kind(upper_depth: u8) -> ConnectorKind { match upper_depth % 4 { 0 => ConnectorKind::Stairwell, 1 => ConnectorKind::Shaft, 2 => ConnectorKind::Well, _ => ConnectorKind::ElevatorCar, }}
fn core_connector( id: u64, kind: ConnectorKind, local: Cell, destination: Cell, quality: u8,) -> Connector { Connector { id, kind, from: local.map(|value| value as f32), to: destination.map(|value| value as f32), length: (local[2] - destination[2]).abs() as f32, anchor_quality: quality, load_kg: 0.0, blocked: false, }}
fn carve_roads(map: &mut BTreeMap<CellKey, Voxel>, roads: &[RoadPlan]) { for road in roads { let radius = i32::from(road.width / 2); for offset in -radius..=radius { let (from, to) = if road.from[0] == road.to[0] { ( [road.from[0] + offset, road.from[1], road.from[2]], [road.to[0] + offset, road.to[1], road.to[2]], ) } else { ( [road.from[0], road.from[1] + offset, road.from[2]], [road.to[0], road.to[1] + offset, road.to[2]], ) }; carve_line(map, from, to); } }}
fn sort_world(plan: &mut WorldPlan) { plan.writes.sort_by_key(|write| cell_key(write.cell)); plan.connectors .sort_by_key(|connector| connector.transition_id); plan.actors.sort_by_key(|actor| cell_key(actor.position)); plan.items.sort_by_key(|item| cell_key(item.position)); plan.interactions .sort_by_key(|anchor| cell_key(anchor.position)); plan.anchors.sort_by_key(|anchor| cell_key(anchor.position)); plan.roads.sort_by(|left, right| left.id.cmp(&right.id));}
fn sort_floor(plan: &mut FloorPlan) { plan.writes.sort_by_key(|write| cell_key(write.cell)); plan.connectors .sort_by_key(|connector| connector.transition_id); plan.actors.sort_by_key(|actor| cell_key(actor.position)); plan.items.sort_by_key(|item| cell_key(item.position)); plan.interactions .sort_by_key(|anchor| cell_key(anchor.position)); plan.anchors.sort_by_key(|anchor| cell_key(anchor.position)); plan.spawn_zones .sort_by(|left, right| left.id.cmp(&right.id));}
/// Construct deterministic Milthollow geometry and named spawn requests.pub(crate) fn build_milthollow(seed: u64) -> WorldPlan { let bounds = Bounds { min: [0, 0, 7], max: [42, 32, 10], }; let walk_z = 8; let mut map = BTreeMap::new(); for z in bounds.min[2]..=bounds.max[2] { for y in bounds.min[1]..=bounds.max[1] { for x in bounds.min[0]..=bounds.max[0] { let voxel = if z == bounds.min[2] || x == bounds.min[0] || x == bounds.max[0] || y == bounds.min[1] || y == bounds.max[1] { solid(material::STONE) } else { open() }; put(&mut map, [x, y, z], voxel); } } }
let roads = vec![ RoadPlan { id: "king-road".into(), from: [21, 31, walk_z], to: [21, 2, walk_z], width: 3, }, RoadPlan { id: "mill-lane".into(), from: [2, 12, walk_z], to: [40, 12, walk_z], width: 3, }, RoadPlan { id: "garden-way".into(), from: [8, 29, walk_z], to: [34, 29, walk_z], width: 3, }, ]; let buildings = [ Rect { x0: 3, y0: 3, x1: 12, y1: 9, }, Rect { x0: 16, y0: 3, x1: 26, y1: 9, }, Rect { x0: 30, y0: 3, x1: 39, y1: 9, }, Rect { x0: 3, y0: 16, x1: 11, y1: 23, }, Rect { x0: 15, y0: 16, x1: 24, y1: 23, }, Rect { x0: 28, y0: 16, x1: 39, y1: 23, }, ]; for rect in buildings { for y in rect.y0..=rect.y1 { for x in rect.x0..=rect.x1 { let wall = x == rect.x0 || x == rect.x1 || y == rect.y0 || y == rect.y1; if wall { put(&mut map, [x, y, walk_z], solid(material::WOOD)); put(&mut map, [x, y, walk_z + 1], solid(material::WOOD)); } else { carve_cell(&mut map, [x, y, walk_z]); } put(&mut map, [x, y, walk_z + 2], solid(material::WOOD)); } } } for door in [ [8, 9, walk_z], [21, 9, walk_z], [34, 9, walk_z], [7, 16, walk_z], [20, 16, walk_z], [34, 16, walk_z], ] { carve_cell(&mut map, door); carve_line(&mut map, door, [door[0], 12, walk_z]); } // Roads are final authoritative public-space cuts through building shells. carve_roads(&mut map, &roads);
let link = link_id(0); let entrance_column = [23, 6, walk_z]; let entrance_access = [22, 6, walk_z]; let floor_one_column = [2, 10, -9]; carve_column(&mut map, bounds, entrance_column); let connector = ConnectorPlan { transition_id: upper_transition(link), paired_transition_id: lower_transition(link), link_id: link, direction: TransitionDirection::Down, destination: PlanDestination::Deep { depth: 1 }, column: entrance_column, access: entrance_access, arrival: floor_one_column, connector: core_connector( link, ConnectorKind::Stairwell, entrance_column, floor_one_column, 100, ), };
let anchor_rows = [ ( "notice_board", "Milthollow notice board", [21, 26, walk_z], [21, 25, walk_z], ), ( "gilded_whetstone", "The Gilded Whetstone", [8, 5, walk_z], [8, 6, walk_z], ), ( "chapel", "chapel of the Lamplighter", [21, 5, walk_z], [20, 5, walk_z], ), ("mill", "Odo's water mill", [34, 5, walk_z], [33, 5, walk_z]), ( "granary", "Milthollow granary", [25, 6, walk_z], [24, 6, walk_z], ), ( "tinker", "Milthollow tinker stall", [7, 19, walk_z], [8, 19, walk_z], ), ( "lampwright", "Lampwrights' Guild counter", [18, 19, walk_z], [19, 19, walk_z], ), ( "warden", "Milthollow warden post", [31, 19, walk_z], [32, 19, walk_z], ), ( "mender", "Mender Sal's surgery", [37, 19, walk_z], [36, 19, walk_z], ), ( "fence", "Congregation fence", [35, 7, walk_z], [34, 7, walk_z], ), ( "auction", "Gilded Audit auction floor", [37, 7, walk_z], [36, 7, walk_z], ), ( "corpse_garden", "Mother Rust's corpse-tender garden", [10, 29, walk_z], [11, 29, walk_z], ), ]; let anchors: Vec<NamedAnchor> = anchor_rows .iter() .map(|(id, label, position, access)| NamedAnchor { id: (*id).into(), label: (*label).into(), position: *position, access: *access, }) .collect(); let interactions: Vec<InteractionAnchor> = anchor_rows .iter() .map(|(id, label, position, access)| { let kind = match *id { "notice_board" => InteractionKind::NoticeBoard, "gilded_whetstone" => InteractionKind::Inn, "mill" | "granary" | "corpse_garden" => InteractionKind::Place { id: (*id).into() }, _ => InteractionKind::Service { id: (*id).into() }, }; InteractionAnchor { position: *position, access: *access, kind, label: (*label).into(), } }) .chain(std::iter::once(InteractionAnchor { position: entrance_column, access: entrance_access, kind: InteractionKind::DeepEntrance { link_id: link }, label: "granary trapdoor to the Deep".into(), })) .collect();
let folk = [ ( "Tomm", Job::Lamplighter, [12, 27, walk_z], [20, 24, walk_z], [20, 27, walk_z], ), ( "Mara", Job::Miller, [5, 13, walk_z], [33, 6, walk_z], [32, 6, walk_z], ), ( "Clem", Job::Tinker, [5, 25, walk_z], [8, 19, walk_z], [8, 18, walk_z], ), ( "Guildlamp", Job::Lampwright, [14, 25, walk_z], [19, 19, walk_z], [19, 18, walk_z], ), ( "Warden Beck", Job::Warden, [25, 25, walk_z], [32, 19, walk_z], [32, 18, walk_z], ), ( "Mender Sal", Job::Mender, [35, 25, walk_z], [36, 19, walk_z], [36, 18, walk_z], ), ( "Skive", Job::Fence, [29, 13, walk_z], [34, 7, walk_z], [34, 6, walk_z], ), ( "Hammer", Job::Auctioneer, [36, 13, walk_z], [36, 7, walk_z], [36, 6, walk_z], ), ( "Whetstone", Job::Innkeep, [6, 13, walk_z], [8, 6, walk_z], [7, 6, walk_z], ), ( "Mother Rust", Job::CorpseTender, [13, 28, walk_z], [11, 29, walk_z], [12, 29, walk_z], ), ( "Chaplain Odo", Job::Chaplain, [21, 13, walk_z], [20, 5, walk_z], [20, 6, walk_z], ), ( "Coach Ivo", Job::CoachDriver, [31, 27, walk_z], [31, 13, walk_z], [30, 13, walk_z], ), ( "Beggar Nan", Job::Beggar, [18, 27, walk_z], [21, 25, walk_z], [22, 25, walk_z], ), ]; let actors = folk .into_iter() .map(|(name, job, home, work, position)| ActorSpawnPlan { // Distinguished generic plan ID: current content has no NPC templates. template_id: "townsfolk".into(), display_name: name.into(), role: ActorRole::Npc, position, hp: 10, home: Some(home), work: Some(work), job: Some(job), }) .collect(); let variation = (seed % 3) as i32; let items = vec![ ItemSpawnPlan { template_id: "lamp_oil".into(), position: [17 + variation, 21, walk_z], quantity: 2, }, ItemSpawnPlan { template_id: "chalk_stick".into(), position: [25 + variation, 13, walk_z], quantity: 1, }, ]; let mut plan = WorldPlan { id: "milthollow".into(), display_name: "Milthollow".into(), seed, population_lore: 312, bounds, start: [21, 31, walk_z], writes: writes_from(map), connectors: vec![connector], actors, items, interactions, anchors, roads, }; sort_world(&mut plan); debug_assert!( plan.validate().is_ok(), "generated Milthollow must validate: {:?}", plan.validate() ); plan}
struct FloorContent { id: &'static str, name: &'static str, anchors: [&'static str; 3], factions: [&'static str; 2], water: &'static str, hook: &'static str,}
fn floor_content(depth: u8) -> FloorContent { match depth { 1 => FloorContent { id: "floor_01_mill_cellars", name: "Mill Cellars", anchors: [ "granary trapdoor room", "millrace culvert", "tinker cache nook", ], factions: ["Lampwrights' Guild", "Salvage Congregation"], water: "millrace seep bends through the wheel pit", hook: "rats_in_granary contract completion binds to the cracked relay nest", }, 2 => FloorContent { id: "floor_02_sewer_chapel", name: "Sewer Relay", anchors: [ "flooded relay station", "grate-mother ventilation nest", "audit way-station", ], factions: ["The Gilded Audit", "The Drowned Choir"], water: "forked sewer channels feed a central sump", hook: "grate-mother drops remain a parent-owned encounter hook", }, 3 => FloorContent { id: "floor_03_relay_station", name: "Copperwash Undermere", anchors: [ "sunken chapel of the Drowned Saint", "rat-king nest warren", "wardens' seal-door", ], factions: ["Wardens of the Seal", "The Drowned Choir"], water: "broad undermere pool surrounds the chapel rail", hook: "rat-king puzzle and seal-door state bind in production", }, 4 => FloorContent { id: "floor_04_counterweight_landing", name: "Counterweight Landing", anchors: ["elevator hall", "counterweight gallery", "flooded shaft"], factions: ["Wardens of the Seal", "Salvage Congregation"], water: "seal-stair runoff drains into the car pit", hook: "moving elevator and counterweight simulation is parent-bound", }, 5 => FloorContent { id: "floor_05_audit_vaults", name: "Audit Vaults", anchors: ["audit vault", "elevator hall", "server shrine"], factions: ["The Gilded Audit", "Salvage Congregation"], water: "narrow cable trenches lead to the vault sump", hook: "lien payment and vault alarms are interaction hooks", }, 6 => FloorContent { id: "floor_06_drowned_relay", name: "Drowned Relay", anchors: ["flooded shaft", "server shrine", "counterweight gallery"], factions: ["The Drowned Choir", "Wardens of the Seal"], water: "deep relay basin crosses the shrine transept", hook: "Saint of Rust bargain/flood escalation is parent-bound", }, 7 => FloorContent { id: "floor_07_server_shrines", name: "Server Shrines", anchors: ["server shrine", "audit vault", "elevator hall"], factions: ["The Gilded Audit", "Lampwrights' Guild"], water: "parallel cooling channels divide shrine aisles", hook: "server hymn modifiers require session binding", }, 8 => FloorContent { id: "floor_08_toll_gallery", name: "Toll Gallery", anchors: ["counterweight gallery", "flooded shaft", "audit vault"], factions: ["Wardens of the Seal", "The Drowned Choir"], water: "muster pool lies below the dry union bridge", hook: "toll negotiation is a parent interaction hook", }, 9 => FloorContent { id: "floor_09_choir_loft", name: "Choir Loft", anchors: ["choir loft", "dream chapel", "sideways fall"], factions: ["The Drowned Choir", "Wardens of the Seal"], water: "black water arcs around the only dry span", hook: "Choir name-song and sideways gravity are explicit unsimulated hooks", }, 10 => FloorContent { id: "floor_10_sideways_fall", name: "Sideways Fall", anchors: ["sideways fall", "saint throne", "choir loft"], factions: ["The Drowned Choir", "The Gilded Audit"], water: "wallward channel marks the gravity-fall axis", hook: "sideways gravity and Audit Seraph bargain are parent-bound", }, 11 => FloorContent { id: "floor_11_saint_throne", name: "Saint Throne", anchors: ["saint throne", "dream chapel", "idle gate"], factions: ["The Drowned Choir", "Wardens of the Seal"], water: "throne mere fills the eastern audience chamber", hook: "Drowned Saint conversation/fight state binds in session", }, 12 => FloorContent { id: "floor_12_the_vault", name: "The Vault", anchors: ["idle gate", "saint throne", "choir loft"], factions: ["The Drowned Choir", "Wardens of the Seal"], water: "still black water terminates at the idle gate", hook: "Vault Choice SEAL/CROWN and return are parent-owned ending hooks", }, _ => unreachable!("depth checked"), }}
fn monster_rows(depth: u8) -> [(&'static str, &'static str, i32); 3] { match depth { 1 => [ ("cable_rat", "Cable-rat", 6), ("cable_rat", "Cable-rat", 6), ("cable_rat", "Cable-rat", 6), ], 2 => [ ("cable_rat", "Cable-rat", 6), ("grate_mother", "Grate-mother", 30), ("drowned_bell", "Drowned bell", 14), ], 3 => [ ("cable_rat", "Cable-rat", 6), ("vendric", "Mimic-vending (Vendric)", 22), ("rat_king", "Rat-king", 60), ], 4 => [ ("cherub_flock", "Audit cherub flock", 24), ("ledger_mite", "Ledger mite", 4), ("shaft_rook", "Shaft rook", 18), ], 5 => [ ("counterweight_wraith", "Counterweight wraith", 36), ("verdigris_ooze", "Verdigris ooze", 28), ("ledger_mite", "Ledger mite", 4), ], 6 => [ ("saint_of_rust", "Saint of Rust", 80), ("drowned_surveyor", "Drowned surveyor", 33), ("verdigris_ooze", "Verdigris ooze", 28), ], 7 => [ ("counterweight_wraith", "Counterweight wraith", 36), ("audit_bailiff", "Audit bailiff", 50), ("flood_verger", "Flood verger", 32), ], 8 => [ ("ledger_mite", "Ledger mite", 4), ("gallow_troll", "Gallow-troll veteran", 70), ("debt_swallow", "Debt swallow", 6), ], 9 => [ ("choir_cantor", "Choir cantor", 40), ("sideways_saint", "Sideways saint", 52), ("toll_matriarch", "Toll-troll matriarch", 120), ], 10 => [ ("audit_seraph", "Audit seraph", 140), ("gravity_apostate", "Gravity apostate", 38), ("debt_swallow", "Debt swallow", 6), ], 11 => [ ("drowned_saint", "The Drowned Saint", 160), ("rust_archon", "Rust archon", 48), ("core_tick", "Core tick", 20), ], 12 => [ ("drowned_saint", "The Drowned Saint", 160), ("core_tick", "Core tick", 20), ("idle_dream", "Idle dream", 42), ], _ => unreachable!("depth checked"), }}
fn item_ids(depth: u8) -> [&'static str; 3] { const BASELINE: [&str; 12] = [ "lamp_oil", "rope_10m", "pitons_5", "pole_3m", "net_catch", "sledgehammer", "hand_bell", "flour_sack", "chalk_stick", "copper_wire_coil", "fresh_aether_cell", "hymn_card_blank", ]; let offset = usize::from(depth.saturating_sub(1)) % BASELINE.len(); [ BASELINE[offset], BASELINE[(offset + 4) % BASELINE.len()], BASELINE[(offset + 8) % BASELINE.len()], ]}
fn paint_water_line(map: &mut BTreeMap<CellKey, Voxel>, from: Cell, to: Cell, amount: u8) { let mut cell = from; carve_cell(map, cell); put(map, cell, water_depth(amount)); while cell[0] != to[0] { cell[0] += (to[0] - cell[0]).signum(); carve_cell(map, cell); put(map, cell, water_depth(amount)); } while cell[1] != to[1] { cell[1] += (to[1] - cell[1]).signum(); carve_cell(map, cell); put(map, cell, water_depth(amount)); }}
fn carve_canonical_topology( depth: u8, walk_z: i32, shift: i32, variant: u8, wall_material: u8, map: &mut BTreeMap<CellKey, Voxel>,) -> Vec<Cell> { let mut vents = Vec::new(); match depth { 1 => { // Granary/mill cellar bays divided by a narrow wheel-pit millrace. carve_rect( map, Rect { x0: 3, y0: 6, x1: 12 + shift, y1: 15, }, walk_z, ); carve_rect( map, Rect { x0: 18 + shift, y0: 7, x1: 29, y1: 14, }, walk_z, ); paint_water_line(map, [2, 11, walk_z], [30, 11, walk_z], 2); carve_line(map, [14, 8, walk_z], [14, 15, walk_z]); } 2 => { // Knee-deep relay sewer forks north/south; two separate solid vent // routes mark west crawlspace and east relay bypass. paint_water_line(map, [3, 10, walk_z], [28, 10, walk_z], 3); paint_water_line(map, [15 + shift, 10, walk_z], [15 + shift, 3, walk_z], 3); paint_water_line(map, [20, 10, walk_z], [20, 19, walk_z], 3); for x in 20..=25 { let cell = [x, 2, walk_z]; put(map, cell, sealed_vent(wall_material)); put(map, [x, 2, walk_z + 1], sealed_vent(wall_material)); vents.push(cell); } for y in 15..=20 { let cell = [31, y, walk_z]; put(map, cell, sealed_vent(wall_material)); put(map, [31, y, walk_z + 1], sealed_vent(wall_material)); vents.push(cell); } } 3 => { // Broad, deeper undermere; three dry islands are the sunken chapel, // rat-king warren, and wardens' seal-door approach. for y in 6..=16 { for x in 3..=29 { put(map, [x, y, walk_z], water_depth(5)); } } for island in [ Rect { x0: 5, y0: 8, x1: 10, y1: 13, }, Rect { x0: 14 + shift, y0: 7, x1: 20 + shift, y1: 12, }, Rect { x0: 24, y0: 9, x1: 29, y1: 14, }, ] { carve_rect(map, island, walk_z); } carve_line(map, [2, 10, walk_z], [30, 10, walk_z]); carve_line(map, [16 + shift, 10, walk_z], [16 + shift, 12, walk_z]); } _ => { // Nine depth-selected anchor overlays (not modulo-three): each adds // a distinct branch/room signature around its canonical anchors. let mode = usize::from(depth - 4); let y = 2 + (mode as i32 % 5) * 4; let x = 4 + (mode as i32 * 3 % 20); match mode { 0 => carve_rect( map, Rect { x0: 11, y0: 2, x1: 22, y1: 7, }, walk_z, ), 1 => { carve_line(map, [2, 4, walk_z], [30, 18, walk_z]); } 2 => carve_rect( map, Rect { x0: 3, y0: 3, x1: 29, y1: 6, }, walk_z, ), 3 => { carve_line(map, [4, 2, walk_z], [4, 20, walk_z]); carve_line(map, [28, 2, walk_z], [28, 20, walk_z]); } 4 => carve_rect( map, Rect { x0: 10, y0: 15, x1: 22, y1: 21, }, walk_z, ), 5 => { carve_line(map, [2, 5, walk_z], [30, 5, walk_z]); carve_line(map, [16, 5, walk_z], [16, 20, walk_z]); } 6 => carve_rect( map, Rect { x0: 3, y0: 2, x1: 9, y1: 20, }, walk_z, ), 7 => { carve_line(map, [2, 18, walk_z], [30, 18, walk_z]); carve_rect( map, Rect { x0: 20, y0: 2, x1: 30, y1: 8, }, walk_z, ); } _ => carve_rect( map, Rect { x0: 12, y0: 2, x1: 20, y1: 20, }, walk_z, ), } carve_line(map, [x, 2, walk_z], [x, y.min(20), walk_z]); if variant & 1 != 0 { carve_line(map, [2, y.min(20), walk_z], [30, y.min(20), walk_z]); } let channel_y = 3 + (mode as i32 * 2 % 16); if mode % 2 == 0 { paint_water_line( map, [3, channel_y, walk_z], [14 + mode as i32, channel_y, walk_z], 2 + (mode % 2) as u8, ); } else { let channel_x = 5 + (mode as i32 * 2 % 20); paint_water_line( map, [channel_x, 3, walk_z], [channel_x, 11 + mode as i32, walk_z], 2 + (mode % 2) as u8, ); } } } vents}
fn floor_connectors( depth: u8, bounds: Bounds, walk_z: i32, map: &mut BTreeMap<CellKey, Voxel>,) -> Vec<ConnectorPlan> { let mut connectors = Vec::new(); let up_link = link_id(depth - 1); let up_column = [2, 10, walk_z]; let up_arrival = if depth == 1 { [23, 6, 8] } else { [30, 10, walk_z + 10] }; carve_column(map, bounds, up_column); connectors.push(ConnectorPlan { transition_id: lower_transition(up_link), paired_transition_id: upper_transition(up_link), link_id: up_link, direction: TransitionDirection::Up, destination: if depth == 1 { PlanDestination::Town } else { PlanDestination::Deep { depth: depth - 1 } }, column: up_column, access: [3, 10, walk_z], arrival: up_arrival, connector: core_connector( up_link, depth_connector_kind(depth - 1), up_column, up_arrival, 90, ), }); if depth < 12 { let down_link = link_id(depth); let down_column = [30, 10, walk_z]; let down_arrival = [2, 10, walk_z - 10]; carve_column(map, bounds, down_column); connectors.push(ConnectorPlan { transition_id: upper_transition(down_link), paired_transition_id: lower_transition(down_link), link_id: down_link, direction: TransitionDirection::Down, destination: PlanDestination::Deep { depth: depth + 1 }, column: down_column, access: [29, 10, walk_z], arrival: down_arrival, connector: core_connector( down_link, depth_connector_kind(depth), down_column, down_arrival, 72 + depth, ), }); } // A genuine two-level local structure: the lower cistern landing and raised // gallery landing are both carved, supported, and independently actionable. let local_link = 9_000 + u64::from(depth); let lower = [8, 3, walk_z]; let upper = [8, 3, walk_z + 4]; let lower_access = [9, 3, walk_z]; let upper_access = [9, 3, walk_z + 4]; carve_column(map, bounds, lower); carve_cell(map, lower_access); carve_cell(map, upper_access); // Raised-gallery support and a short gallery apron. put( map, [upper_access[0], upper_access[1], upper_access[2] - 1], solid(material::IRON), ); carve_line(map, upper_access, [13, 3, upper_access[2]]); for x in 9..=13 { put(map, [x, 3, upper_access[2] - 1], solid(material::IRON)); } connectors.push(ConnectorPlan { transition_id: upper_transition(local_link), paired_transition_id: lower_transition(local_link), link_id: local_link, direction: TransitionDirection::Up, destination: PlanDestination::Deep { depth }, column: lower, access: lower_access, arrival: upper, connector: core_connector(local_link, ConnectorKind::Vent, lower, upper, 65 + depth), }); connectors.push(ConnectorPlan { transition_id: lower_transition(local_link), paired_transition_id: upper_transition(local_link), link_id: local_link, direction: TransitionDirection::Down, destination: PlanDestination::Deep { depth }, column: upper, access: upper_access, arrival: lower, connector: core_connector(local_link, ConnectorKind::Vent, upper, lower, 65 + depth), }); connectors}
/// Construct one deterministic floor. Geometry is apply-ready; mechanic hooks/// remain inert until a production session binds their interactions.pub(crate) fn build_playable_floor(master_seed: u64, depth: u8) -> FloorPlan { assert!((1..=12).contains(&depth), "Deep depth must be in 1..=12"); let content = floor_content(depth); let stratum = crate::stratum_for(depth); let base_z = -i32::from(depth) * 10; let walk_z = base_z + 1; let bounds = Bounds { min: [0, 0, base_z], // Main floor plus a physically supported raised utility gallery. max: [32, 22, base_z + 7], }; let floor_seed = master_seed ^ u64::from(depth).wrapping_mul(0x9E37_79B9_7F4A_7C15); let mixed = floor_seed ^ floor_seed.rotate_left(17) ^ master_seed.wrapping_mul(0xA24B_AED4_963E_E407); let shift = (mixed % 3) as i32 - 1; let spawn_shift = ((mixed >> 11) % 3) as i32 - 1; let wall_material = match stratum { Stratum::Shallows => material::STONE, Stratum::Works => material::IRON, Stratum::Deep => material::SAINT_BONE, }; let mut map = BTreeMap::new(); for z in bounds.min[2]..=bounds.max[2] { for y in 0..=22 { for x in 0..=32 { put(&mut map, [x, y, z], solid(wall_material)); } } } let rooms = [ Rect { x0: 1, y0: 7, x1: 10 + shift, y1: 14, }, Rect { x0: 12 + shift, y0: 5, x1: 21 + shift, y1: 16, }, Rect { x0: 23 + shift, y0: 7, x1: 31, y1: 14, }, Rect { x0: 6, y0: 1, x1: 12, y1: 5, }, Rect { x0: 20, y0: 17, x1: 27, y1: 21, }, ]; for room in rooms { carve_rect(&mut map, room, walk_z); } let centers = [ [5, 10, walk_z], [16 + shift, 10, walk_z], [27, 10, walk_z], [9, 3, walk_z], [23, 19, walk_z], ]; for pair in centers.windows(2) { carve_line(&mut map, pair[0], pair[1]); } // Depth-specific branch geometry materially changes topology. if depth % 3 == 1 { carve_line(&mut map, [5, 8, walk_z], [27, 8 + shift, walk_z]); } else if depth % 3 == 2 { carve_line(&mut map, [8, 3, walk_z], [8 + shift, 19, walk_z]); carve_line(&mut map, [8 + shift, 19, walk_z], [24, 19, walk_z]); } else { carve_rect( &mut map, Rect { x0: 13, y0: 8, x1: 20, y1: 13 + shift, }, walk_z, ); carve_line(&mut map, [2, 10, walk_z], [30, 10, walk_z]); }
let variant = ((mixed >> 19) & 0xff) as u8; let mut vent_positions = carve_canonical_topology(depth, walk_z, shift, variant, wall_material, &mut map); if vent_positions.is_empty() { let count = 1 + usize::from(depth % 3); for index in 0..count { let vent_position = [ 14 + index as i32 * 2 + shift, if depth % 2 == 0 { 5 } else { 16 }, walk_z, ]; let vent_access = [vent_position[0] - 1, vent_position[1], walk_z]; carve_cell(&mut map, vent_access); put(&mut map, vent_position, sealed_vent(wall_material)); put( &mut map, [vent_position[0], vent_position[1], walk_z + 1], sealed_vent(wall_material), ); vent_positions.push(vent_position); } } // Independent seed-driven corridor/branch decisions materially alter the // voxel projection while preserving canonical anchor rooms and routes. if depth >= 4 { for (bit, y) in [(0, 4), (1, 7), (2, 15), (3, 18)] { if variant & (1 << bit) != 0 { let end = 9 + i32::from((variant >> (bit + 4)) & 0x03) * 5; carve_line(&mut map, [3, y, walk_z], [end.min(30), y, walk_z]); } } }
let connectors = floor_connectors(depth, bounds, walk_z, &mut map); let objective = [16 + shift, 11, walk_z]; let objective_feature = [16 + shift, 12, walk_z]; carve_cell(&mut map, objective); put(&mut map, objective_feature, solid(material::BRASS)); let up = connectors .iter() .find(|connector| { connector.direction == TransitionDirection::Up && connector.destination != PlanDestination::Deep { depth } }) .expect("up connector"); let down = connectors.iter().find(|connector| { connector.direction == TransitionDirection::Down && connector.destination != PlanDestination::Deep { depth } }); let objective_id = match depth { 1 => "rats_in_granary".into(), 9 => "choir_sign".into(), 12 => "vault_choice".into(), _ => format!("floor_{depth:02}_objective"), }; let mut interactions = vec![ InteractionAnchor { position: up.column, access: up.access, kind: InteractionKind::Up { link_id: up.link_id, }, label: "upward passage".into(), }, InteractionAnchor { position: objective_feature, access: objective, kind: InteractionKind::Objective { id: objective_id }, label: if depth == 9 { "The Choir sings your name".into() } else if depth == 12 { "Vault Choice: SEAL or CROWN".into() } else { content.name.into() }, }, ]; if let Some(down) = down { interactions.push(InteractionAnchor { position: down.column, access: down.access, kind: InteractionKind::Down { link_id: down.link_id, }, label: "downward passage".into(), }); } else { interactions.push(InteractionAnchor { position: [27, 13, walk_z], access: [27, 12, walk_z], kind: InteractionKind::Return, label: "return after the Vault Choice".into(), }); } let anchor_positions = [[5, 10, walk_z], [16 + shift, 8, walk_z], [27, 10, walk_z]]; let anchors = content .anchors .into_iter() .zip(anchor_positions) .map(|(label, access)| NamedAnchor { id: label.replace([' ', '\'', '-'], "_").to_lowercase(), label: label.into(), position: [access[0], access[1] + 1, access[2]], access, }) .collect(); let monster_positions = [ [7 + shift, 13 + spawn_shift, walk_z], [17 + shift, 15 + spawn_shift, walk_z], [25 + shift, 12 + spawn_shift, walk_z], ]; for position in monster_positions { carve_cell(&mut map, position); } let rows = monster_rows(depth); let rotation = ((mixed >> 27) % 3) as usize; let actors = monster_positions .into_iter() .enumerate() .map(|(index, position)| { let (template_id, display_name, hp) = rows[(index + rotation) % rows.len()]; ActorSpawnPlan { template_id: template_id.into(), display_name: display_name.into(), role: ActorRole::Monster, position, hp, home: None, work: None, job: None, } }) .collect(); let item_positions = [ [6 + shift, 5, walk_z], [15 + shift, 17, walk_z], [26 + shift, 6, walk_z], ]; for position in item_positions { carve_cell(&mut map, position); } let items = item_ids(depth) .into_iter() .zip(item_positions) .map(|(template_id, position)| ItemSpawnPlan { template_id: template_id.into(), position, quantity: 1, }) .collect(); let factions = content .factions .into_iter() .map(str::to_owned) .collect::<Vec<_>>(); let spawn_zones = vec![ SpawnZone { id: "near-zone".into(), bounds: Bounds { min: [3, 7, walk_z], max: [10, 15, walk_z + 1], }, faction: Some(factions[0].clone()), template_ids: vec![monster_rows(depth)[0].0.into()], }, SpawnZone { id: "far-zone".into(), bounds: Bounds { min: [22, 7, walk_z], max: [29, 16, walk_z + 1], }, faction: Some(factions[1].clone()), template_ids: vec![monster_rows(depth)[2].0.into()], }, ]; let airflow = AirflowPlan { zones: vec![ PressureZone { id: "entry-air".into(), bounds: Bounds { min: [2, 7, walk_z], max: [10, 15, walk_z + 1], }, pressure_hint: 4, }, PressureZone { id: "anchor-air".into(), bounds: Bounds { min: [12, 5, walk_z], max: [21, 16, walk_z + 1], }, pressure_hint: -4, }, ], edges: if depth == 2 { vec![vent_positions[0], vent_positions[6]] } else { vent_positions.clone() } .into_iter() .map(|via| AirflowEdge { from_zone: "entry-air".into(), to_zone: "anchor-air".into(), via, sealed: true, }) .collect(), runtime_hook: "parent computes live pressure/flow after apply and opens this edge only when revealed" .into(), }; let mechanic_hooks = vec![MechanicHook { id: format!("floor_{depth:02}_special"), anchor: objective_feature, description: format!("parent binds this hook in production: {}", content.hook), }]; let mut plan = FloorPlan { id: content.id.into(), name: content.name.into(), depth, stratum, seed: floor_seed, bounds, start: up.access, writes: writes_from(map), connectors, actors, items, interactions, anchors, factions, spawn_zones, water_topology: content.water.into(), airflow, mechanic_hooks, }; sort_floor(&mut plan); debug_assert!( plan.validate().is_ok(), "generated floor {depth} must validate: {:?}", plan.validate() ); plan}