//! Build intents (wiki/mechanics/building.md). //! //! A build is a pinned job, not a subsystem: declare an intent, then an //! actuator from social.md / messages.md / reach.md realizes it. This module //! owns only the intent queue and its status machine; the sim routes //! realization through existing favor, forged-message, and `connect_devices` //! paths (wiki/mechanics/system-laws.md: "Building: intent and actuators"; wiki/interface/presence.md: "No disembodied //! hands", "Self-similar scale"). use serde::{Deserialize, Serialize}; use crate::reach::Device; /// How an intent is being (or will be) realized. The signature follows the /// actuator, never a channel-independent "build" kind. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum BuildActuator { /// Willing person: spends obligation; quiet human-work signature. Favor { person: u8 }, /// Unwitting person via a forged work-order message (false source). ForgedOrder { builder: u8 }, /// Controlled machine builds directly. Staged stub (people before robots) /// so the interface slots in without a rewrite; emits physical-by-proxy. Robot, } impl BuildActuator { pub fn label(self) -> &'static str { match self { BuildActuator::Favor { .. } => "favor", BuildActuator::ForgedOrder { .. } => "forged order", BuildActuator::Robot => "robot", } } pub fn person(self) -> Option { match self { BuildActuator::Favor { person } => Some(person), BuildActuator::ForgedOrder { builder } => Some(builder), BuildActuator::Robot => None, } } } /// What the intent wants done. B1 ships the network-link shape; other kinds /// share the same status machine when they land. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum IntentKind { /// Run a physical network cable between two devices (reach.md edge). NetworkLink { a: u32, b: u32 }, } impl IntentKind { pub fn label(self) -> &'static str { match self { IntentKind::NetworkLink { .. } => "network link", } } /// Endpoints in canonical order (min, max) so equality is order-independent. pub fn endpoints(self) -> Option<(u32, u32)> { match self { IntentKind::NetworkLink { a, b } => Some((a.min(b), a.max(b))), } } } /// Legible lifecycle. Blocked carries a reason the player can read before /// committing an actuator (honesty clause). #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum IntentStatus { /// Declared; no actuator assigned. Changes nothing in the world. Pending, /// An actuator is assigned and working toward completion. InProgress, /// Requirements unmet; see [`BuildIntent::block_reason`]. Blocked, /// Realized; the world effect has landed. Done, /// Player cancelled; inert. Cancelled, } impl IntentStatus { pub fn label(self) -> &'static str { match self { IntentStatus::Pending => "pending", IntentStatus::InProgress => "in progress", IntentStatus::Blocked => "blocked", IntentStatus::Done => "done", IntentStatus::Cancelled => "cancelled", } } } /// One pinned build job. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BuildIntent { pub id: u64, pub kind: IntentKind, pub status: IntentStatus, /// Assigned actuator, if any. pub actuator: Option, /// Player-facing blocker when status is Blocked (or waiting on a read). pub block_reason: Option, /// Tick the intent was declared (provenance for the log / save). pub declared_tick: u64, } impl BuildIntent { pub fn network_link(id: u64, a: u32, b: u32, tick: u64) -> Self { Self { id, kind: IntentKind::NetworkLink { a, b }, status: IntentStatus::Pending, actuator: None, block_reason: None, declared_tick: tick, } } pub fn is_open(&self) -> bool { matches!( self.status, IntentStatus::Pending | IntentStatus::InProgress | IntentStatus::Blocked ) } pub fn label(&self, devices: &[Device]) -> String { match self.kind { IntentKind::NetworkLink { a, b } => { let name = |id: u32| { devices .iter() .find(|d| d.id == id) .map(|d| d.name.clone()) .unwrap_or_else(|| format!("device:{id}")) }; format!("{} <-> {}", name(a), name(b)) } } } pub fn status_line(&self) -> String { match (&self.status, &self.block_reason) { (IntentStatus::Blocked, Some(r)) => format!("blocked: {r}"), (status, Some(r)) => format!("{} ({r})", status.label()), (status, None) => status.label().into(), } } }