//! games-ground — the GROUND rules aggregate (specs/GroundRules.md, //! GameKernel K15–K16). T07 scaffolds the state shell; validate/fold per //! GR-rule land in T08 with rule IDs cross-referenced in doc comments. use cb_kernel::PlayerId; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; /// GR-O02: per-player state. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PlayerState { /// GR-F01: clamped 0–5. pub stress: u8, /// GR-F03. pub freedom_ready: bool, /// GR-D01/D02: OFF or the pending/active stage. pub darvo: DarvoStage, pub hand: Vec, pub protection: u8, /// Blame tokens in front of this player (GR-T02), keyed by owner. pub blame_from: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum DarvoStage { Off, Deny, Attack, Reverse, } /// GR-O04: one Problem card's live state. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProblemState { pub suit: Suit, pub value: u8, pub face_up: bool, pub denied: bool, pub claimed_by: Option, /// GR-A11: protected from Deny this round by GROUND—OU. pub protected_this_round: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum Suit { Clarify, Repair, Boundary, Change, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct SolutionCard { pub suit: Suit, } /// GR-O05: at most one relation per pair; endpoints ordered low→high. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Relation { Bond, Rivalry, } /// GR-O01..O05: the authoritative GROUND aggregate. Fields use ordered /// collections only (GameKernel K6). #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct GroundState { pub round: u8, pub lead: PlayerId, pub players: BTreeMap, /// Keyed by (low, high) player pair. pub relations: BTreeMap<(PlayerId, PlayerId), Relation>, pub problems: BTreeMap, pub solution_deck: Vec, pub solution_discard: Vec, /// Focus placements: sequence owner → target (GR-T03). pub focus: BTreeMap, } #[cfg(test)] mod tests { use super::*; use cb_events::state_hash_hex; fn tiny_state() -> GroundState { GroundState { round: 1, lead: PlayerId(0), players: BTreeMap::from([( PlayerId(0), PlayerState { stress: 2, freedom_ready: true, darvo: DarvoStage::Off, hand: vec![SolutionCard { suit: Suit::Repair }], protection: 0, blame_from: vec![], }, )]), relations: BTreeMap::new(), problems: BTreeMap::new(), solution_deck: vec![], solution_discard: vec![], focus: BTreeMap::new(), } } /// K7 on the real aggregate: hash stable across clones, sensitive to /// semantic change. #[test] fn ground_state_hashes_canonically() { let a = tiny_state(); let b = a.clone(); assert_eq!(state_hash_hex(&a), state_hash_hex(&b)); let mut c = a.clone(); c.players.get_mut(&PlayerId(0)).unwrap().stress = 5; assert_ne!(state_hash_hex(&a), state_hash_hex(&c)); } }