dreamwell_engine/waymark/
semantics.rs1use 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
14pub 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 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 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 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 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 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}