@relu-interactives/spatial-ecs 0.2.22 → 0.2.23

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.
@@ -14,18 +14,20 @@ import { ScriptObject, type ColorInput } from "@relu-interactives/spatial-ecs";
14
14
  import * as THREE from "three";
15
15
 
16
16
  export default class WavePlane extends ScriptObject {
17
- // Typed data → inspector fields (same envelopes as Behaviours).
18
- declare data: { color: ColorInput };
17
+ // Inspector fields: an INLINE `data = { … }` literal (same envelopes as
18
+ // Behaviours). NEVER `declare data` — that is a type-only annotation with no
19
+ // runtime object, so the editor can't discover your fields and the inspector
20
+ // shows nothing to edit.
21
+ data = { color: "#3aa0ff" as ColorInput };
19
22
 
20
23
  private mat!: THREE.ShaderMaterial;
21
24
 
22
25
  // REQUIRED — build and return the visual root.
23
26
  build(): THREE.Object3D {
24
- this.data.color ??= { type: "color", value: "#3aa0ff" };
25
27
  this.mat = new THREE.ShaderMaterial({
26
28
  uniforms: {
27
29
  uTime: { value: 0 },
28
- uColor: { value: new THREE.Color(this.data.color.value) },
30
+ uColor: { value: new THREE.Color(this.data.color) },
29
31
  },
30
32
  vertexShader: /* glsl */ `
31
33
  uniform float uTime;
@@ -66,22 +68,33 @@ export default class WavePlane extends ScriptObject {
66
68
  | --- | --- | --- |
67
69
  | `this.world` | `World` | The owning ECS world. |
68
70
  | `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`. |
71
+ | `this.data` | `Record<string, unknown>` | Inspector-bound payload. Declare it with an INLINE literal (`data = { … }`) — never `declare data` (a type-only annotation with no runtime object → no inspector fields). |
70
72
  | `this.root` | `THREE.Object3D \| null` | The object `build()` returned (set after build). |
71
73
 
72
74
  ## Data fields (inspector)
73
75
 
74
- Type `data` fields with the same input envelopes as Behaviours to get inspector controls:
76
+ Declare `data` as an **inline literal** (`data = { … }`) with the same input envelopes as Behaviours. **Never use `declare data`** — it is a type-only annotation that produces no runtime object, so the editor can't discover the fields and the inspector shows nothing to edit. (Likewise, don't set defaults with `this.data.x ??= …` inside `build()` — put the default in the literal.)
77
+
78
+ Simple scalars take a value plus an `as <Input>` cast and are read directly:
79
+
80
+ ```ts
81
+ data = {
82
+ color: "#3aa0ff" as ColorInput, // color swatch → this.data.color
83
+ speed: 1.0 as FloatInput, // number field → this.data.speed
84
+ enabled: true as ToggleInput, // toggle → this.data.enabled
85
+ };
86
+ ```
87
+
88
+ Rich inputs that carry metadata are authored as inline objects and read via `.value` (or the resolved view for entity/component pickers):
75
89
 
76
90
  ```ts
77
- declare data: {
78
- color: ColorInput; // color swatch
79
- amplitude: SliderInput; // slider
80
- target: EntityInput; // entity picker
91
+ data = {
92
+ amplitude: { type: "slider", min: 0, max: 1, value: 0.5 }, // → this.data.amplitude.value
93
+ target: { type: "entity", value: null }, // → this.data.target.value
81
94
  };
82
95
  ```
83
96
 
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.
97
+ 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
98
 
86
99
  ## Rules
87
100
 
package/dist/index.cjs CHANGED
@@ -9419,9 +9419,9 @@ var _ScriptObjectSystem = class _ScriptObjectSystem {
9419
9419
  } catch {
9420
9420
  instance = new ctor(world, entityId, currentData);
9421
9421
  }
9422
- const defaults = normalizeScriptData(instance.data);
9423
- const merged = mergeDataByCurrentDefaults(defaults, currentData);
9424
- instance.data = resolveInputData(world, merged);
9422
+ const literalDefaults = normalizeScriptData(instance.data);
9423
+ const seed = Object.keys(literalDefaults).length > 0 ? mergeDataByCurrentDefaults(literalDefaults, currentData) : { ...currentData };
9424
+ instance.data = resolveInputData(world, seed);
9425
9425
  const parent = world.getComponent(entityId, Object3DRef)?.object;
9426
9426
  if (!parent) {
9427
9427
  throw new Error("Script object entity has no Object3D to attach to.");
@@ -9451,6 +9451,13 @@ var _ScriptObjectSystem = class _ScriptObjectSystem {
9451
9451
  built.userData[SCRIPT_OBJECT_ROOT_FLAG] = true;
9452
9452
  parent.add(built);
9453
9453
  instance.root = built;
9454
+ const postBuildData = normalizeScriptData(instance.data);
9455
+ const schemaDefaults = { ...literalDefaults };
9456
+ for (const [key2, value] of Object.entries(postBuildData)) {
9457
+ if (!(key2 in schemaDefaults)) schemaDefaults[key2] = value;
9458
+ }
9459
+ const merged = mergeDataByCurrentDefaults(schemaDefaults, currentData);
9460
+ instance.data = resolveInputData(world, merged);
9454
9461
  const stored = serializeDataForStorage(merged, merged);
9455
9462
  component.data = stored;
9456
9463
  component.runtimeState = "ready";