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 super::errors::{TopologyError, WaymarkError};
use super::functions::FunctionRegistry;
use crate::physics::properties::PropertyValue;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum TopologyLayer {
    Universe = 0,
    Galaxy = 1,
    Sector = 2,
    World = 3,
    Realm = 4,
    Region = 5,
    Area = 6,
    Location = 7,
    Room = 8,
    Point = 9,
}

impl TopologyLayer {
    pub const ALL: &[TopologyLayer] = &[
        Self::Universe,
        Self::Galaxy,
        Self::Sector,
        Self::World,
        Self::Realm,
        Self::Region,
        Self::Area,
        Self::Location,
        Self::Room,
        Self::Point,
    ];

    pub fn from_u8(v: u8) -> Option<Self> {
        match v {
            0 => Some(Self::Universe),
            1 => Some(Self::Galaxy),
            2 => Some(Self::Sector),
            3 => Some(Self::World),
            4 => Some(Self::Realm),
            5 => Some(Self::Region),
            6 => Some(Self::Area),
            7 => Some(Self::Location),
            8 => Some(Self::Room),
            9 => Some(Self::Point),
            _ => None,
        }
    }

    pub fn name(&self) -> &'static str {
        match self {
            Self::Universe => "Universe",
            Self::Galaxy => "Galaxy",
            Self::Sector => "Sector",
            Self::World => "World",
            Self::Realm => "Realm",
            Self::Region => "Region",
            Self::Area => "Area",
            Self::Location => "Location",
            Self::Room => "Room",
            Self::Point => "Point",
        }
    }

    pub fn parent_layer(&self) -> Option<TopologyLayer> {
        match self {
            Self::Universe => None,
            Self::Galaxy => Some(Self::Universe),
            Self::Sector => Some(Self::Galaxy),
            Self::World => Some(Self::Sector),
            Self::Realm => Some(Self::World),
            Self::Region => Some(Self::Realm),
            Self::Area => Some(Self::Region),
            Self::Location => Some(Self::Area),
            Self::Room => Some(Self::Location),
            Self::Point => Some(Self::Room),
        }
    }

    pub fn depth(&self) -> u8 {
        *self as u8
    }

    /// Cell size in world units for this topology layer.
    /// Cosmic layers use large cells; room/point layers use unit cells.
    pub fn cell_size(self) -> i32 {
        match self {
            Self::Universe | Self::Galaxy => 1_048_576,
            Self::Sector | Self::World => 65_536,
            Self::Realm | Self::Region => 4_096,
            Self::Area | Self::Location => 128,
            Self::Room | Self::Point => 1,
        }
    }

    /// GPU-uploadable index (u32 representation of the layer).
    pub fn index(self) -> u32 {
        self as u32
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TopologyNode {
    pub layer: TopologyLayer,
    pub id: String,
    pub name: String,
    pub parent_id: Option<String>,
    pub properties: HashMap<String, PropertyValue>,
    pub tags: Vec<String>,
}

#[derive(Debug, Clone, Default)]
pub struct TopologyTree {
    nodes: HashMap<String, TopologyNode>,
    children: HashMap<String, Vec<String>>,
}

impl TopologyTree {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn insert(&mut self, node: TopologyNode) -> Result<(), WaymarkError> {
        if let Some(ref pid) = node.parent_id {
            if let Some(parent) = self.nodes.get(pid) {
                if node.layer <= parent.layer {
                    return Err(WaymarkError::Topology(TopologyError::InvalidParentChildRelation {
                        parent: pid.clone(),
                        child: node.id.clone(),
                    }));
                }
            }
            self.children.entry(pid.clone()).or_default().push(node.id.clone());
        }
        self.nodes.insert(node.id.clone(), node);
        Ok(())
    }

    pub fn get(&self, id: &str) -> Option<&TopologyNode> {
        self.nodes.get(id)
    }

    pub fn children_of(&self, id: &str) -> Vec<&str> {
        self.children
            .get(id)
            .map(|v| v.iter().map(|s| s.as_str()).collect())
            .unwrap_or_default()
    }

    pub fn ancestor_chain(&self, id: &str) -> Vec<&TopologyNode> {
        let mut chain = Vec::new();
        let mut current = id;
        while let Some(node) = self.nodes.get(current) {
            chain.push(node);
            match &node.parent_id {
                Some(pid) => current = pid,
                None => break,
            }
        }
        chain
    }

    pub fn resolve_property(&self, node_id: &str, key: &str) -> Option<PropertyValue> {
        for node in self.ancestor_chain(node_id) {
            if let Some(val) = node.properties.get(key) {
                return Some(val.clone());
            }
        }
        None
    }

    pub fn resolve_property_with_default(
        &self,
        node_id: &str,
        key: &str,
        registry: &FunctionRegistry,
    ) -> PropertyValue {
        if let Some(val) = self.resolve_property(node_id, key) {
            return val;
        }
        registry
            .find(key)
            .map(|f| f.default_value.clone())
            .unwrap_or(PropertyValue::Float(0.0))
    }

    pub fn len(&self) -> usize {
        self.nodes.len()
    }
    pub fn is_empty(&self) -> bool {
        self.nodes.is_empty()
    }

    pub fn nodes_mut(&mut self) -> &mut HashMap<String, TopologyNode> {
        &mut self.nodes
    }

    pub fn validate(&self) -> Vec<WaymarkError> {
        let mut errors = Vec::new();
        for node in self.nodes.values() {
            if let Some(ref pid) = node.parent_id {
                if !self.nodes.contains_key(pid) {
                    errors.push(WaymarkError::Topology(TopologyError::InvalidParentChildRelation {
                        parent: pid.clone(),
                        child: node.id.clone(),
                    }));
                }
            }
        }
        errors
    }
}

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

    #[test]
    fn layer_all_count() {
        assert_eq!(TopologyLayer::ALL.len(), 10);
    }

    #[test]
    fn layer_ordering() {
        assert!(TopologyLayer::Universe < TopologyLayer::Galaxy);
        assert!(TopologyLayer::Galaxy < TopologyLayer::World);
        assert!(TopologyLayer::Room < TopologyLayer::Point);
    }

    #[test]
    fn layer_parent() {
        assert_eq!(TopologyLayer::Galaxy.parent_layer(), Some(TopologyLayer::Universe));
        assert_eq!(TopologyLayer::Universe.parent_layer(), None);
    }

    #[test]
    fn tree_insert_and_get() {
        let mut tree = TopologyTree::new();
        tree.insert(TopologyNode {
            layer: TopologyLayer::Universe,
            id: "u1".into(),
            name: "Test".into(),
            parent_id: None,
            properties: HashMap::new(),
            tags: vec![],
        })
        .unwrap();
        assert!(tree.get("u1").is_some());
    }

    #[test]
    fn tree_ancestor_chain() {
        let mut tree = TopologyTree::new();
        tree.insert(TopologyNode {
            layer: TopologyLayer::Universe,
            id: "u".into(),
            name: "U".into(),
            parent_id: None,
            properties: HashMap::new(),
            tags: vec![],
        })
        .unwrap();
        tree.insert(TopologyNode {
            layer: TopologyLayer::Galaxy,
            id: "g".into(),
            name: "G".into(),
            parent_id: Some("u".into()),
            properties: HashMap::new(),
            tags: vec![],
        })
        .unwrap();
        tree.insert(TopologyNode {
            layer: TopologyLayer::World,
            id: "w".into(),
            name: "W".into(),
            parent_id: Some("g".into()),
            properties: HashMap::new(),
            tags: vec![],
        })
        .unwrap();
        let chain = tree.ancestor_chain("w");
        assert_eq!(chain.len(), 3);
        assert_eq!(chain[0].id, "w");
        assert_eq!(chain[2].id, "u");
    }

    #[test]
    fn resolve_property_inheritance() {
        let mut tree = TopologyTree::new();
        let mut props = HashMap::new();
        props.insert("gravity.planetary".into(), PropertyValue::Float(3.71));
        tree.insert(TopologyNode {
            layer: TopologyLayer::World,
            id: "w".into(),
            name: "W".into(),
            parent_id: None,
            properties: props,
            tags: vec![],
        })
        .unwrap();
        tree.insert(TopologyNode {
            layer: TopologyLayer::Region,
            id: "r".into(),
            name: "R".into(),
            parent_id: Some("w".into()),
            properties: HashMap::new(),
            tags: vec![],
        })
        .unwrap();
        let val = tree.resolve_property("r", "gravity.planetary");
        assert_eq!(val, Some(PropertyValue::Float(3.71)));
    }

    #[test]
    fn resolve_property_local_override() {
        let mut tree = TopologyTree::new();
        let mut world_props = HashMap::new();
        world_props.insert("gravity.planetary".into(), PropertyValue::Float(9.81));
        let mut region_props = HashMap::new();
        region_props.insert("gravity.planetary".into(), PropertyValue::Float(1.62));
        tree.insert(TopologyNode {
            layer: TopologyLayer::World,
            id: "w".into(),
            name: "W".into(),
            parent_id: None,
            properties: world_props,
            tags: vec![],
        })
        .unwrap();
        tree.insert(TopologyNode {
            layer: TopologyLayer::Region,
            id: "r".into(),
            name: "R".into(),
            parent_id: Some("w".into()),
            properties: region_props,
            tags: vec![],
        })
        .unwrap();
        let val = tree.resolve_property("r", "gravity.planetary");
        assert_eq!(val, Some(PropertyValue::Float(1.62)));
    }

    #[test]
    fn resolve_with_function_default() {
        let tree = TopologyTree::new();
        let reg = FunctionRegistry::with_builtins();
        let val = tree.resolve_property_with_default("nonexistent", "gravity.local", &reg);
        assert_eq!(val, PropertyValue::Float(9.81));
    }

    #[test]
    fn cell_size_cosmic_layers() {
        assert_eq!(TopologyLayer::Universe.cell_size(), 1_048_576);
        assert_eq!(TopologyLayer::Galaxy.cell_size(), 1_048_576);
    }

    #[test]
    fn cell_size_world_layers() {
        assert_eq!(TopologyLayer::Sector.cell_size(), 65_536);
        assert_eq!(TopologyLayer::World.cell_size(), 65_536);
    }

    #[test]
    fn cell_size_region_layers() {
        assert_eq!(TopologyLayer::Realm.cell_size(), 4_096);
        assert_eq!(TopologyLayer::Region.cell_size(), 4_096);
    }

    #[test]
    fn cell_size_local_layers() {
        assert_eq!(TopologyLayer::Area.cell_size(), 128);
        assert_eq!(TopologyLayer::Location.cell_size(), 128);
        assert_eq!(TopologyLayer::Room.cell_size(), 1);
        assert_eq!(TopologyLayer::Point.cell_size(), 1);
    }

    #[test]
    fn index_matches_repr() {
        for layer in TopologyLayer::ALL {
            assert_eq!(layer.index(), layer.depth() as u32);
        }
    }

    #[test]
    fn validate_orphaned_node() {
        let mut tree = TopologyTree::new();
        tree.insert(TopologyNode {
            layer: TopologyLayer::Region,
            id: "r".into(),
            name: "R".into(),
            parent_id: Some("missing".into()),
            properties: HashMap::new(),
            tags: vec![],
        })
        .unwrap();
        let errors = tree.validate();
        assert_eq!(errors.len(), 1);
    }
}