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::topology::TopologyLayer;
use crate::physics::properties::PropertyValue;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

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

#[derive(Debug, Clone, Default)]
pub struct TemplateRegistry {
    templates: Vec<SceneTemplate>,
    id_index: HashMap<String, usize>,
}

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

    pub fn with_builtins() -> Self {
        let mut reg = Self::new();
        // Universe (3)
        for (id, name, desc) in [
            ("universe_cosmology_map", "Cosmology Map", "Top-level cosmology view"),
            ("universe_timeline_root", "Timeline Root", "Universal timeline root"),
            (
                "universe_faction_registry",
                "Faction Registry",
                "Global faction registry",
            ),
        ] {
            let _ = reg.register(tmpl(id, name, TopologyLayer::Universe, desc));
        }
        // Galaxy (4)
        for (id, name, desc) in [
            ("galaxy_strategy_map", "Strategy Map", "Galaxy-scale strategy view"),
            ("galaxy_trade_route", "Trade Route", "Interstellar trade route network"),
            (
                "galaxy_warp_lane_network",
                "Warp Lane Network",
                "FTL jump corridor network",
            ),
            (
                "galaxy_hostile_void_corridor",
                "Hostile Void Corridor",
                "Dangerous void passage",
            ),
        ] {
            let _ = reg.register(tmpl(id, name, TopologyLayer::Galaxy, desc));
        }
        // Sector (4)
        for (id, name, desc) in [
            ("sector_campaign", "Campaign Sector", "Campaign event zone"),
            ("sector_patrol", "Patrol Sector", "Patrol route zone"),
            ("sector_frontier", "Frontier Sector", "Frontier exploration zone"),
            ("sector_contested", "Contested Sector", "Contested territory zone"),
        ] {
            let _ = reg.register(tmpl(id, name, TopologyLayer::Sector, desc));
        }
        // World (5)
        for (id, name, desc, grav) in [
            (
                "world_habitable_planet",
                "Habitable Planet",
                "Standard habitable world",
                9.81,
            ),
            (
                "world_hostile_wasteland",
                "Hostile Wasteland",
                "Dangerous wasteland world",
                9.81,
            ),
            ("world_ocean", "Ocean World", "Water-dominated world", 9.81),
            ("world_city", "City World", "Urban megacity world", 9.81),
            ("world_mining", "Mining World", "Resource extraction world", 7.5),
        ] {
            let mut t = tmpl(id, name, TopologyLayer::World, desc);
            t.tags.push("isHabitableWorld".into());
            t.properties
                .insert("gravity.planetary".into(), PropertyValue::Float(grav));
            t.properties
                .insert("atmosphere.density".into(), PropertyValue::Float(1.0));
            let _ = reg.register(t);
        }
        // Realm (5)
        for (id, name, desc) in [
            ("realm_dream_overlay", "Dream Overlay", "Dream reality overlay"),
            ("realm_spirit_layer", "Spirit Layer", "Spirit world layer"),
            (
                "realm_corrupted_mirror",
                "Corrupted Mirror",
                "Corrupted mirror dimension",
            ),
            ("realm_tactical_combat", "Tactical Combat", "Combat pocket realm"),
            (
                "realm_simulation_pocket",
                "Simulation Pocket",
                "Simulation pocket realm",
            ),
        ] {
            let mut t = tmpl(id, name, TopologyLayer::Realm, desc);
            t.tags.push("isDreamLayer".into());
            let _ = reg.register(t);
        }
        // Region (5)
        for (id, name, desc) in [
            ("region_desert_basin", "Desert Basin", "Arid desert region"),
            ("region_snowy_ridge", "Snowy Ridge", "Cold mountain region"),
            (
                "region_industrial_zone",
                "Industrial Zone",
                "Industrial production region",
            ),
            ("region_jungle_basin", "Jungle Basin", "Dense jungle region"),
            ("region_ash_crater", "Ash Crater", "Volcanic ash region"),
        ] {
            let _ = reg.register(tmpl(id, name, TopologyLayer::Region, desc));
        }
        // Area (5)
        for (id, name, desc) in [
            ("area_boss_arena", "Boss Arena", "Boss encounter area"),
            ("area_harvest_field", "Harvest Field", "Resource gathering area"),
            (
                "area_settlement_perimeter",
                "Settlement Perimeter",
                "Settlement outer zone",
            ),
            ("area_siege_lane", "Siege Lane", "Siege combat corridor"),
            ("area_build_zone", "Build Zone", "Construction zone"),
        ] {
            let _ = reg.register(tmpl(id, name, TopologyLayer::Area, desc));
        }
        // Location (6)
        for (id, name, desc) in [
            ("location_town", "Town", "Settlement with services"),
            ("location_camp", "Camp", "Temporary camp site"),
            ("location_station", "Station", "Service station"),
            ("location_dungeon_entrance", "Dungeon Entrance", "Dungeon entry point"),
            ("location_foundry", "Foundry", "Industrial foundry"),
            ("location_temple", "Temple", "Religious or mystical temple"),
        ] {
            let _ = reg.register(tmpl(id, name, TopologyLayer::Location, desc));
        }
        // Room (6)
        for (id, name, desc) in [
            ("room_hallway", "Hallway", "Corridor passage"),
            ("room_reactor_chamber", "Reactor Chamber", "Power reactor room"),
            ("room_vault", "Vault", "Secure vault"),
            ("room_bedroom", "Bedroom", "Sleeping quarters"),
            ("room_throne_room", "Throne Room", "Throne chamber"),
            ("room_workshop", "Workshop", "Crafting workshop"),
        ] {
            let mut t = tmpl(id, name, TopologyLayer::Room, desc);
            t.tags.push("isInterior".into());
            let _ = reg.register(t);
        }
        // Point (6)
        for (id, name, desc) in [
            ("point_loot", "Loot Point", "Item pickup point"),
            ("point_power_socket", "Power Socket", "Power connection socket"),
            ("point_door_trigger", "Door Trigger", "Door activation trigger"),
            ("point_spawn_marker", "Spawn Marker", "Entity spawn point"),
            ("point_crafting_slot", "Crafting Slot", "Crafting station slot"),
            ("point_dialogue_anchor", "Dialogue Anchor", "NPC dialogue anchor"),
        ] {
            let mut t = tmpl(id, name, TopologyLayer::Point, desc);
            t.tags.push("isPointSocket".into());
            let _ = reg.register(t);
        }
        reg
    }

    pub fn register(&mut self, template: SceneTemplate) -> Result<(), WaymarkError> {
        if self.id_index.contains_key(&template.id) {
            return Err(WaymarkError::Authoring(AuthoringError::InvalidTemplateReference(
                format!("duplicate template: {}", template.id),
            )));
        }
        let idx = self.templates.len();
        self.id_index.insert(template.id.clone(), idx);
        self.templates.push(template);
        Ok(())
    }

    pub fn find(&self, id: &str) -> Option<&SceneTemplate> {
        self.id_index.get(id).map(|&i| &self.templates[i])
    }

    pub fn by_layer(&self, layer: TopologyLayer) -> Vec<&SceneTemplate> {
        self.templates.iter().filter(|t| t.layer == layer).collect()
    }

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

fn tmpl(id: &str, name: &str, layer: TopologyLayer, desc: &str) -> SceneTemplate {
    SceneTemplate {
        id: id.into(),
        name: name.into(),
        layer,
        tags: vec![],
        properties: HashMap::new(),
        uses: vec![],
        description: desc.into(),
    }
}

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

    #[test]
    fn builtins_count() {
        let reg = TemplateRegistry::with_builtins();
        assert_eq!(reg.len(), 49); // 3+4+4+5+5+5+5+6+6+6
    }

    #[test]
    fn find_template() {
        let reg = TemplateRegistry::with_builtins();
        assert!(reg.find("world_habitable_planet").is_some());
    }

    #[test]
    fn by_layer_universe() {
        let reg = TemplateRegistry::with_builtins();
        assert_eq!(reg.by_layer(TopologyLayer::Universe).len(), 3);
    }

    #[test]
    fn by_layer_all_covered() {
        let reg = TemplateRegistry::with_builtins();
        for &layer in TopologyLayer::ALL {
            assert!(!reg.by_layer(layer).is_empty(), "no templates for {:?}", layer);
        }
    }

    #[test]
    fn no_duplicate_ids() {
        let reg = TemplateRegistry::with_builtins();
        let mut ids: Vec<&str> = reg.templates.iter().map(|t| t.id.as_str()).collect();
        let before = ids.len();
        ids.sort();
        ids.dedup();
        assert_eq!(before, ids.len());
    }

    #[test]
    fn world_template_has_gravity() {
        let reg = TemplateRegistry::with_builtins();
        let t = reg.find("world_habitable_planet").unwrap();
        assert!(t.properties.contains_key("gravity.planetary"));
    }
}