1use alloc::{collections::BTreeMap, string::String, vec::Vec};
2use core::fmt;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5pub mod wire_u64 {
7 use super::*;
8 use alloc::string::ToString;
9 use serde::de::Error;
10 pub fn serialize<S: Serializer>(value: &u64, serializer: S) -> Result<S::Ok, S::Error> {
12 serializer.serialize_str(&value.to_string())
13 }
14 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
19 let value = String::deserialize(deserializer)?;
20 if value.is_empty()
21 || value.starts_with('+')
22 || (value.len() > 1 && value.starts_with('0'))
23 || !value.bytes().all(|byte| byte.is_ascii_digit())
24 {
25 return Err(D::Error::custom(
26 "expected canonical unsigned decimal string",
27 ));
28 }
29 value
30 .parse::<u64>()
31 .map_err(|_| D::Error::custom("unsigned decimal string exceeds u64"))
32 }
33}
34pub const FORMAT_VERSION: u16 = 1;
36pub const MAX_ENTITIES: usize = 256;
38pub const MAX_SOURCE_BYTES: usize = 64 * 1024;
40pub const MAX_NESTING: usize = 16;
42pub const MAX_RUNTIME_EVENTS: usize = 64;
44pub const MAX_COORDINATE_M: f64 = 1_000_000.0;
46pub const MAX_SAMPLE_SECONDS: f64 = 31_536_000.0;
48pub const MAX_SPIN_DEGREES_PER_SECOND: f64 = 36_000.0;
50pub const MAX_ID_BYTES: usize = 128;
52pub const MAX_TEXT_BYTES: usize = 16 * 1024;
54pub const MAX_CONVERSATION_MESSAGES: usize = 256;
56pub const MAX_ACTIONS_PER_ENTITY: usize = 32;
58pub const MAX_ANIMATIONS_PER_ENTITY: usize = 16;
60
61#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
62#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
63#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
64#[serde(transparent)]
65pub struct SceneId(pub String);
69#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
70#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
71#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
72#[serde(transparent)]
73pub struct EntityId(pub String);
75#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
76#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
77#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
78#[serde(transparent)]
79pub struct ActionName(pub String);
81
82#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
83#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
84#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
85#[serde(deny_unknown_fields)]
86pub struct Vec3 {
88 pub x: f64,
90 pub y: f64,
92 pub z: f64,
94}
95impl Vec3 {
96 pub const ZERO: Self = Self {
98 x: 0.0,
99 y: 0.0,
100 z: 0.0,
101 };
102}
103#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
104#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
105#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
106#[serde(deny_unknown_fields)]
107pub struct Quaternion {
111 pub x: f64,
113 pub y: f64,
115 pub z: f64,
117 pub w: f64,
119}
120impl Quaternion {
121 pub const IDENTITY: Self = Self {
123 x: 0.0,
124 y: 0.0,
125 z: 0.0,
126 w: 1.0,
127 };
128}
129#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
130#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
131#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
132#[serde(deny_unknown_fields)]
133pub struct Transform {
135 pub translation: Vec3,
137 pub rotation: Quaternion,
139 pub scale: Vec3,
141}
142impl Default for Transform {
143 fn default() -> Self {
144 Self {
145 translation: Vec3::ZERO,
146 rotation: Quaternion::IDENTITY,
147 scale: Vec3 {
148 x: 1.0,
149 y: 1.0,
150 z: 1.0,
151 },
152 }
153 }
154}
155#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
156#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
157#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
158#[serde(deny_unknown_fields)]
159pub struct Color {
161 pub r: u8,
163 pub g: u8,
165 pub b: u8,
167 pub a: u8,
169}
170impl Color {
171 pub const WHITE: Self = Self {
173 r: 255,
174 g: 255,
175 b: 255,
176 a: 255,
177 };
178}
179#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
180#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
181#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
182#[serde(deny_unknown_fields)]
183pub struct Material {
185 pub roughness: f32,
187 pub emissive: f32,
189}
190impl Default for Material {
191 fn default() -> Self {
192 Self {
193 roughness: 0.65,
194 emissive: 0.0,
195 }
196 }
197}
198
199#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
200#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
201#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
202#[serde(deny_unknown_fields)]
203#[serde(tag = "kind", rename_all = "snake_case")]
204pub enum Geometry {
206 Group,
208 Sphere {
210 radius_m: f64,
212 },
213 Torus {
215 major_radius_m: f64,
217 minor_radius_m: f64,
219 },
220 Box {
222 size_m: Vec3,
224 },
225 Cylinder {
227 radius_m: f64,
229 height_m: f64,
231 },
232 Plane {
234 width_m: f64,
236 depth_m: f64,
238 },
239 Line {
241 from_m: Vec3,
243 to_m: Vec3,
245 },
246 Mesh {
248 asset: String,
250 },
251}
252#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
253#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
254#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
255#[serde(deny_unknown_fields)]
256#[serde(tag = "kind", rename_all = "snake_case")]
257pub enum Animation {
259 Orbit {
261 radius_m: f64,
263 period_s: f64,
265 },
266 Spin {
268 degrees_per_second: f64,
270 },
271 Bob {
273 amplitude_m: f64,
275 period_s: f64,
277 },
278}
279#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
280#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
281#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
282#[serde(deny_unknown_fields)]
283#[serde(tag = "kind", rename_all = "snake_case")]
284pub enum Action {
286 ToggleMotion,
288 ChangeColor {
290 color: Color,
292 },
293 ProposeEffect {
295 effect: String,
297 },
298}
299#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
300#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
301#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
302#[serde(deny_unknown_fields)]
303pub struct Entity {
305 pub id: EntityId,
307 pub parent: Option<EntityId>,
309 pub transform: Transform,
311 pub geometry: Geometry,
313 pub color: Color,
315 #[serde(default)]
316 pub material: Material,
318 pub animations: Vec<Animation>,
320 pub actions: BTreeMap<ActionName, Action>,
322}
323#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
324#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
325#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
326#[serde(deny_unknown_fields)]
327#[serde(rename_all = "snake_case")]
328pub enum ConversationRole {
330 User,
332 Assistant,
334 System,
336}
337#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
338#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
339#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
340#[serde(deny_unknown_fields)]
341pub struct ConversationMessage {
343 pub role: ConversationRole,
345 pub text: String,
347 #[serde(default)]
348 pub scene_references: Vec<EntityId>,
350}
351#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
352#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
353#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
354#[serde(deny_unknown_fields)]
355pub struct SceneDocument {
357 pub format: String,
359 pub version: u16,
361 pub id: SceneId,
363 pub title: String,
365 pub entities: Vec<Entity>,
367 #[serde(default)]
368 pub conversation: Vec<ConversationMessage>,
370}
371
372#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
374#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
375#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
376#[serde(deny_unknown_fields)]
377pub struct Observation {
378 pub id: String,
380 pub frame: FrameId,
382 pub time: Timestamp,
384 #[serde(with = "wire_u64")]
385 #[cfg_attr(feature = "schema", schemars(with = "String"))]
386 #[cfg_attr(feature = "typescript", ts(type = "string"))]
387 pub sequence: u64,
389 pub quality: Quality,
391 pub provenance: Provenance,
393}
394#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
395#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
396#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
397#[serde(transparent)]
398pub struct FrameId(pub String);
400#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
401#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
402#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
403#[serde(deny_unknown_fields)]
404pub struct Timestamp {
406 pub clock: String,
408 #[serde(with = "wire_u64")]
409 #[cfg_attr(feature = "schema", schemars(with = "String"))]
410 #[cfg_attr(feature = "typescript", ts(type = "string"))]
411 pub nanoseconds: u64,
413}
414#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
415#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
416#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
417#[serde(deny_unknown_fields)]
418pub struct Quality {
420 pub confidence: f32,
422 pub valid_for_ms: u32,
424}
425#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
426#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
427#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
428#[serde(deny_unknown_fields)]
429pub struct Provenance {
431 pub source: String,
433 pub model: Option<String>,
435 pub revision: Option<String>,
437}
438#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
439#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
440#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
441#[serde(deny_unknown_fields)]
442pub struct Capabilities {
444 pub room_anchors: bool,
446 pub camera: bool,
448 pub microphone: bool,
450 pub effects: bool,
452}
453
454#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
455#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
456#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
457#[serde(deny_unknown_fields)]
458pub struct SourceSpan {
460 pub start: usize,
462 pub end: usize,
464 pub line: u32,
466 pub column: u32,
468}
469#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
470#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
471#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
472#[serde(deny_unknown_fields)]
473pub struct Diagnostic {
475 pub message: String,
477 pub span: Option<SourceSpan>,
479}
480impl Diagnostic {
481 pub(crate) fn plain(message: impl Into<String>) -> Self {
482 Self {
483 message: message.into(),
484 span: None,
485 }
486 }
487}
488impl fmt::Display for Diagnostic {
489 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490 if let Some(s) = &self.span {
491 write!(f, "{} at {}:{}", self.message, s.line, s.column)
492 } else {
493 f.write_str(&self.message)
494 }
495 }
496}
497#[cfg(feature = "std")]
498impl std::error::Error for Diagnostic {}