1use super::errors::{AuthoringError, WaymarkError};
2use crate::physics::properties::PropertyValue;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub enum FunctionScope {
8 Universe,
9 Galaxy,
10 Sector,
11 World,
12 Realm,
13 Region,
14 Area,
15 Location,
16 Room,
17 Point,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
21pub enum FunctionCategory {
22 Physics,
23 Traversal,
24 Combat,
25 Simulation,
26 Dreamwell,
27 Topology,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct UniversalFunction {
32 pub key: String,
33 pub category: FunctionCategory,
34 pub scope: FunctionScope,
35 pub default_value: PropertyValue,
36 pub description: String,
37}
38
39#[derive(Debug, Clone, Default)]
40pub struct FunctionRegistry {
41 functions: Vec<UniversalFunction>,
42 key_index: HashMap<String, usize>,
43}
44
45impl FunctionRegistry {
46 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn with_builtins() -> Self {
51 let mut reg = Self::new();
52 let builtins = vec![
53 (
54 "gravity.local",
55 FunctionCategory::Physics,
56 FunctionScope::Region,
57 PropertyValue::Float(9.81),
58 "Local gravity acceleration",
59 ),
60 (
61 "gravity.planetary",
62 FunctionCategory::Physics,
63 FunctionScope::World,
64 PropertyValue::Float(9.81),
65 "Planetary gravity",
66 ),
67 (
68 "atmosphere.density",
69 FunctionCategory::Physics,
70 FunctionScope::World,
71 PropertyValue::Float(1.0),
72 "Atmospheric density multiplier",
73 ),
74 (
75 "pressure.local",
76 FunctionCategory::Physics,
77 FunctionScope::Room,
78 PropertyValue::Float(1.0),
79 "Local air pressure",
80 ),
81 (
82 "temperature.ambient",
83 FunctionCategory::Physics,
84 FunctionScope::Region,
85 PropertyValue::Float(20.0),
86 "Ambient temperature",
87 ),
88 (
89 "fluid.viscosity",
90 FunctionCategory::Physics,
91 FunctionScope::Area,
92 PropertyValue::Float(1.0),
93 "Fluid viscosity multiplier",
94 ),
95 (
96 "pathing.walkability",
97 FunctionCategory::Traversal,
98 FunctionScope::Area,
99 PropertyValue::Float(1.0),
100 "Walkability multiplier",
101 ),
102 (
103 "pathing.flightAllowed",
104 FunctionCategory::Traversal,
105 FunctionScope::Region,
106 PropertyValue::Bool(false),
107 "Flight allowed",
108 ),
109 (
110 "pathing.swimAllowed",
111 FunctionCategory::Traversal,
112 FunctionScope::Area,
113 PropertyValue::Bool(false),
114 "Swimming allowed",
115 ),
116 (
117 "damage.falloff",
118 FunctionCategory::Combat,
119 FunctionScope::Area,
120 PropertyValue::Float(1.0),
121 "Damage falloff rate",
122 ),
123 (
124 "shield.rechargeRate",
125 FunctionCategory::Combat,
126 FunctionScope::Region,
127 PropertyValue::Float(1.0),
128 "Shield recharge rate",
129 ),
130 (
131 "projectile.arcScale",
132 FunctionCategory::Combat,
133 FunctionScope::Region,
134 PropertyValue::Float(1.0),
135 "Projectile arc scale",
136 ),
137 (
138 "time.flow",
139 FunctionCategory::Simulation,
140 FunctionScope::Universe,
141 PropertyValue::Float(1.0),
142 "Time flow rate",
143 ),
144 (
145 "time.scale",
146 FunctionCategory::Simulation,
147 FunctionScope::Realm,
148 PropertyValue::Float(1.0),
149 "Time scale multiplier",
150 ),
151 (
152 "weather.pattern",
153 FunctionCategory::Simulation,
154 FunctionScope::Region,
155 PropertyValue::String("clear".into()),
156 "Weather pattern",
157 ),
158 (
159 "resource.respawnRate",
160 FunctionCategory::Simulation,
161 FunctionScope::Region,
162 PropertyValue::Float(1.0),
163 "Resource respawn rate",
164 ),
165 (
166 "corruption.spreadRate",
167 FunctionCategory::Dreamwell,
168 FunctionScope::Region,
169 PropertyValue::Float(0.0),
170 "Corruption spread rate",
171 ),
172 (
173 "dreammatter.condenseRate",
174 FunctionCategory::Dreamwell,
175 FunctionScope::Realm,
176 PropertyValue::Float(0.0),
177 "Dreammatter condensation rate",
178 ),
179 (
180 "timeline.branchPolicy",
181 FunctionCategory::Dreamwell,
182 FunctionScope::Universe,
183 PropertyValue::String("signed_canonical".into()),
184 "Timeline branch policy",
185 ),
186 (
187 "attestation.visibility",
188 FunctionCategory::Dreamwell,
189 FunctionScope::Universe,
190 PropertyValue::Bool(true),
191 "Attestation visibility",
192 ),
193 (
194 "topology.transitionMode",
195 FunctionCategory::Topology,
196 FunctionScope::Location,
197 PropertyValue::String("seamless_stream".into()),
198 "Topology transition mode",
199 ),
200 (
201 "topology.scalePresentation",
202 FunctionCategory::Topology,
203 FunctionScope::Area,
204 PropertyValue::String("continuous".into()),
205 "Scale presentation mode",
206 ),
207 (
208 "topology.sceneBinding",
209 FunctionCategory::Topology,
210 FunctionScope::Location,
211 PropertyValue::String("default".into()),
212 "Scene binding mode",
213 ),
214 ];
215 for (key, cat, scope, val, desc) in builtins {
216 let _ = reg.register(UniversalFunction {
217 key: key.into(),
218 category: cat,
219 scope,
220 default_value: val,
221 description: desc.into(),
222 });
223 }
224 reg
225 }
226
227 pub fn register(&mut self, func: UniversalFunction) -> Result<(), WaymarkError> {
228 if self.key_index.contains_key(&func.key) {
229 return Err(WaymarkError::Authoring(AuthoringError::UnknownFunction(format!(
230 "duplicate function key: {}",
231 func.key
232 ))));
233 }
234 let idx = self.functions.len();
235 self.key_index.insert(func.key.clone(), idx);
236 self.functions.push(func);
237 Ok(())
238 }
239
240 pub fn find(&self, key: &str) -> Option<&UniversalFunction> {
241 self.key_index.get(key).map(|&i| &self.functions[i])
242 }
243
244 pub fn by_category(&self, cat: FunctionCategory) -> Vec<&UniversalFunction> {
245 self.functions.iter().filter(|f| f.category == cat).collect()
246 }
247
248 pub fn by_scope(&self, scope: FunctionScope) -> Vec<&UniversalFunction> {
249 self.functions.iter().filter(|f| f.scope == scope).collect()
250 }
251
252 pub fn validate_key(&self, key: &str) -> Result<(), WaymarkError> {
253 if self.key_index.contains_key(key) {
254 Ok(())
255 } else {
256 Err(WaymarkError::Authoring(AuthoringError::UnknownFunction(key.into())))
257 }
258 }
259
260 pub fn len(&self) -> usize {
261 self.functions.len()
262 }
263 pub fn is_empty(&self) -> bool {
264 self.functions.is_empty()
265 }
266}
267
268impl FunctionScope {
269 pub const ALL: &[FunctionScope] = &[
270 Self::Universe,
271 Self::Galaxy,
272 Self::Sector,
273 Self::World,
274 Self::Realm,
275 Self::Region,
276 Self::Area,
277 Self::Location,
278 Self::Room,
279 Self::Point,
280 ];
281}
282
283impl FunctionCategory {
284 pub const ALL: &[FunctionCategory] = &[
285 Self::Physics,
286 Self::Traversal,
287 Self::Combat,
288 Self::Simulation,
289 Self::Dreamwell,
290 Self::Topology,
291 ];
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn builtins_count() {
300 let reg = FunctionRegistry::with_builtins();
301 assert_eq!(reg.len(), 23);
302 }
303
304 #[test]
305 fn find_gravity() {
306 let reg = FunctionRegistry::with_builtins();
307 let f = reg.find("gravity.local").unwrap();
308 assert_eq!(f.category, FunctionCategory::Physics);
309 }
310
311 #[test]
312 fn by_category_physics() {
313 let reg = FunctionRegistry::with_builtins();
314 let phys = reg.by_category(FunctionCategory::Physics);
315 assert_eq!(phys.len(), 6);
316 }
317
318 #[test]
319 fn validate_key_valid() {
320 let reg = FunctionRegistry::with_builtins();
321 assert!(reg.validate_key("gravity.local").is_ok());
322 }
323
324 #[test]
325 fn validate_key_invalid() {
326 let reg = FunctionRegistry::with_builtins();
327 assert!(reg.validate_key("nonexistent.key").is_err());
328 }
329
330 #[test]
331 fn scope_all_count() {
332 assert_eq!(FunctionScope::ALL.len(), 10);
333 }
334
335 #[test]
336 fn category_all_count() {
337 assert_eq!(FunctionCategory::ALL.len(), 6);
338 }
339}