//! Canonical vocabulary for actions that have crossed the input boundary. use crate::services::Service; /// Eight explicit movement directions; invalid deltas cannot enter a journal. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Direction { North, South, West, East, NorthWest, NorthEast, SouthWest, SouthEast, } impl Direction { /// Horizontal voxel delta for this direction, in the heading convention /// (north is -Y, matching `Heading` bin 0; east is +X; south is +Y). #[must_use] pub const fn delta(&self) -> (i32, i32) { match self { Self::North => (0, -1), Self::South => (0, 1), Self::West => (-1, 0), Self::East => (1, 0), Self::NorthWest => (-1, -1), Self::NorthEast => (1, -1), Self::SouthWest => (-1, 1), Self::SouthEast => (1, 1), } } /// Stable canonical tag for wire and canonical-hash encoding. Zero is /// reserved for "no direction"; the save wire table delegates here so the /// journal and the pending-action hash cannot drift. #[must_use] pub const fn canonical_tag(&self) -> u8 { match self { Self::North => 1, Self::South => 2, Self::West => 3, Self::East => 4, Self::NorthWest => 5, Self::NorthEast => 6, Self::SouthWest => 7, Self::SouthEast => 8, } } } /// Typed committed actions. Strings are canonical content/town identifiers, /// never terminal command text. #[derive(Clone, Debug, PartialEq, Eq)] pub enum CommittedAction { Move(Direction), Wait, InspectBoard { town: String }, AcceptContract { id: String }, UseTransition { transition_id: u64 }, ResolveAttack { target_actor: u64 }, Pickup { item_id: u64 }, Drop { item_id: u64 }, Wield { item_id: u64 }, TinkerIdentify { item_id: u64 }, TurnIn { contract_id: String }, UseService { service: Service }, } #[cfg(test)] mod tests { use super::*; #[test] fn all_direction_deltas_are_explicit() { assert_eq!(Direction::North.delta(), (0, -1)); assert_eq!(Direction::South.delta(), (0, 1)); assert_eq!(Direction::West.delta(), (-1, 0)); assert_eq!(Direction::East.delta(), (1, 0)); assert_eq!(Direction::NorthWest.delta(), (-1, -1)); assert_eq!(Direction::NorthEast.delta(), (1, -1)); assert_eq!(Direction::SouthWest.delta(), (-1, 1)); assert_eq!(Direction::SouthEast.delta(), (1, 1)); } /// Cardinal deltas must agree with the canonical `Heading` unit vectors so /// the action journal and the locomotion authority cannot drift apart. #[test] fn cardinal_deltas_match_heading_convention() { for (direction, heading) in [ (Direction::North, crate::heading::Heading::NORTH), (Direction::East, crate::heading::Heading::EAST), (Direction::South, crate::heading::Heading::SOUTH), (Direction::West, crate::heading::Heading::WEST), ] { let [x, y] = heading.unit_vector(); let (dx, dy) = direction.delta(); assert_eq!( (i64::from(dx), i64::from(dy)), (x.signum(), y.signum()), "{direction:?} must match its heading unit vector" ); } } }