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::{AuthoringError, WaymarkError};
use super::functions::FunctionRegistry;
use super::schema::DreamwellPackV1;
use super::templates::TemplateRegistry;
use super::topology::{TopologyLayer, TopologyNode, TopologyTree};
use super::transitions::TransitionRegistry;
use crate::physics::heuristics::HeuristicEngine;
use crate::physics::presets::PresetRegistry;
use crate::physics::properties::PropertyValue;
use crate::physics::semantic_binding::SemanticBinding;
use crate::physics::tags::TagRegistry;
use std::collections::HashMap;

/// Waymark Simulation Semantics — unified entry point for semantic resolution.
pub struct WaymarkSimSemantics {
    pub tag_registry: TagRegistry,
    pub function_registry: FunctionRegistry,
    pub heuristic_engine: HeuristicEngine,
    pub preset_registry: PresetRegistry,
    pub template_registry: TemplateRegistry,
    pub transition_registry: TransitionRegistry,
    pub topology_tree: TopologyTree,
}

#[derive(Debug, Clone, Default)]
pub struct ValidationReport {
    pub errors: Vec<WaymarkError>,
    pub warnings: Vec<String>,
}

impl ValidationReport {
    pub fn is_valid(&self) -> bool {
        self.errors.is_empty()
    }
}

impl WaymarkSimSemantics {
    pub fn new() -> Self {
        Self {
            tag_registry: TagRegistry::new(),
            function_registry: FunctionRegistry::new(),
            heuristic_engine: HeuristicEngine::new(),
            preset_registry: PresetRegistry::new(),
            template_registry: TemplateRegistry::new(),
            transition_registry: TransitionRegistry::new(),
            topology_tree: TopologyTree::new(),
        }
    }

    pub fn with_builtins() -> Self {
        let mut tag_registry = TagRegistry::new();
        tag_registry.register_builtins();
        Self {
            tag_registry,
            function_registry: FunctionRegistry::with_builtins(),
            heuristic_engine: HeuristicEngine::with_builtins(),
            preset_registry: PresetRegistry::with_builtins(),
            template_registry: TemplateRegistry::with_builtins(),
            transition_registry: TransitionRegistry::new(),
            topology_tree: TopologyTree::new(),
        }
    }

    pub fn from_pack(pack: &DreamwellPackV1) -> Result<Self, Vec<WaymarkError>> {
        let mut sem = Self::with_builtins();
        let mut errors = Vec::new();

        // Build topology from pack
        let universe_name = pack
            .topology
            .universe_name
            .clone()
            .unwrap_or_else(|| pack.title.clone());
        let _ = sem.topology_tree.insert(TopologyNode {
            layer: TopologyLayer::Universe,
            id: format!("universe:{}", pack.id),
            name: universe_name,
            parent_id: None,
            properties: HashMap::new(),
            tags: vec![],
        });

        // Add galaxies
        for g in &pack.topology.galaxies {
            let _ = sem.topology_tree.insert(TopologyNode {
                layer: TopologyLayer::Galaxy,
                id: g.id.clone(),
                name: g.name.clone(),
                parent_id: Some(format!("universe:{}", pack.id)),
                properties: HashMap::new(),
                tags: vec!["isNavigableGalaxy".into()],
            });
        }

        // Add worlds
        for w in &pack.topology.worlds {
            let parent = if w.sector_id.is_empty() {
                pack.topology
                    .galaxies
                    .first()
                    .map(|g| g.id.clone())
                    .or(Some(format!("universe:{}", pack.id)))
            } else {
                Some(w.sector_id.clone())
            };
            let mut props = HashMap::new();
            props.insert(
                "gravity.planetary".into(),
                PropertyValue::Float(pack.physics.gravity_planetary),
            );
            props.insert(
                "atmosphere.density".into(),
                PropertyValue::Float(pack.physics.atmosphere_density),
            );
            let _ = sem.topology_tree.insert(TopologyNode {
                layer: TopologyLayer::World,
                id: w.id.clone(),
                name: w.name.clone(),
                parent_id: parent,
                properties: props,
                tags: vec!["isHabitableWorld".into()],
            });
        }

        // Add regions
        for r in &pack.topology.regions {
            let parent = if r.realm_id.is_empty() {
                pack.topology.worlds.first().map(|w| w.id.clone())
            } else {
                Some(r.realm_id.clone())
            };
            let mut props = HashMap::new();
            if let Some(grav) = pack.physics.gravity_local {
                props.insert("gravity.local".into(), PropertyValue::Float(grav));
            }
            props.insert(
                "temperature.ambient".into(),
                PropertyValue::Float(pack.physics.temperature_ambient),
            );
            let _ = sem.topology_tree.insert(TopologyNode {
                layer: TopologyLayer::Region,
                id: r.id.clone(),
                name: r.name.clone(),
                parent_id: parent,
                properties: props,
                tags: vec![],
            });
        }

        // Validate topology
        errors.extend(sem.topology_tree.validate());

        if errors.is_empty() {
            Ok(sem)
        } else {
            Err(errors)
        }
    }

    pub fn validate_tag(&self, name: &str) -> Result<(), WaymarkError> {
        if self.tag_registry.find(name).is_some() {
            Ok(())
        } else {
            Err(WaymarkError::Authoring(AuthoringError::UnknownTag(name.into())))
        }
    }

    pub fn validate_signal(&self, name: &str) -> Result<(), WaymarkError> {
        if name.starts_with("signal.") && self.tag_registry.find(name).is_some() {
            Ok(())
        } else {
            Err(WaymarkError::Authoring(AuthoringError::UnknownSignal(name.into())))
        }
    }

    pub fn validate_receiver(&self, name: &str) -> Result<(), WaymarkError> {
        if name.starts_with("receive.") && self.tag_registry.find(name).is_some() {
            Ok(())
        } else {
            Err(WaymarkError::Authoring(AuthoringError::UnknownReceiver(name.into())))
        }
    }

    pub fn validate_function(&self, key: &str) -> Result<(), WaymarkError> {
        self.function_registry.validate_key(key)
    }

    pub fn validate_semantic_binding(&self, binding: &SemanticBinding) -> Vec<WaymarkError> {
        let mut errors = Vec::new();
        for tag in &binding.trait_tags {
            if let Err(e) = self.validate_tag(tag) {
                errors.push(e);
            }
        }
        for sig in &binding.signal_tags {
            if let Err(e) = self.validate_signal(sig) {
                errors.push(e);
            }
        }
        for recv in &binding.receiver_tags {
            if let Err(e) = self.validate_receiver(recv) {
                errors.push(e);
            }
        }
        errors
    }

    pub fn resolve_property(&self, node_id: &str, key: &str) -> Option<PropertyValue> {
        self.topology_tree.resolve_property(node_id, key)
    }

    pub fn resolve_property_with_default(&self, node_id: &str, key: &str) -> PropertyValue {
        self.topology_tree
            .resolve_property_with_default(node_id, key, &self.function_registry)
    }

    pub fn validate_all(&self) -> ValidationReport {
        let mut report = ValidationReport::default();
        report.errors.extend(self.topology_tree.validate());
        report
            .errors
            .extend(self.transition_registry.validate(&self.topology_tree));
        report
    }

    pub fn apply_template(&mut self, node_id: &str, template_id: &str) -> Result<(), WaymarkError> {
        let template = self
            .template_registry
            .find(template_id)
            .ok_or_else(|| WaymarkError::Authoring(AuthoringError::InvalidTemplateReference(template_id.into())))?
            .clone();
        if let Some(node) = self.topology_tree.nodes_mut().get_mut(node_id) {
            node.tags.extend(template.tags);
            for (k, v) in template.properties {
                node.properties.insert(k, v);
            }
            Ok(())
        } else {
            Err(WaymarkError::Topology(
                super::errors::TopologyError::InvalidTopologyBinding(node_id.into()),
            ))
        }
    }
}

impl Default for WaymarkSimSemantics {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn with_builtins_populated() {
        let sem = WaymarkSimSemantics::with_builtins();
        assert!(sem.tag_registry.len() >= 94);
        assert_eq!(sem.function_registry.len(), 23);
        assert_eq!(sem.heuristic_engine.rules.len(), 10);
        assert!(
            sem.preset_registry.len() >= 180,
            "preset_registry has {} presets",
            sem.preset_registry.len()
        );
        assert_eq!(sem.template_registry.len(), 49);
    }

    #[test]
    fn validate_tag_valid() {
        let sem = WaymarkSimSemantics::with_builtins();
        assert!(sem.validate_tag("isFlammable").is_ok());
    }

    #[test]
    fn validate_tag_invalid() {
        let sem = WaymarkSimSemantics::with_builtins();
        assert!(sem.validate_tag("nonexistent_tag").is_err());
    }

    #[test]
    fn validate_signal_valid() {
        let sem = WaymarkSimSemantics::with_builtins();
        assert!(sem.validate_signal("signal.ignited").is_ok());
    }

    #[test]
    fn validate_receiver_valid() {
        let sem = WaymarkSimSemantics::with_builtins();
        assert!(sem.validate_receiver("receive.fire").is_ok());
    }

    #[test]
    fn validate_function_valid() {
        let sem = WaymarkSimSemantics::with_builtins();
        assert!(sem.validate_function("gravity.local").is_ok());
    }

    #[test]
    fn validate_function_invalid() {
        let sem = WaymarkSimSemantics::with_builtins();
        assert!(sem.validate_function("nonexistent.function").is_err());
    }

    #[test]
    fn resolve_with_default() {
        let sem = WaymarkSimSemantics::with_builtins();
        let val = sem.resolve_property_with_default("any", "gravity.local");
        assert_eq!(val, PropertyValue::Float(9.81));
    }

    #[test]
    fn validate_all_clean() {
        let sem = WaymarkSimSemantics::with_builtins();
        let report = sem.validate_all();
        assert!(report.is_valid());
    }

    #[test]
    fn validate_semantic_binding() {
        let sem = WaymarkSimSemantics::with_builtins();
        let binding = SemanticBinding {
            trait_tags: vec!["isFlammable".into(), "isWall".into()],
            signal_tags: vec!["signal.ignited".into()],
            receiver_tags: vec!["receive.fire".into()],
            property_defaults: vec![],
        };
        let errors = sem.validate_semantic_binding(&binding);
        assert!(errors.is_empty());
    }
}