Skip to main content

nightshade_api/editor/protocol/
agent.rs

1use enum2schema::Schema;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5/// A full generational entity handle as it crosses the agent boundary. Never a
6/// bare index: a reused slot reads as a different handle, not the same entity
7/// mutated, so a command or read against a stale handle fails rather than
8/// hitting the new occupant.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Schema)]
10pub struct EntityRef {
11    pub id: u32,
12    pub generation: u32,
13}
14
15/// Correlation id for request/response matching and the optional delta origin
16/// tag. Unique per host process lifetime.
17pub type CorrelationId = u64;
18
19/// The host-assigned subscription handle. Unique for the host process lifetime,
20/// never reused.
21pub type SubscriptionId = u64;
22
23/// The collection system's own monotonic batch counter. Not the freecs tick.
24pub type Version = u64;
25
26/// Whether the generic component bag may carry a component.
27#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
28pub enum WritePolicyInfo {
29    /// Plain data: the bag may carry it, no side effects on write.
30    Free,
31    /// A named command owns this component's cascade; reject it in the bag and
32    /// point the agent at the command.
33    Owned { command: String },
34    /// A system computes this component; no command writes it and the bag must
35    /// not carry it.
36    Derived,
37}
38
39/// One registry entry as the agent discovers it. Schema and example are derived
40/// from the same type so they cannot drift from the wire format.
41#[derive(Clone, Debug, Serialize, Deserialize)]
42pub struct ComponentInfo {
43    pub name: String,
44    pub write_policy: WritePolicyInfo,
45    /// Structural shape of the component's value.
46    pub schema: Value,
47    /// A concrete sample value (the serialized default), a complement to the
48    /// schema for the flat cases.
49    pub example: Value,
50}
51
52/// The command set. Named variants exist where the engine does something beyond
53/// writing data (a cascade); the generic setters are the open end the registry
54/// absorbs new components through.
55#[derive(Clone, Debug, Serialize, Deserialize)]
56pub enum AgentCommand {
57    Reparent {
58        child: EntityRef,
59        new_parent: Option<EntityRef>,
60    },
61    DeleteEntity {
62        entity: EntityRef,
63    },
64    LoadGltf {
65        uri: String,
66    },
67    SelectNode {
68        entity: EntityRef,
69    },
70    /// Make the given camera entity the active viewport camera.
71    SetActiveCamera {
72        entity: EntityRef,
73    },
74    /// Browse-and-grab a Polyhaven model by slug, loaded additively.
75    LoadPolyhavenModel {
76        slug: String,
77        resolution: u32,
78    },
79    /// Spawn a parametric primitive mesh, apply a component bag (e.g.
80    /// local_transform, material_ref) to it, and return its handle.
81    AddPrimitive {
82        kind: super::ShapeKind,
83        components: Vec<(String, Value)>,
84    },
85    /// Spawn a light, apply a component bag, and return its handle.
86    AddLight {
87        kind: super::LightKind,
88        components: Vec<(String, Value)>,
89    },
90    /// Despawn the whole current scene (everything the user or the agent has
91    /// placed), leaving an empty stage with the editor camera, sun, and
92    /// environment intact.
93    ClearScene,
94    SpawnEntity {
95        components: Vec<(String, Value)>,
96    },
97    SetComponents {
98        entity: EntityRef,
99        components: Vec<(String, Value)>,
100    },
101    RemoveComponents {
102        entity: EntityRef,
103        component_types: Vec<String>,
104    },
105}
106
107/// Which components and optionally which entities a subscription covers.
108#[derive(Clone, Debug, Serialize, Deserialize, Schema)]
109pub struct SubscriptionFilter {
110    pub component_types: Vec<String>,
111    pub entities: Option<Vec<EntityRef>>,
112}
113
114/// A material to create or edit in the material library. `name` keys the
115/// material; every other field is optional and only the set ones are written, so
116/// editing leaves untouched fields (including textures) intact.
117#[derive(Clone, Debug, Default, Serialize, Deserialize, Schema)]
118pub struct MaterialSpec {
119    pub name: String,
120    /// Linear RGBA albedo.
121    pub base_color: Option<[f32; 4]>,
122    pub metallic: Option<f32>,
123    pub roughness: Option<f32>,
124    pub emissive_factor: Option<[f32; 3]>,
125    pub emissive_strength: Option<f32>,
126    pub unlit: Option<bool>,
127    pub double_sided: Option<bool>,
128    /// Name of an already-loaded texture for the base-color map. Built-in
129    /// prototype textures are always available: "proto_light", "proto_dark",
130    /// "proto_green", "proto_orange", "proto_purple", "proto_red" (grid
131    /// greybox textures).
132    pub base_texture: Option<String>,
133    /// How many times the base texture repeats across a surface. Higher tiles the
134    /// pattern more finely; set it near a surface's size in units (e.g. 6 for a
135    /// 6-unit wall) so a greybox grid reads as roughly one cell per unit. Applied
136    /// when a base_texture is set; defaults to a small value.
137    pub tiling: Option<f32>,
138    /// Name of an already-loaded texture for the normal map.
139    pub normal_texture: Option<String>,
140    /// Normal map intensity multiplier (1 is full strength).
141    pub normal_scale: Option<f32>,
142    /// Name of an already-loaded texture for the metallic (B) / roughness (G) map.
143    pub metallic_roughness_texture: Option<String>,
144    /// Name of an already-loaded texture for the emissive map.
145    pub emissive_texture: Option<String>,
146    /// Name of an already-loaded texture for the ambient-occlusion (R) map.
147    pub occlusion_texture: Option<String>,
148    /// Occlusion effect strength (0 none, 1 full).
149    pub occlusion_strength: Option<f32>,
150    /// Transparency handling: one of "Opaque", "Mask", "Blend".
151    pub alpha_mode: Option<String>,
152    /// Alpha threshold for the "Mask" alpha mode.
153    pub alpha_cutoff: Option<f32>,
154}
155
156/// A partial update to the world's render and post-processing settings. Every
157/// field is optional; only the set ones change. Read the current values from
158/// `get_editor_state` (the `render` block), then send back just what you want to
159/// change.
160#[derive(Clone, Debug, Default, Serialize, Deserialize, Schema)]
161pub struct RenderSettingsPatch {
162    pub ambient_light: Option<[f32; 4]>,
163    pub bloom_enabled: Option<bool>,
164    pub bloom_intensity: Option<f32>,
165    pub bloom_threshold: Option<f32>,
166    pub bloom_knee: Option<f32>,
167    pub bloom_filter_radius: Option<f32>,
168    pub ssao_enabled: Option<bool>,
169    pub ssao_radius: Option<f32>,
170    pub ssao_intensity: Option<f32>,
171    pub ssao_bias: Option<f32>,
172    pub ssao_sample_count: Option<u32>,
173    pub ssgi_enabled: Option<bool>,
174    pub ssgi_radius: Option<f32>,
175    pub ssgi_intensity: Option<f32>,
176    pub ssgi_max_steps: Option<u32>,
177    pub ssr_enabled: Option<bool>,
178    pub ssr_max_steps: Option<u32>,
179    pub ssr_thickness: Option<f32>,
180    pub ssr_max_distance: Option<f32>,
181    pub ssr_stride: Option<f32>,
182    pub ssr_fade_start: Option<f32>,
183    pub ssr_fade_end: Option<f32>,
184    pub ssr_intensity: Option<f32>,
185    pub fog_enabled: Option<bool>,
186    pub fog_color: Option<[f32; 3]>,
187    pub fog_start: Option<f32>,
188    pub fog_end: Option<f32>,
189    pub dof_enabled: Option<bool>,
190    pub dof_focus_distance: Option<f32>,
191    pub dof_focus_range: Option<f32>,
192    pub dof_max_blur_radius: Option<f32>,
193    /// Depth-of-field quality: 0 low, 1 medium, 2 high.
194    pub dof_quality: Option<u32>,
195    pub taa_enabled: Option<bool>,
196    pub render_scale: Option<f32>,
197    pub ibl_blend_factor: Option<f32>,
198    pub unlit_mode: Option<bool>,
199    pub selection_outline_enabled: Option<bool>,
200    pub selection_outline_color: Option<[f32; 4]>,
201    pub exposure: Option<f32>,
202    pub saturation: Option<f32>,
203    pub contrast: Option<f32>,
204    pub brightness: Option<f32>,
205    pub gamma: Option<f32>,
206    pub show_grid: Option<bool>,
207    pub show_normals: Option<bool>,
208    pub ssao_visualization: Option<bool>,
209    pub navmesh_debug: Option<bool>,
210    /// PBR debug visualization index (0 is off).
211    pub pbr_debug_index: Option<u32>,
212}
213
214/// Global sky and environment controls, beyond what the UI buttons expose. Every
215/// field is optional; only the set ones are applied.
216#[derive(Clone, Debug, Default, Serialize, Deserialize, Schema)]
217pub struct Environment {
218    /// One of: None, Sky, CloudySky, Space, Nebula, Sunset, DayNight, Hdr.
219    pub atmosphere: Option<String>,
220    pub show_sky: Option<bool>,
221    /// Linear RGBA background used when the atmosphere is None.
222    pub clear_color: Option<[f32; 4]>,
223    /// Hour of day (0..24) for the DayNight atmosphere.
224    pub hour: Option<f32>,
225    pub exposure: Option<f32>,
226    /// Fetch an .hdr from this URL and use it as the skybox (sets atmosphere Hdr).
227    pub hdri_uri: Option<String>,
228}
229
230/// Host to worker. Every variant carries a correlation id except the resync
231/// request, which is keyed by subscription.
232#[derive(Clone, Debug, Serialize, Deserialize)]
233pub enum AgentRequest {
234    ListComponentTypes {
235        correlation_id: CorrelationId,
236    },
237    Query {
238        correlation_id: CorrelationId,
239        component_types: Vec<String>,
240    },
241    GetComponents {
242        correlation_id: CorrelationId,
243        entity: EntityRef,
244        component_types: Vec<String>,
245    },
246    Command {
247        correlation_id: CorrelationId,
248        command: AgentCommand,
249    },
250    Subscribe {
251        correlation_id: CorrelationId,
252        filter: SubscriptionFilter,
253    },
254    Unsubscribe {
255        correlation_id: CorrelationId,
256        subscription_id: SubscriptionId,
257    },
258    /// Drive the playback of an entity's animation player.
259    ControlAnimation {
260        correlation_id: CorrelationId,
261        entity: EntityRef,
262        command: super::AnimationCommand,
263    },
264    /// Apply a partial update to the world render and post-processing settings.
265    SetRenderSettings {
266        correlation_id: CorrelationId,
267        settings: RenderSettingsPatch,
268    },
269    /// The flattened scene hierarchy as the editor's tree panel sees it.
270    SceneTree {
271        correlation_id: CorrelationId,
272    },
273    /// Perform any editor action a user could trigger from the UI: spawning,
274    /// greybox tools, prefabs, play mode, tags, layers, undo, saving.
275    EditorAction {
276        correlation_id: CorrelationId,
277        action: Box<super::EditorAction>,
278    },
279    /// Read the editor state: render settings, the current selection (with its
280    /// name and transform), entity counts, mode, and project. The asset catalog
281    /// is separate (see ListAssets) so this stays small.
282    GetEditorState {
283        correlation_id: CorrelationId,
284    },
285    /// Set the sky and environment.
286    SetEnvironment {
287        correlation_id: CorrelationId,
288        environment: Environment,
289    },
290    /// Create or edit a named material in the library.
291    SetMaterial {
292        correlation_id: CorrelationId,
293        material: MaterialSpec,
294    },
295    /// List every material in the library with its core PBR properties.
296    ListMaterials {
297        correlation_id: CorrelationId,
298    },
299    /// The asset-browser index lists (Khronos models, Polyhaven HDRIs and
300    /// models). Split out of the editor state because the catalog is large and
301    /// rarely needed. `search` filters Polyhaven and Khronos entries by a
302    /// case-insensitive substring of the name, slug, category, or tag.
303    ListAssets {
304        correlation_id: CorrelationId,
305        search: Option<String>,
306    },
307    /// Capture the rendered viewport as a PNG. With `camera`, a frame is
308    /// rendered from that camera and the active camera is restored afterward;
309    /// without it, the current view is captured. `max_dimension` downscales the
310    /// capture (preserving aspect) so the longer side fits.
311    Screenshot {
312        correlation_id: CorrelationId,
313        camera: Option<EntityRef>,
314        max_dimension: Option<u32>,
315    },
316}
317
318/// A read against a stale handle returns this distinct outcome, never the new
319/// occupant's data.
320#[derive(Clone, Debug, Serialize, Deserialize)]
321pub enum GetResult {
322    Live {
323        entity: EntityRef,
324        components: Vec<(String, Value)>,
325    },
326    NotLive {
327        entity: EntityRef,
328    },
329}
330
331/// One entity's subscribed slice in a snapshot.
332#[derive(Clone, Debug, Serialize, Deserialize)]
333pub struct SnapshotEntity {
334    pub entity: EntityRef,
335    pub components: Vec<(String, Value)>,
336}
337
338/// A full point-in-time view of the subscribed slice, stamped with the version
339/// the collection system assigned at its slot.
340#[derive(Clone, Debug, Serialize, Deserialize)]
341pub struct Snapshot {
342    pub version: Version,
343    pub entities: Vec<SnapshotEntity>,
344}
345
346/// A cheap digest of a subscribed slice at a stated version, for the third
347/// (drift-catching) layer of desync detection.
348#[derive(Clone, Debug, Serialize, Deserialize)]
349pub struct Checksum {
350    pub version: Version,
351    pub digest: u64,
352}
353
354/// The five delta kinds. Value-changes come from change detection; structural
355/// changes from the freecs structural log. Never coalesced. `origin` is a
356/// correlation tag only and never gates whether a delta is applied.
357#[derive(Clone, Debug, Serialize, Deserialize)]
358pub enum Delta {
359    Changed {
360        entity: EntityRef,
361        component: String,
362        value: Value,
363        origin: Option<CorrelationId>,
364    },
365    Added {
366        entity: EntityRef,
367        component: String,
368        value: Value,
369        origin: Option<CorrelationId>,
370    },
371    Removed {
372        entity: EntityRef,
373        component: String,
374        origin: Option<CorrelationId>,
375    },
376    Spawned {
377        entity: EntityRef,
378        components: Vec<(String, Value)>,
379        origin: Option<CorrelationId>,
380    },
381    Despawned {
382        entity: EntityRef,
383        origin: Option<CorrelationId>,
384    },
385}
386
387/// One frame's emission. Contiguous per subscriber (`next.base == prev.target`),
388/// applied atomically. Ordered creates, then values, then deletes. Every frame
389/// emits exactly one batch while tracking is on, empty frames included.
390#[derive(Clone, Debug, Serialize, Deserialize)]
391pub struct DeltaBatch {
392    pub base_version: Version,
393    pub target_version: Version,
394    pub deltas: Vec<Delta>,
395    pub checksum: Option<Checksum>,
396}
397
398/// Worker to host.
399#[derive(Clone, Debug, Serialize, Deserialize)]
400pub enum AgentResponse {
401    ComponentTypes {
402        correlation_id: CorrelationId,
403        components: Vec<ComponentInfo>,
404    },
405    QueryResult {
406        correlation_id: CorrelationId,
407        entities: Vec<EntityRef>,
408    },
409    GetResult {
410        correlation_id: CorrelationId,
411        result: GetResult,
412    },
413    /// A command landed. Independent of the delta stream. For long-running
414    /// commands this fires at the version the last effect lands.
415    CommandApplied {
416        correlation_id: CorrelationId,
417        version: Version,
418    },
419    /// An additive glTF load finished, reporting the spawned root handles so the
420    /// agent can position the model by its root.
421    Loaded {
422        correlation_id: CorrelationId,
423        version: Version,
424        roots: Vec<EntityRef>,
425    },
426    CommandFailed {
427        correlation_id: CorrelationId,
428        error: String,
429    },
430    CommandProgress {
431        correlation_id: CorrelationId,
432        stage: String,
433    },
434    Subscribed {
435        correlation_id: CorrelationId,
436        subscription_id: SubscriptionId,
437        snapshot: Snapshot,
438    },
439    Unsubscribed {
440        correlation_id: CorrelationId,
441        subscription_id: SubscriptionId,
442    },
443    /// The current editor state (render settings, selection, counts, project).
444    EditorState {
445        correlation_id: CorrelationId,
446        state: Value,
447    },
448    /// The material library.
449    Materials {
450        correlation_id: CorrelationId,
451        materials: Value,
452    },
453    /// The asset-browser index lists.
454    Assets {
455        correlation_id: CorrelationId,
456        assets: Value,
457    },
458    /// A captured viewport frame as a base64 PNG.
459    Screenshot {
460        correlation_id: CorrelationId,
461        width: u32,
462        height: u32,
463        png_base64: String,
464    },
465    /// One batch per tracking-on frame, broadcast to the host fan-out.
466    Batch { batch: DeltaBatch },
467    /// The flattened scene hierarchy, depth-first.
468    SceneTree {
469        correlation_id: CorrelationId,
470        rows: Value,
471    },
472    /// Command, event, and error log lines from the running scene, streamed to
473    /// the host fan-out as they happen (script errors, collisions, despawns,
474    /// applied commands, toasts). Drained by the host with `poll_log`.
475    Log { entries: Value },
476}