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;
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();
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![],
});
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()],
});
}
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()],
});
}
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![],
});
}
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());
}
}