@relu-interactives/spatial-ecs 0.2.21 → 0.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/{System-me3iAcMF.d.cts → System-CYWZ_Zx5.d.cts} +4 -3
  2. package/dist/{System-me3iAcMF.d.ts → System-CYWZ_Zx5.d.ts} +4 -3
  3. package/dist/{Video-DHnxjGhW.d.ts → Video-C1IOat7L.d.ts} +38 -9
  4. package/dist/{Video-U9hAnf9y.d.cts → Video-ppAXaKt0.d.cts} +38 -9
  5. package/dist/{chunk-K6KMKKJ6.js → chunk-52CHDPRQ.js} +118 -92
  6. package/dist/chunk-52CHDPRQ.js.map +1 -0
  7. package/dist/{chunk-5U3YS4JD.js → chunk-GZ2NKSMV.js} +421 -65
  8. package/dist/chunk-GZ2NKSMV.js.map +1 -0
  9. package/dist/{chunk-V7BNVTJW.js → chunk-JHCPRVBC.js} +25 -4
  10. package/dist/chunk-JHCPRVBC.js.map +1 -0
  11. package/dist/{chunk-AXRCGF63.js → chunk-WKGCC7YL.js} +3 -3
  12. package/dist/{chunk-AXRCGF63.js.map → chunk-WKGCC7YL.js.map} +1 -1
  13. package/dist/components/index.cjs +22 -1
  14. package/dist/components/index.cjs.map +1 -1
  15. package/dist/components/index.d.cts +3 -3
  16. package/dist/components/index.d.ts +3 -3
  17. package/dist/components/index.js +1 -1
  18. package/dist/docs/guide/authoring-a-behaviour.md +3 -40
  19. package/dist/docs/guide/authoring-a-script-object.md +102 -0
  20. package/dist/docs/guide/behaviour-lifecycle.md +4 -4
  21. package/dist/docs/guide/spawning/script-object.md +36 -0
  22. package/dist/docs/guide/systems/_sidebar.json +4 -0
  23. package/dist/docs/guide/systems/script-behaviour-system.md +1 -17
  24. package/dist/docs/guide/systems/script-object-system.md +44 -0
  25. package/dist/factories/index.cjs +164 -117
  26. package/dist/factories/index.cjs.map +1 -1
  27. package/dist/factories/index.d.cts +5 -3
  28. package/dist/factories/index.d.ts +5 -3
  29. package/dist/factories/index.js +3 -3
  30. package/dist/{index-D7FSg0Au.d.ts → index-Cu2Pt7wN.d.cts} +6 -3
  31. package/dist/{index-CZo5I5Gs.d.cts → index-DdiWu1b6.d.ts} +6 -3
  32. package/dist/index.cjs +677 -265
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +117 -28
  35. package/dist/index.d.ts +117 -28
  36. package/dist/index.js +13 -8
  37. package/dist/index.js.map +1 -1
  38. package/dist/systems/index.cjs +403 -36
  39. package/dist/systems/index.cjs.map +1 -1
  40. package/dist/systems/index.d.cts +44 -3
  41. package/dist/systems/index.d.ts +44 -3
  42. package/dist/systems/index.js +3 -3
  43. package/dist/types.d.cts +1 -1
  44. package/dist/types.d.ts +1 -1
  45. package/dist/worldSnapshotUtils.d.cts +1 -1
  46. package/dist/worldSnapshotUtils.d.ts +1 -1
  47. package/package.json +1 -1
  48. package/dist/chunk-5U3YS4JD.js.map +0 -1
  49. package/dist/chunk-K6KMKKJ6.js.map +0 -1
  50. package/dist/chunk-V7BNVTJW.js.map +0 -1
@@ -1,5 +1,5 @@
1
- export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, n as EntityType, I as ImageComponent, o as ImageTarget, p as ImageTargetAnchor, q as ImageTargetAnchorOptions, r as ImageTargetReference, s as InstanceSlotComponent, t as InstanceTransform, u as InstancedMeshComponent, v as InstancedMeshKind, L as LightComponent, w as LightValueState, M as MaterialComponent, x as MaterialTypeValue, y as MaterialValueState, z as MeshComponent, B as MeshGeometryComponent, F as MeshGeometryParameters, G as MeshGeometryParametersByKind, H as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, J as PhysicsState, K as PlaneMeshParameters, R as RadiusHeightMeshParameters, Q as RadiusMeshParameters, S as RigidbodyState, T as RigidbodyType, U as ScriptComponent, V as ScriptEntry, W as Selectable, X as SelectionTransformState, Y as SpriteComponent, Z as TextureFilteringValue, _ as TransformModeValue, $ as TransformSpaceValue, a0 as VideoComponent, a1 as defaultMaterialValue, a2 as normalizePhysicsState } from '../Video-U9hAnf9y.cjs';
2
- export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-me3iAcMF.cjs';
3
- export { P as PrefabInstance, a as PrefabNode } from '../index-CZo5I5Gs.cjs';
1
+ export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, n as EntityType, I as ImageComponent, o as ImageTarget, p as ImageTargetAnchor, q as ImageTargetAnchorOptions, r as ImageTargetReference, s as InstanceSlotComponent, t as InstanceTransform, u as InstancedMeshComponent, v as InstancedMeshKind, L as LightComponent, w as LightValueState, M as MaterialComponent, x as MaterialTypeValue, y as MaterialValueState, z as MeshComponent, B as MeshGeometryComponent, F as MeshGeometryParameters, G as MeshGeometryParametersByKind, H as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, J as PhysicsState, K as PlaneMeshParameters, R as RadiusHeightMeshParameters, Q as RadiusMeshParameters, S as RigidbodyState, T as RigidbodyType, U as ScriptComponent, V as ScriptEntry, W as ScriptObjectComponent, X as ScriptObjectComponentInput, Y as ScriptObjectRuntimeState, Z as Selectable, _ as SelectionTransformState, $ as SpriteComponent, a0 as TextureFilteringValue, a1 as TransformModeValue, a2 as TransformSpaceValue, a3 as VideoComponent, a4 as defaultMaterialValue, a5 as normalizePhysicsState } from '../Video-ppAXaKt0.cjs';
2
+ export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-CYWZ_Zx5.cjs';
3
+ export { P as PrefabInstance, a as PrefabNode } from '../index-Cu2Pt7wN.cjs';
4
4
  import 'three';
5
5
  import '@dimforge/rapier3d-compat';
@@ -1,5 +1,5 @@
1
- export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, n as EntityType, I as ImageComponent, o as ImageTarget, p as ImageTargetAnchor, q as ImageTargetAnchorOptions, r as ImageTargetReference, s as InstanceSlotComponent, t as InstanceTransform, u as InstancedMeshComponent, v as InstancedMeshKind, L as LightComponent, w as LightValueState, M as MaterialComponent, x as MaterialTypeValue, y as MaterialValueState, z as MeshComponent, B as MeshGeometryComponent, F as MeshGeometryParameters, G as MeshGeometryParametersByKind, H as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, J as PhysicsState, K as PlaneMeshParameters, R as RadiusHeightMeshParameters, Q as RadiusMeshParameters, S as RigidbodyState, T as RigidbodyType, U as ScriptComponent, V as ScriptEntry, W as Selectable, X as SelectionTransformState, Y as SpriteComponent, Z as TextureFilteringValue, _ as TransformModeValue, $ as TransformSpaceValue, a0 as VideoComponent, a1 as defaultMaterialValue, a2 as normalizePhysicsState } from '../Video-DHnxjGhW.js';
2
- export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-me3iAcMF.js';
3
- export { P as PrefabInstance, a as PrefabNode } from '../index-D7FSg0Au.js';
1
+ export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, n as EntityType, I as ImageComponent, o as ImageTarget, p as ImageTargetAnchor, q as ImageTargetAnchorOptions, r as ImageTargetReference, s as InstanceSlotComponent, t as InstanceTransform, u as InstancedMeshComponent, v as InstancedMeshKind, L as LightComponent, w as LightValueState, M as MaterialComponent, x as MaterialTypeValue, y as MaterialValueState, z as MeshComponent, B as MeshGeometryComponent, F as MeshGeometryParameters, G as MeshGeometryParametersByKind, H as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, J as PhysicsState, K as PlaneMeshParameters, R as RadiusHeightMeshParameters, Q as RadiusMeshParameters, S as RigidbodyState, T as RigidbodyType, U as ScriptComponent, V as ScriptEntry, W as ScriptObjectComponent, X as ScriptObjectComponentInput, Y as ScriptObjectRuntimeState, Z as Selectable, _ as SelectionTransformState, $ as SpriteComponent, a0 as TextureFilteringValue, a1 as TransformModeValue, a2 as TransformSpaceValue, a3 as VideoComponent, a4 as defaultMaterialValue, a5 as normalizePhysicsState } from '../Video-C1IOat7L.js';
2
+ export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-CYWZ_Zx5.js';
3
+ export { P as PrefabInstance, a as PrefabNode } from '../index-DdiWu1b6.js';
4
4
  import 'three';
5
5
  import '@dimforge/rapier3d-compat';
@@ -1,4 +1,4 @@
1
- export { AnimationComponent, AudioComponent, CameraComponent, DEFAULT_COLLIDER_STATE, DEFAULT_ENVIRONMENT_STATE, DEFAULT_INSTANCED_MESH_PARAMETERS, DEFAULT_INSTANCE_TRANSFORM, DEFAULT_MESH_GEOMETRY_PARAMETERS, DEFAULT_PHYSICS_STATE, DEFAULT_POSTPROCESSING_STATE, DEFAULT_RIGIDBODY_STATE, EntityType, Environment, ImageComponent, ImageTarget, ImageTargetAnchor, InstanceSlotComponent, InstancedMeshComponent, LightComponent, MaterialComponent, MeshComponent, MeshGeometryComponent, ModelComponent, Name, Object3DRef, ParentId, PhysicsComponent, Postprocessing, PrefabInstance, PrefabNode, ScriptComponent, Selectable, SelectionTransformState, SpriteComponent, TransformComponent, VideoComponent, defaultMaterialValue, normalizePhysicsState } from '../chunk-V7BNVTJW.js';
1
+ export { AnimationComponent, AudioComponent, CameraComponent, DEFAULT_COLLIDER_STATE, DEFAULT_ENVIRONMENT_STATE, DEFAULT_INSTANCED_MESH_PARAMETERS, DEFAULT_INSTANCE_TRANSFORM, DEFAULT_MESH_GEOMETRY_PARAMETERS, DEFAULT_PHYSICS_STATE, DEFAULT_POSTPROCESSING_STATE, DEFAULT_RIGIDBODY_STATE, EntityType, Environment, ImageComponent, ImageTarget, ImageTargetAnchor, InstanceSlotComponent, InstancedMeshComponent, LightComponent, MaterialComponent, MeshComponent, MeshGeometryComponent, ModelComponent, Name, Object3DRef, ParentId, PhysicsComponent, Postprocessing, PrefabInstance, PrefabNode, ScriptComponent, ScriptObjectComponent, Selectable, SelectionTransformState, SpriteComponent, TransformComponent, VideoComponent, defaultMaterialValue, normalizePhysicsState } from '../chunk-JHCPRVBC.js';
2
2
  import '../chunk-OY7ZODZP.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -122,45 +122,8 @@ See [Accessing Components](./accessing-components) to read and mutate built-in c
122
122
 
123
123
  See [Reading Input](./reading-input) for the full keyboard and mouse input API, `KeyboardKeys` reference, and scripting examples.
124
124
 
125
- ## Running scripts in the editor
125
+ ## Behaviours run at preview/runtime only
126
126
 
127
- By default, `Behaviour` scripts only execute at preview or runtime. Setting **Execute in editor** to `true` on a script entry (via the inspector toggle) enables the full lifecycle (`init` → `onUpdate`) while the editor is open.
127
+ `Behaviour` scripts execute **only** at preview and runtime — never inside the editor. Gameplay logic, input handling, physics, and anything that should not run while authoring belongs in a Behaviour.
128
128
 
129
- This is useful for Three.js functionality the editor's object palette does not expose — custom shaders, procedural geometry, grass, post-processing passes, and similar.
130
-
131
- ```ts
132
- import { Behaviour } from "@relu-interactives/spatial-ecs";
133
- import * as THREE from "three";
134
-
135
- export default class GrassPlane extends Behaviour {
136
- private mesh: THREE.Mesh | null = null;
137
-
138
- protected init() {
139
- // Access the live Three.js scene directly.
140
- const scene = this.world.getScene();
141
-
142
- const geo = new THREE.PlaneGeometry(10, 10, 32, 32);
143
- const mat = new THREE.MeshStandardMaterial({ color: 0x228b22 });
144
- this.mesh = new THREE.Mesh(geo, mat);
145
- scene.add(this.mesh);
146
- }
147
-
148
- protected dispose() {
149
- if (this.mesh) {
150
- this.world.getScene().remove(this.mesh);
151
- this.mesh = null;
152
- }
153
- }
154
- }
155
- ```
156
-
157
- > [!WARNING]
158
- > **Undo-history churn** — avoid writing to ECS components (e.g. `TransformComponent`) on every frame when `executeInEditor` is `true`. The editor's change-detection system records a new undo entry each time a component snapshot differs, which will flood the undo stack and cause performance degradation. Keep per-frame mutations in local Three.js state (objects, materials, uniforms) rather than in ECS component fields.
159
-
160
- Available world accessors in editor mode:
161
-
162
- | Accessor | Returns |
163
- | --- | --- |
164
- | `this.world.getScene()` | `THREE.Scene` |
165
- | `this.world.getCamera()` | `THREE.PerspectiveCamera` |
166
- | `this.world.getRenderer()` | `THREE.WebGLRenderer` |
129
+ For a visual that must render and animate **inside the editor** (custom shaders, procedural geometry, grass, particle systems — anything the object palette can't produce), use a [Script Object](./authoring-a-script-object) instead. A `ScriptObject` builds a `THREE.Object3D` that renders in the editor AND at runtime.
@@ -0,0 +1,102 @@
1
+ # Authoring a Script Object
2
+
3
+ A **Script Object** is an entity whose THREE.js visual is built entirely by your code — custom shaders, procedural geometry, particle systems, anything the editor's object palette can't produce. Unlike a [Behaviour](./authoring-a-behaviour), a Script Object runs in the **editor AND at runtime**: `build()` and `update()` execute identically in both, so what you see while authoring is exactly what ships.
4
+
5
+ > [!IMPORTANT]
6
+ > **Do NOT gate anything on `world.isEditorMode()`.** Running everywhere is the whole point of a Script Object. Use a [Behaviour](./authoring-a-behaviour) for gameplay logic that must NOT run in the editor.
7
+
8
+ ## The contract
9
+
10
+ Subclass `ScriptObject` and export it (named export matching the class, or default export):
11
+
12
+ ```ts
13
+ import { ScriptObject, type ColorInput } from "@relu-interactives/spatial-ecs";
14
+ import * as THREE from "three";
15
+
16
+ export default class WavePlane extends ScriptObject {
17
+ // Typed data → inspector fields (same envelopes as Behaviours).
18
+ declare data: { color: ColorInput };
19
+
20
+ private mat!: THREE.ShaderMaterial;
21
+
22
+ // REQUIRED — build and return the visual root.
23
+ build(): THREE.Object3D {
24
+ this.data.color ??= { type: "color", value: "#3aa0ff" };
25
+ this.mat = new THREE.ShaderMaterial({
26
+ uniforms: {
27
+ uTime: { value: 0 },
28
+ uColor: { value: new THREE.Color(this.data.color.value) },
29
+ },
30
+ vertexShader: /* glsl */ `
31
+ uniform float uTime;
32
+ varying float vH;
33
+ void main() {
34
+ vec3 p = position;
35
+ p.z += sin(p.x * 4.0 + uTime) * 0.1;
36
+ vH = p.z;
37
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
38
+ }`,
39
+ fragmentShader: /* glsl */ `
40
+ uniform vec3 uColor;
41
+ varying float vH;
42
+ void main() { gl_FragColor = vec4(uColor * (0.6 + vH), 1.0); }`,
43
+ });
44
+ const geo = new THREE.PlaneGeometry(2, 2, 64, 64);
45
+ geo.rotateX(-Math.PI / 2);
46
+ return new THREE.Mesh(geo, this.mat);
47
+ }
48
+
49
+ // OPTIONAL — per-frame, runs in editor AND runtime.
50
+ update(deltaTime: number): void {
51
+ this.mat.uniforms.uTime.value += deltaTime;
52
+ }
53
+
54
+ // OPTIONAL — free GPU resources you created.
55
+ dispose(): void {
56
+ this.mat.dispose();
57
+ }
58
+ }
59
+ ```
60
+
61
+ `ScriptObjectSystem` calls `build()` once, attaches the returned object under the entity's Object3D (so it inherits the entity's transform, selection, and visibility), then calls `update(deltaTime)` every frame. `dispose()` runs on removal, disable, or hot-reload.
62
+
63
+ ## Members available
64
+
65
+ | Member | Type | Notes |
66
+ | --- | --- | --- |
67
+ | `this.world` | `World` | The owning ECS world. |
68
+ | `this.entityId` | `number` | The entity this script is bound to. |
69
+ | `this.data` | `Record<string, unknown>` | Inspector-bound payload. Declare a shape with `declare data: MyShape`. |
70
+ | `this.root` | `THREE.Object3D \| null` | The object `build()` returned (set after build). |
71
+
72
+ ## Data fields (inspector)
73
+
74
+ Type `data` fields with the same input envelopes as Behaviours to get inspector controls:
75
+
76
+ ```ts
77
+ declare data: {
78
+ color: ColorInput; // color swatch
79
+ amplitude: SliderInput; // slider
80
+ target: EntityInput; // entity picker
81
+ };
82
+ ```
83
+
84
+ Read a field's value as `this.data.color.value`. See [Data binding](./behaviour-data-binding/) for the full envelope catalog. Edits made in the inspector are pushed to the live instance's `this.data` between frames.
85
+
86
+ ## Rules
87
+
88
+ - **`build()` must return a `THREE.Object3D`** — a `Group`, `Mesh`, `Points`, etc. Returning anything else marks the object as errored.
89
+ - **Do NOT gate on `world.isEditorMode()`.** Build and animate unconditionally.
90
+ - **`dispose()` must free what you allocated** — geometries, materials, textures, render targets. THREE `dispose()` is idempotent, so it's safe to over-dispose. The engine removes the built subtree from the scene for you; freeing GPU memory is yours.
91
+ - **Keep per-frame work in local THREE state / uniforms.** Do NOT write ECS components every frame from `update()` — that churns the editor's undo history.
92
+ - **Pivot at the origin, meters for scale.** The built object rides the entity's transform, so build it centered on `(0,0,0)` at real-world scale.
93
+ - **Imports**: only `three` (and `three/examples/jsm/*`) and `@relu-interactives/spatial-ecs`. Other bare imports won't resolve.
94
+
95
+ ## Behaviour vs Script Object
96
+
97
+ | | Behaviour | Script Object |
98
+ | --- | --- | --- |
99
+ | Runs in editor | ❌ preview/runtime only | ✅ editor AND runtime |
100
+ | Purpose | gameplay logic, input, physics | visuals: shaders, procedural geometry, particles |
101
+ | Produces | side effects on the world | a `THREE.Object3D` (the entity's visual) |
102
+ | Hooks | `init` / `onUpdate` / `dispose` | `build` / `update` / `dispose` |
@@ -28,13 +28,13 @@ This ordering means:
28
28
 
29
29
  | Hook | Override? | Frequency | Runs in editor? | Purpose |
30
30
  | --- | --- | --- | --- | --- |
31
- | `init()` | Yes (protected) | Once before first `onUpdate` | Only when `executeInEditor: true` | Default and validate `data`, cache references |
32
- | `onUpdate()` | Yes (protected) | Every frame | Only when `executeInEditor: true` | Per-frame logic |
33
- | `dispose()` | Yes | Once on detach/destroy | Only when `executeInEditor: true` | Release timers, listeners, resources |
31
+ | `init()` | Yes (protected) | Once before first `onUpdate` | No | Default and validate `data`, cache references |
32
+ | `onUpdate()` | Yes (protected) | Every frame | No | Per-frame logic |
33
+ | `dispose()` | Yes | Once on detach/destroy | No | Release timers, listeners, resources |
34
34
  | `onSelect()` / `onDeselect()` | Optional | Editor selection toggles | Yes | UX-only hooks |
35
35
  | `onTargetFound()` / `onTargetLost()` | Optional | Image tracking | No | React to AR target visibility |
36
36
 
37
- Set `executeInEditor: true` on a script entry in the inspector to opt a script into the full lifecycle in editor mode. See [Running scripts in the editor](./authoring-a-behaviour#running-scripts-in-the-editor).
37
+ Behaviours never run in the editor. For a visual that must render and animate in the editor, use a [Script Object](./authoring-a-script-object).
38
38
 
39
39
  <!-- ## Tips
40
40
 
@@ -0,0 +1,36 @@
1
+ # Script Object
2
+
3
+ An entity whose visual is built by a user [`ScriptObject`](../authoring-a-script-object) script (custom shaders, procedural geometry, particles). Renders and animates in the editor AND at runtime. Starts as an empty `THREE.Group`; `ScriptObjectSystem` builds the visual once a script is linked.
4
+
5
+ ```ts
6
+ import { Behaviour } from "@relu-interactives/spatial-ecs";
7
+
8
+ export default class Spawner extends Behaviour {
9
+ init() {
10
+ const objects = this.world.getObjectManagementSystem();
11
+ objects?.requestCreate("scriptObject", {
12
+ name: "Wave",
13
+ transform: { position: { x: 0, y: 0, z: -3 } },
14
+ options: {
15
+ scriptUrl: "localasset://.../scripts/WavePlane.js",
16
+ className: "WavePlane",
17
+ },
18
+ });
19
+ }
20
+ }
21
+ ```
22
+
23
+ ## Payload
24
+
25
+ | Field | Type | Default | Description |
26
+ | --- | --- | --- | --- |
27
+ | `name` | `string` | `"Script Object"` | Display name. |
28
+ | `transform` | `TransformData` | identity | Position / rotation (radians) / scale. |
29
+ | `options.scriptUrl` | `string \| null` | `null` | Compiled script URL. Optional — can be assigned later in the inspector. |
30
+ | `options.className` | `string \| null` | `null` | Exported `ScriptObject` class name. |
31
+ | `options.enabled` | `boolean` | `true` | When `false`, the visual isn't built/ticked. |
32
+ | `options.data` | `Record<string, unknown>` | `{}` | Initial inspector data. |
33
+
34
+ ## Components attached
35
+
36
+ `Transform`, `Object3DRef`, `Name`, `EntityType`, `ParentId`, `Selectable`, `ScriptObjectComponent`.
@@ -59,6 +59,10 @@
59
59
  "text": "ScriptBehaviourSystem",
60
60
  "link": "/guide/systems/script-behaviour-system"
61
61
  },
62
+ {
63
+ "text": "ScriptObjectSystem",
64
+ "link": "/guide/systems/script-object-system"
65
+ },
62
66
  {
63
67
  "text": "TransformSyncSystem",
64
68
  "link": "/guide/systems/transform-sync-system"
@@ -34,23 +34,7 @@ If no valid `Behaviour` subclass is found the entry is skipped and an error is w
34
34
  - **Instance keying** — each script instance is keyed by `entityId + scriptUrl + className`. A new instance is created only when any of these three values changes.
35
35
  - **Error isolation** — errors thrown inside `init()` or `onUpdate()` are caught per-instance and logged to the console. A failing script does not crash the frame loop or affect other scripts.
36
36
  - **Destroyed entity cleanup** — when an entity is removed from the world, all of its script instances are destroyed and removed from the registry on the next update.
37
- - **Editor vs preview** — the system is used in both the editor and preview. By default, scripts are **not** executed in the editor. Set `executeInEditor: true` on a `ScriptEntry` to opt a script into full lifecycle execution (including `init()`) while the editor is open. Scripts without this flag stay in `idle` state in editor mode and never call `init()` or `onUpdate()`.
38
-
39
- ## Editor mode execution
40
-
41
- When `ScriptEntry.executeInEditor` is `true`:
42
-
43
- 1. The script module is loaded and the class is instantiated as normal.
44
- 2. `init()` is called once (same as preview).
45
- 3. `onUpdate()` is called every frame while the script is enabled.
46
- 4. Direct Three.js scene access is available via `this.world.getScene()`, `getCamera()`, and `getRenderer()`.
47
-
48
- This allows scripts to create geometry, shaders, materials, and any other Three.js construct that the editor's object palette does not expose.
49
-
50
- > [!WARNING]
51
- > Writing to ECS components (e.g. `TransformComponent`) every frame in editor mode will generate an undo history entry on every tick. Keep per-frame mutations in local Three.js state to avoid flooding the undo stack.
52
-
53
- See [Running scripts in the editor](../authoring-a-behaviour#running-scripts-in-the-editor) for usage examples.
37
+ - **Editor vs preview** — Behaviours execute **only** at preview/runtime. In an editor world (`isEditorMode()`), every `ScriptComponent` entry stays in `idle` state and `init()`/`onUpdate()` never run. For visuals that must render in the editor, use [`ScriptObjectSystem`](./script-object-system) + [`ScriptObject`](../authoring-a-script-object).
54
38
 
55
39
  ## API reference
56
40
 
@@ -0,0 +1,44 @@
1
+ # ScriptObjectSystem
2
+
3
+ Loads, builds, ticks, and disposes [`ScriptObject`](../authoring-a-script-object) scripts attached via `ScriptObjectComponent`. Unlike [`ScriptBehaviourSystem`](./script-behaviour-system), it runs in the **editor AND at runtime** — there is no `isEditorMode()` gating.
4
+
5
+ ## Lifecycle
6
+
7
+ For each entity with an enabled `ScriptObjectComponent` that has a `scriptUrl` + `className`:
8
+
9
+ 1. **Load** — the module is fetched via the `moduleLoader`, the class resolved (named export → `default` → `__reluScriptObjectClass` marker), and instantiated. `runtimeState` transitions `idle → loading`.
10
+ 2. **Build** — `build()` runs; the returned `THREE.Object3D` is tagged (`userData.__scriptObjectRoot`) and attached under the entity's `Object3DRef.object`. `runtimeState → ready`.
11
+ 3. **Update** — every frame while enabled and visible, `update(deltaTime)` is called. Inspector data edits are pushed to `this.data` between frames (dirty-checked, not per-frame).
12
+ 4. **Dispose** — on removal, disable, script change, or `invalidate*()`, `dispose()` is called and the built subtree is removed and its geometries/materials/textures freed (shared model-cache resources are skipped).
13
+
14
+ `runtimeState` is written **only on transitions** (never every frame). A `build()`/`update()` error sets `runtimeState: "error"` with a sanitized message and tombstones the record so a broken script doesn't reload every frame (cleared by a script/enabled change or `invalidate*()`).
15
+
16
+ ## Keying
17
+
18
+ Instances are keyed by **entity id** — a duplicated entity gets its own fresh instance, and two entities sharing one `scriptUrl` get independent instances.
19
+
20
+ ## Hot reload
21
+
22
+ - `invalidateAll()` — disposes every live instance so the next `update()` reloads with freshly compiled modules. The editor calls this on its asset-changed epoch.
23
+ - `invalidate(scriptUrl)` — same, scoped to one script.
24
+
25
+ The `moduleLoader` owns cache-busting: with the default dynamic-import loader the URL must change for fresh code to load; `invalidate*()` guarantees re-instantiation.
26
+
27
+ ## Wiring
28
+
29
+ Construct it and pass it to `World.setup`:
30
+
31
+ ```ts
32
+ import { ScriptObjectSystem } from "@relu-interactives/spatial-ecs";
33
+
34
+ world.setup({
35
+ systems: [new ScriptObjectSystem(myModuleLoader)],
36
+ isEditor: false,
37
+ });
38
+ ```
39
+
40
+ ## API reference
41
+
42
+ - Class: [`ScriptObjectComponent`](../../api/components/classes/ScriptObjectComponent.md)
43
+ - Base class: [`ScriptObject`](../../api/classes/ScriptObject.md)
44
+ - Source: [`core/systems/ScriptObjectSystem.ts`](https://github.com/Relu-Interactives/relu-spatial-ecs-engine/blob/main/core/systems/ScriptObjectSystem.ts)