Skip to main content

mecha10_sim/sim/
catalog.rs

1//! Environment catalog loading and management
2
3use crate::paths;
4use anyhow::{Context, Result};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fs;
8use std::path::{Path, PathBuf};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct EnvironmentCatalog {
12    pub version: String,
13    pub environments: Vec<Environment>,
14    #[serde(default)]
15    pub tags: HashMap<String, String>,
16    #[serde(default)]
17    pub difficulty_levels: HashMap<String, String>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct Environment {
22    pub id: String,
23    pub path: String,
24    pub name: String,
25    pub description: String,
26    pub tags: Vec<String>,
27    pub required_sensors: Vec<String>,
28    #[serde(default)]
29    pub optional_sensors: Vec<String>,
30    pub robot_platforms: Vec<String>,
31    #[serde(default)]
32    pub task_nodes: Vec<String>,
33    pub difficulty: String,
34    #[serde(default)]
35    pub dimensions: Vec<f32>,
36    #[serde(default)]
37    pub features: EnvironmentFeatures,
38    #[serde(default)]
39    pub curriculum_stage: i32,
40    #[serde(default)]
41    pub estimated_duration_s: i32,
42    #[serde(default)]
43    pub reward_config: HashMap<String, f32>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, Default)]
47pub struct EnvironmentFeatures {
48    #[serde(default)]
49    pub floor: bool,
50    #[serde(default)]
51    pub walls: bool,
52    #[serde(default)]
53    pub obstacles: String,
54    #[serde(default)]
55    pub obstacle_count: i32,
56    #[serde(default)]
57    pub tables: i32,
58    #[serde(default)]
59    pub waypoints: i32,
60    #[serde(default)]
61    pub goal_zones: i32,
62    #[serde(default)]
63    pub npcs: bool,
64    #[serde(default)]
65    pub npc_count: i32,
66    #[serde(default)]
67    pub dynamic: bool,
68}
69
70impl EnvironmentCatalog {
71    /// Load catalog from default location
72    pub fn load_default() -> Result<Self> {
73        let catalog_path = Self::default_catalog_path()?;
74        Self::load(&catalog_path)
75    }
76
77    /// Load catalog from specific path
78    pub fn load(path: &Path) -> Result<Self> {
79        let content =
80            fs::read_to_string(path).with_context(|| format!("Failed to read catalog file: {}", path.display()))?;
81
82        let catalog: EnvironmentCatalog =
83            serde_json::from_str(&content).with_context(|| "Failed to parse catalog.json")?;
84
85        Ok(catalog)
86    }
87
88    /// Get default catalog path (monorepo location)
89    fn default_catalog_path() -> Result<PathBuf> {
90        // Try to find monorepo root
91        let current_dir = std::env::current_dir()?;
92
93        // Look for packages/simulation/environments/robot-tasks/catalog.json
94        let mut search_dir = current_dir.clone();
95        loop {
96            let catalog_path = search_dir
97                .join("packages")
98                .join("simulation")
99                .join("environments")
100                .join("robot-tasks")
101                .join("catalog.json");
102
103            if catalog_path.exists() {
104                return Ok(catalog_path);
105            }
106
107            // Move up one directory
108            if let Some(parent) = search_dir.parent() {
109                search_dir = parent.to_path_buf();
110            } else {
111                break;
112            }
113        }
114
115        // Fallback: relative to current directory
116        Ok(PathBuf::from(paths::framework::ROBOT_TASKS_CATALOG))
117    }
118}
119
120impl Environment {
121    /// Check if environment is compatible with robot (has all required sensors)
122    pub fn is_compatible_with_sensors(&self, sensors: &[String]) -> bool {
123        self.required_sensors.iter().all(|required| sensors.contains(required))
124    }
125
126    /// Check if environment is compatible with platform
127    pub fn is_compatible_with_platform(&self, platform: &str) -> bool {
128        self.robot_platforms.contains(&platform.to_string())
129    }
130}