Skip to main content

konjure_sdk/
domain.rs

1use alloc::{collections::BTreeMap, string::String, vec::Vec};
2use core::fmt;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5/// Serde adapters that encode `u64` values as canonical decimal strings.
6pub mod wire_u64 {
7    use super::*;
8    use alloc::string::ToString;
9    use serde::de::Error;
10    /// Serializes a value without precision loss in JSON-oriented consumers.
11    pub fn serialize<S: Serializer>(value: &u64, serializer: S) -> Result<S::Ok, S::Error> {
12        serializer.serialize_str(&value.to_string())
13    }
14    /// Deserializes an unsigned canonical decimal string.
15    ///
16    /// Leading zeroes, an explicit plus sign, non-digits, and values larger
17    /// than `u64::MAX` are rejected by the supplied serde deserializer.
18    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}
34/// Current `.konjure` scene document format version.
35pub const FORMAT_VERSION: u16 = 1;
36/// Maximum entities in one validated scene document.
37pub const MAX_ENTITIES: usize = 256;
38/// Maximum UTF-8 source length accepted by [`crate::compile`], in bytes.
39pub const MAX_SOURCE_BYTES: usize = 64 * 1024;
40/// Maximum permitted parent-chain depth for an entity.
41pub const MAX_NESTING: usize = 16;
42/// Maximum retained runtime events; older events are discarded first.
43pub const MAX_RUNTIME_EVENTS: usize = 64;
44/// Largest absolute coordinate or positive dimension accepted, in metres.
45pub const MAX_COORDINATE_M: f64 = 1_000_000.0;
46/// Largest runtime or recording sample time accepted, in seconds.
47pub const MAX_SAMPLE_SECONDS: f64 = 31_536_000.0;
48/// Largest absolute spin speed accepted, in degrees per second.
49pub const MAX_SPIN_DEGREES_PER_SECOND: f64 = 36_000.0;
50/// Maximum byte length of a scene, entity, action, or effect identifier.
51pub const MAX_ID_BYTES: usize = 128;
52/// Maximum UTF-8 byte length of one conversation message.
53pub const MAX_TEXT_BYTES: usize = 16 * 1024;
54/// Maximum conversation messages attached to a scene document.
55pub const MAX_CONVERSATION_MESSAGES: usize = 256;
56/// Maximum declared actions on one entity.
57pub const MAX_ACTIONS_PER_ENTITY: usize = 32;
58/// Maximum declared animations on one entity.
59pub 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)]
65/// Owned identifier for a scene.
66///
67/// [`crate::validate`] requires non-empty ASCII identifier syntax.
68pub 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)]
73/// Owned identifier for an entity within a scene.
74pub 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)]
79/// Owned name of an action declared by an entity.
80pub 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)]
86/// A three-component vector. Spatial values use metres unless documented otherwise.
87pub struct Vec3 {
88    /// X component.
89    pub x: f64,
90    /// Y component.
91    pub y: f64,
92    /// Z component.
93    pub z: f64,
94}
95impl Vec3 {
96    /// Zero vector.
97    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)]
107/// A scalar-last rotation quaternion.
108///
109/// Valid scene transforms require finite, unit-length values.
110pub struct Quaternion {
111    /// X imaginary component.
112    pub x: f64,
113    /// Y imaginary component.
114    pub y: f64,
115    /// Z imaginary component.
116    pub z: f64,
117    /// Real scalar component.
118    pub w: f64,
119}
120impl Quaternion {
121    /// Identity rotation.
122    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)]
133/// Local affine transform composed with parent transforms by the runtime.
134pub struct Transform {
135    /// Translation in metres.
136    pub translation: Vec3,
137    /// Unit quaternion rotation.
138    pub rotation: Quaternion,
139    /// Positive, dimensionless component scale.
140    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)]
159/// Eight-bit RGBA colour channels.
160pub struct Color {
161    /// Red channel.
162    pub r: u8,
163    /// Green channel.
164    pub g: u8,
165    /// Blue channel.
166    pub b: u8,
167    /// Alpha channel.
168    pub a: u8,
169}
170impl Color {
171    /// Fully opaque white.
172    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)]
183/// Renderer-neutral material parameters.
184pub struct Material {
185    /// Surface roughness from `0.0` through `1.0`.
186    pub roughness: f32,
187    /// Emissive intensity from `0.0` through `100.0`.
188    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")]
204/// A built-in primitive, grouping node, or adapter-owned mesh reference.
205pub enum Geometry {
206    /// A non-drawable parent for child entities.
207    Group,
208    /// UV sphere centred on the local origin.
209    Sphere {
210        /// Sphere radius in metres.
211        radius_m: f64,
212    },
213    /// Torus centred on the local origin, around the Y axis.
214    Torus {
215        /// Radius from origin to tube centre, in metres.
216        major_radius_m: f64,
217        /// Tube radius in metres; it must be smaller than the major radius.
218        minor_radius_m: f64,
219    },
220    /// Axis-aligned box centred on the local origin.
221    Box {
222        /// Full width, height, and depth in metres.
223        size_m: Vec3,
224    },
225    /// Cylinder centred on the local origin and aligned with the Y axis.
226    Cylinder {
227        /// Cylinder radius in metres.
228        radius_m: f64,
229        /// Full cylinder height in metres.
230        height_m: f64,
231    },
232    /// Horizontal XZ plane centred on the local origin.
233    Plane {
234        /// X-axis width in metres.
235        width_m: f64,
236        /// Z-axis depth in metres.
237        depth_m: f64,
238    },
239    /// A two-vertex line segment.
240    Line {
241        /// First endpoint in local metres.
242        from_m: Vec3,
243        /// Second endpoint in local metres.
244        to_m: Vec3,
245    },
246    /// An opaque asset supplied and tessellated by a platform adapter.
247    Mesh {
248        /// Adapter-defined asset identifier.
249        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")]
257/// Time-based local transform animation evaluated by [`crate::Runtime`].
258pub enum Animation {
259    /// Circular XZ translation around the entity's authored position.
260    Orbit {
261        /// Orbit radius in metres.
262        radius_m: f64,
263        /// Complete revolution duration in seconds.
264        period_s: f64,
265    },
266    /// Rotation about the local Y axis.
267    Spin {
268        /// Signed angular speed in degrees per second.
269        degrees_per_second: f64,
270    },
271    /// Sinusoidal vertical local translation.
272    Bob {
273        /// Peak vertical displacement in metres.
274        amplitude_m: f64,
275        /// Complete oscillation duration in seconds.
276        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")]
284/// A named interaction that an entity explicitly permits.
285pub enum Action {
286    /// Toggles all animation evaluation for the entity.
287    ToggleMotion,
288    /// Updates the entity's runtime colour.
289    ChangeColor {
290        /// Replacement colour retained by the runtime.
291        color: Color,
292    },
293    /// Emits an effect proposal without executing an external effect.
294    ProposeEffect {
295        /// Identifier interpreted only by an explicit external effect boundary.
296        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)]
303/// An owned scene node with geometry, presentation, and declared interactions.
304pub struct Entity {
305    /// Unique identifier in the containing scene.
306    pub id: EntityId,
307    /// Optional identifier of the entity supplying this node's parent transform.
308    pub parent: Option<EntityId>,
309    /// Authored local transform.
310    pub transform: Transform,
311    /// Geometry drawn by the runtime or resolved by an adapter.
312    pub geometry: Geometry,
313    /// Initial render colour, possibly changed by a dispatched action.
314    pub color: Color,
315    #[serde(default)]
316    /// Material parameters; absent serialized values use [`Material::default`].
317    pub material: Material,
318    /// Ordered animations evaluated while motion is enabled.
319    pub animations: Vec<Animation>,
320    /// Action declarations indexed in deterministic key order.
321    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")]
328/// Author of a conversation message attached to a scene.
329pub enum ConversationRole {
330    /// A person interacting with the scene.
331    User,
332    /// The Konjure assistant.
333    Assistant,
334    /// System context or instructions.
335    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)]
341/// An owned chat record associated with a scene document.
342pub struct ConversationMessage {
343    /// Message author.
344    pub role: ConversationRole,
345    /// UTF-8 message body.
346    pub text: String,
347    #[serde(default)]
348    /// Entity identifiers mentioned by the message.
349    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)]
355/// A serializable, owned Konjure scene and its resumable conversation context.
356pub struct SceneDocument {
357    /// Wire-format name, currently `"konjure.scene"`.
358    pub format: String,
359    /// Wire-format version, currently [`FORMAT_VERSION`].
360    pub version: u16,
361    /// Stable scene identifier.
362    pub id: SceneId,
363    /// Human-readable scene title.
364    pub title: String,
365    /// Owned scene graph nodes.
366    pub entities: Vec<Entity>,
367    #[serde(default)]
368    /// Optional ordered conversation history.
369    pub conversation: Vec<ConversationMessage>,
370}
371
372/// Immutable spatial evidence. It is never inferred to be an authored transform.
373#[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    /// Observation identifier supplied by its producing boundary.
379    pub id: String,
380    /// Coordinate frame in which the observation was expressed.
381    pub frame: FrameId,
382    /// Timestamp from the producing clock.
383    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    /// Monotonic producer sequence, serialized as a decimal string.
388    pub sequence: u64,
389    /// Producer-supplied quality and validity interval.
390    pub quality: Quality,
391    /// Source, model, and revision evidence for this immutable record.
392    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)]
398/// Owned identifier of a spatial coordinate frame.
399pub 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)]
404/// Timestamp whose epoch and scale are identified by its producing clock.
405pub struct Timestamp {
406    /// Producer-defined clock identifier; the SDK does not infer a shared epoch.
407    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    /// Tick value in nanoseconds, serialized as a decimal string.
412    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)]
418/// Producer-reported confidence and validity bound for an observation.
419pub struct Quality {
420    /// Producer-defined confidence value.
421    pub confidence: f32,
422    /// Interval after the timestamp for which the observation is valid, in milliseconds.
423    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)]
429/// Immutable provenance supplied with an observation.
430pub struct Provenance {
431    /// System or sensor that produced the observation.
432    pub source: String,
433    /// Optional model identifier used by the producer.
434    pub model: Option<String>,
435    /// Optional producer, data, or model revision.
436    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)]
442/// Capabilities explicitly supplied by a platform adapter.
443pub struct Capabilities {
444    /// Whether the adapter supports room anchors.
445    pub room_anchors: bool,
446    /// Whether the adapter can access a camera.
447    pub camera: bool,
448    /// Whether the adapter can access a microphone.
449    pub microphone: bool,
450    /// Whether the adapter can execute approved effects.
451    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)]
458/// Half-open byte range and one-based source position for a diagnostic.
459pub struct SourceSpan {
460    /// Zero-based starting UTF-8 byte offset.
461    pub start: usize,
462    /// Zero-based exclusive ending UTF-8 byte offset.
463    pub end: usize,
464    /// One-based source line.
465    pub line: u32,
466    /// One-based source column counted in Unicode scalar values.
467    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)]
473/// A recoverable compiler, validation, geometry, recording, or runtime error.
474pub struct Diagnostic {
475    /// Human-readable diagnostic message.
476    pub message: String,
477    /// Source position when the error can be tied to compiler input.
478    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 {}