dreamwell-engine 1.0.0

Dreamwell pure-logic engine library — transforms, hierarchy, canon pipeline, spatial math, hashing, tile rules, validation, waymark schema, material/lighting descriptors. No SpacetimeDB dependency.
Documentation
use serde::{Deserialize, Serialize};

/// Promotion target — what a particle becomes when promoted to gameplay entity.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PromotionTarget {
    Pickup,
    Hazard,
    Interactable,
    WorldScar,
    PersistentDebris,
    ResourceNode,
    AIStimulus,
}

/// Promotion authority — who decides promotion.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PromotionAuthority {
    ClientSuggestsServerConfirms,
    ServerOnly,
    EventDeterministic,
}

/// Promotion rule — condition for a particle to be promoted.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromotionRule {
    pub when: String,
    pub min_age: f32,
    pub requires_tags: Vec<String>,
    pub set_tags: Vec<String>,
}

/// Promotion policy for an emitter.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromotionPolicy {
    pub enabled: bool,
    pub target: PromotionTarget,
    pub rules: Vec<PromotionRule>,
    pub authority: PromotionAuthority,
}

impl Default for PromotionPolicy {
    fn default() -> Self {
        Self {
            enabled: false,
            target: PromotionTarget::Pickup,
            rules: Vec::new(),
            authority: PromotionAuthority::ClientSuggestsServerConfirms,
        }
    }
}

impl PromotionTarget {
    pub const ALL: &[PromotionTarget] = &[
        Self::Pickup,
        Self::Hazard,
        Self::Interactable,
        Self::WorldScar,
        Self::PersistentDebris,
        Self::ResourceNode,
        Self::AIStimulus,
    ];

    pub fn name(&self) -> &'static str {
        match self {
            Self::Pickup => "Pickup",
            Self::Hazard => "Hazard",
            Self::Interactable => "Interactable",
            Self::WorldScar => "World Scar",
            Self::PersistentDebris => "Persistent Debris",
            Self::ResourceNode => "Resource Node",
            Self::AIStimulus => "AI Stimulus",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn promotion_target_all() {
        assert_eq!(PromotionTarget::ALL.len(), 7);
    }

    #[test]
    fn promotion_policy_default() {
        let p = PromotionPolicy::default();
        assert!(!p.enabled);
    }

    #[test]
    fn promotion_policy_serde() {
        let p = PromotionPolicy {
            enabled: true,
            target: PromotionTarget::Pickup,
            authority: PromotionAuthority::ClientSuggestsServerConfirms,
            rules: vec![PromotionRule {
                when: "resting".into(),
                min_age: 1.5,
                requires_tags: vec!["isDebris".into(), "isStone".into()],
                set_tags: vec!["isPickupable".into(), "signal.promoted".into()],
            }],
        };
        let json = serde_json::to_string(&p).unwrap();
        let restored: PromotionPolicy = serde_json::from_str(&json).unwrap();
        assert_eq!(restored.rules.len(), 1);
    }
}