Skip to main content

dreamwell_engine/waymark/
semantics.rs

1use super::errors::{AuthoringError, WaymarkError};
2use super::functions::FunctionRegistry;
3use super::schema::DreamwellPackV1;
4use super::templates::TemplateRegistry;
5use super::topology::{TopologyLayer, TopologyNode, TopologyTree};
6use super::transitions::TransitionRegistry;
7use crate::physics::heuristics::HeuristicEngine;
8use crate::physics::presets::PresetRegistry;
9use crate::physics::properties::PropertyValue;
10use crate::physics::semantic_binding::SemanticBinding;
11use crate::physics::tags::TagRegistry;
12use std::collections::HashMap;
13
14/// Waymark Simulation Semantics — unified entry point for semantic resolution.
15pub struct WaymarkSimSemantics {
16    pub tag_registry: TagRegistry,
17    pub function_registry: FunctionRegistry,
18    pub heuristic_engine: HeuristicEngine,
19    pub preset_registry: PresetRegistry,
20    pub template_registry: TemplateRegistry,
21    pub transition_registry: TransitionRegistry,
22    pub topology_tree: TopologyTree,
23}
24
25#[derive(Debug, Clone, Default)]
26pub struct ValidationReport {
27    pub errors: Vec<WaymarkError>,
28    pub warnings: Vec<String>,
29}
30
31impl ValidationReport {
32    pub fn is_valid(&self) -> bool {
33        self.errors.is_empty()
34    }
35}
36
37impl WaymarkSimSemantics {
38    pub fn new() -> Self {
39        Self {
40            tag_registry: TagRegistry::new(),
41            function_registry: FunctionRegistry::new(),
42            heuristic_engine: HeuristicEngine::new(),
43            preset_registry: PresetRegistry::new(),
44            template_registry: TemplateRegistry::new(),
45            transition_registry: TransitionRegistry::new(),
46            topology_tree: TopologyTree::new(),
47        }
48    }
49
50    pub fn with_builtins() -> Self {
51        let mut tag_registry = TagRegistry::new();
52        tag_registry.register_builtins();
53        Self {
54            tag_registry,
55            function_registry: FunctionRegistry::with_builtins(),
56            heuristic_engine: HeuristicEngine::with_builtins(),
57            preset_registry: PresetRegistry::with_builtins(),
58            template_registry: TemplateRegistry::with_builtins(),
59            transition_registry: TransitionRegistry::new(),
60            topology_tree: TopologyTree::new(),
61        }
62    }
63
64    pub fn from_pack(pack: &DreamwellPackV1) -> Result<Self, Vec<WaymarkError>> {
65        let mut sem = Self::with_builtins();
66        let mut errors = Vec::new();
67
68        // Build topology from pack
69        let universe_name = pack
70            .topology
71            .universe_name
72            .clone()
73            .unwrap_or_else(|| pack.title.clone());
74        let _ = sem.topology_tree.insert(TopologyNode {
75            layer: TopologyLayer::Universe,
76            id: format!("universe:{}", pack.id),
77            name: universe_name,
78            parent_id: None,
79            properties: HashMap::new(),
80            tags: vec![],
81        });
82
83        // Add galaxies
84        for g in &pack.topology.galaxies {
85            let _ = sem.topology_tree.insert(TopologyNode {
86                layer: TopologyLayer::Galaxy,
87                id: g.id.clone(),
88                name: g.name.clone(),
89                parent_id: Some(format!("universe:{}", pack.id)),
90                properties: HashMap::new(),
91                tags: vec!["isNavigableGalaxy".into()],
92            });
93        }
94
95        // Add worlds
96        for w in &pack.topology.worlds {
97            let parent = if w.sector_id.is_empty() {
98                pack.topology
99                    .galaxies
100                    .first()
101                    .map(|g| g.id.clone())
102                    .or(Some(format!("universe:{}", pack.id)))
103            } else {
104                Some(w.sector_id.clone())
105            };
106            let mut props = HashMap::new();
107            props.insert(
108                "gravity.planetary".into(),
109                PropertyValue::Float(pack.physics.gravity_planetary),
110            );
111            props.insert(
112                "atmosphere.density".into(),
113                PropertyValue::Float(pack.physics.atmosphere_density),
114            );
115            let _ = sem.topology_tree.insert(TopologyNode {
116                layer: TopologyLayer::World,
117                id: w.id.clone(),
118                name: w.name.clone(),
119                parent_id: parent,
120                properties: props,
121                tags: vec!["isHabitableWorld".into()],
122            });
123        }
124
125        // Add regions
126        for r in &pack.topology.regions {
127            let parent = if r.realm_id.is_empty() {
128                pack.topology.worlds.first().map(|w| w.id.clone())
129            } else {
130                Some(r.realm_id.clone())
131            };
132            let mut props = HashMap::new();
133            if let Some(grav) = pack.physics.gravity_local {
134                props.insert("gravity.local".into(), PropertyValue::Float(grav));
135            }
136            props.insert(
137                "temperature.ambient".into(),
138                PropertyValue::Float(pack.physics.temperature_ambient),
139            );
140            let _ = sem.topology_tree.insert(TopologyNode {
141                layer: TopologyLayer::Region,
142                id: r.id.clone(),
143                name: r.name.clone(),
144                parent_id: parent,
145                properties: props,
146                tags: vec![],
147            });
148        }
149
150        // Validate topology
151        errors.extend(sem.topology_tree.validate());
152
153        if errors.is_empty() {
154            Ok(sem)
155        } else {
156            Err(errors)
157        }
158    }
159
160    pub fn validate_tag(&self, name: &str) -> Result<(), WaymarkError> {
161        if self.tag_registry.find(name).is_some() {
162            Ok(())
163        } else {
164            Err(WaymarkError::Authoring(AuthoringError::UnknownTag(name.into())))
165        }
166    }
167
168    pub fn validate_signal(&self, name: &str) -> Result<(), WaymarkError> {
169        if name.starts_with("signal.") && self.tag_registry.find(name).is_some() {
170            Ok(())
171        } else {
172            Err(WaymarkError::Authoring(AuthoringError::UnknownSignal(name.into())))
173        }
174    }
175
176    pub fn validate_receiver(&self, name: &str) -> Result<(), WaymarkError> {
177        if name.starts_with("receive.") && self.tag_registry.find(name).is_some() {
178            Ok(())
179        } else {
180            Err(WaymarkError::Authoring(AuthoringError::UnknownReceiver(name.into())))
181        }
182    }
183
184    pub fn validate_function(&self, key: &str) -> Result<(), WaymarkError> {
185        self.function_registry.validate_key(key)
186    }
187
188    pub fn validate_semantic_binding(&self, binding: &SemanticBinding) -> Vec<WaymarkError> {
189        let mut errors = Vec::new();
190        for tag in &binding.trait_tags {
191            if let Err(e) = self.validate_tag(tag) {
192                errors.push(e);
193            }
194        }
195        for sig in &binding.signal_tags {
196            if let Err(e) = self.validate_signal(sig) {
197                errors.push(e);
198            }
199        }
200        for recv in &binding.receiver_tags {
201            if let Err(e) = self.validate_receiver(recv) {
202                errors.push(e);
203            }
204        }
205        errors
206    }
207
208    pub fn resolve_property(&self, node_id: &str, key: &str) -> Option<PropertyValue> {
209        self.topology_tree.resolve_property(node_id, key)
210    }
211
212    pub fn resolve_property_with_default(&self, node_id: &str, key: &str) -> PropertyValue {
213        self.topology_tree
214            .resolve_property_with_default(node_id, key, &self.function_registry)
215    }
216
217    pub fn validate_all(&self) -> ValidationReport {
218        let mut report = ValidationReport::default();
219        report.errors.extend(self.topology_tree.validate());
220        report
221            .errors
222            .extend(self.transition_registry.validate(&self.topology_tree));
223        report
224    }
225
226    pub fn apply_template(&mut self, node_id: &str, template_id: &str) -> Result<(), WaymarkError> {
227        let template = self
228            .template_registry
229            .find(template_id)
230            .ok_or_else(|| WaymarkError::Authoring(AuthoringError::InvalidTemplateReference(template_id.into())))?
231            .clone();
232        if let Some(node) = self.topology_tree.nodes_mut().get_mut(node_id) {
233            node.tags.extend(template.tags);
234            for (k, v) in template.properties {
235                node.properties.insert(k, v);
236            }
237            Ok(())
238        } else {
239            Err(WaymarkError::Topology(
240                super::errors::TopologyError::InvalidTopologyBinding(node_id.into()),
241            ))
242        }
243    }
244}
245
246impl Default for WaymarkSimSemantics {
247    fn default() -> Self {
248        Self::new()
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn with_builtins_populated() {
258        let sem = WaymarkSimSemantics::with_builtins();
259        assert!(sem.tag_registry.len() >= 94);
260        assert_eq!(sem.function_registry.len(), 23);
261        assert_eq!(sem.heuristic_engine.rules.len(), 10);
262        assert!(
263            sem.preset_registry.len() >= 180,
264            "preset_registry has {} presets",
265            sem.preset_registry.len()
266        );
267        assert_eq!(sem.template_registry.len(), 49);
268    }
269
270    #[test]
271    fn validate_tag_valid() {
272        let sem = WaymarkSimSemantics::with_builtins();
273        assert!(sem.validate_tag("isFlammable").is_ok());
274    }
275
276    #[test]
277    fn validate_tag_invalid() {
278        let sem = WaymarkSimSemantics::with_builtins();
279        assert!(sem.validate_tag("nonexistent_tag").is_err());
280    }
281
282    #[test]
283    fn validate_signal_valid() {
284        let sem = WaymarkSimSemantics::with_builtins();
285        assert!(sem.validate_signal("signal.ignited").is_ok());
286    }
287
288    #[test]
289    fn validate_receiver_valid() {
290        let sem = WaymarkSimSemantics::with_builtins();
291        assert!(sem.validate_receiver("receive.fire").is_ok());
292    }
293
294    #[test]
295    fn validate_function_valid() {
296        let sem = WaymarkSimSemantics::with_builtins();
297        assert!(sem.validate_function("gravity.local").is_ok());
298    }
299
300    #[test]
301    fn validate_function_invalid() {
302        let sem = WaymarkSimSemantics::with_builtins();
303        assert!(sem.validate_function("nonexistent.function").is_err());
304    }
305
306    #[test]
307    fn resolve_with_default() {
308        let sem = WaymarkSimSemantics::with_builtins();
309        let val = sem.resolve_property_with_default("any", "gravity.local");
310        assert_eq!(val, PropertyValue::Float(9.81));
311    }
312
313    #[test]
314    fn validate_all_clean() {
315        let sem = WaymarkSimSemantics::with_builtins();
316        let report = sem.validate_all();
317        assert!(report.is_valid());
318    }
319
320    #[test]
321    fn validate_semantic_binding() {
322        let sem = WaymarkSimSemantics::with_builtins();
323        let binding = SemanticBinding {
324            trait_tags: vec!["isFlammable".into(), "isWall".into()],
325            signal_tags: vec!["signal.ignited".into()],
326            receiver_tags: vec!["receive.fire".into()],
327            property_defaults: vec![],
328        };
329        let errors = sem.validate_semantic_binding(&binding);
330        assert!(errors.is_empty());
331    }
332}