@forgeax/engine-scene 0.1.23 → 0.1.25

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.
package/README.md CHANGED
@@ -19,8 +19,29 @@ await context.fiber.restart();
19
19
  ```
20
20
 
21
21
  `scenePlugin()` is a native Cordis plugin. Its Fiber installs hierarchy
22
- propagation and removes it when the realm unloads. Read failures as
23
- `SceneError` and branch on `error.code`; do not parse messages.
22
+ propagation and removes it when the realm unloads. The direct
23
+ `propagateTransforms(world)` entry point returns `Result<void, SceneError>`;
24
+ branch on `error.code` and repair a diagnosable stale edge, mirror mismatch, or
25
+ cycle through `World.set`/`World.removeComponent` (or the owning structural
26
+ command) before retrying while the World is healthy.
27
+
28
+ When the plugin runs from the scheduled path, `world.update(deltaSeconds)`
29
+ wraps a thrown Scene failure as `system-failed`; the wrapper's
30
+ `error.detail.cause` is the original `SceneError` (including its structured
31
+ `code`/`detail`). That post-write system failure poisons the World, so the next
32
+ use returns `world-poisoned`. Inspect `detail.cause`, stop using that World
33
+ identity, discard it, and ask the App execution owner to call
34
+ `app.execution.rebuild()` for a fresh World. Do not retry a poisoned or
35
+ partially-written World in place.
36
+
37
+ If an explicitly malformed internal fixture reports a `Children` mirror
38
+ mismatch, never write the target array directly. A same-target
39
+ `world.set(child, ChildOf, { parent })` intentionally records the source
40
+ evidence but skips mirror/index churn, so it cannot repair that mismatch. On a
41
+ healthy World, repair through the source owner by removing and re-adding
42
+ `ChildOf` (or reparenting through another target and then back to the intended
43
+ one); if the failure has poisoned the World, discard it and rebuild through the
44
+ App instead.
24
45
 
25
46
  Transform propagation writes the exact recomputed frontier into
26
47
  `GlobalTransform` and advances those rows' ordinary component versions only
@@ -30,6 +51,18 @@ subtree is exposed as contiguous spans without a parallel event journal.
30
51
  `Transform` remains authored local TRS; `GlobalTransform` is the resolved
31
52
  world-space authority.
32
53
 
54
+ For a valid hierarchy, propagation expands the ECS-maintained `Children`
55
+ buffer from each root in parent-first order. `ChildOf` is the writable source
56
+ fact; `Children` is a read-only materialized target, so source mutations and
57
+ their reverse lists converge before the frame pass. The numeric traversal
58
+ borrows ECS columns and uses only constant matrix scratch plus depth/row
59
+ markers; it does not build a Scene graph or per-node matrix cache.
60
+
61
+ Removing `ChildOf` is a structural root transition. Even when the authored
62
+ `Transform` is unchanged, the next propagation composes and publishes that
63
+ entity as a flat local root; ordinary same-value hierarchy recomputation still
64
+ does not publish an unchanged `GlobalTransform` row.
65
+
33
66
  Flat propagation uses the same numeric kernel inline or through an installed
34
67
  SharedKernel executor. It borrows the existing changed query, joins root
35
68
  matrices before resolving the hierarchy, and leaves small or fragmented ranges
@@ -50,6 +83,16 @@ the full local/world pair transitively, while explicit `Transform` data still
50
83
  wins. This keeps hierarchy authoring small without adding per-frame repair or
51
84
  an additional scene-side component registry.
52
85
 
86
+ Deferred `Commands.spawn` uses the same `{ component, data }` entries as
87
+ `World.spawn`; the pending child is materialized and linked at command flush.
88
+ For an existing child, the generic reparent call is
89
+ `world.reparent(child, newParent, ChildOf, { parent: newParent })` and routes
90
+ through the same relationship owner.
91
+
92
+ `ChildOf` uses `linkedSpawn: true`: despawning a parent recursively despawns
93
+ its linked hierarchy. A generic relationship may opt out, but that is not the
94
+ Scene hierarchy lifecycle.
95
+
53
96
  Removing the required component is still an explicit malformed-state escape
54
97
  hatch, not an automatic cascade. Propagation reports that state as a
55
98
  structured `SceneError`, preserving a clear owner and recovery path.
@@ -1,8 +1,10 @@
1
1
  import type { EntityHandle, World } from '@forgeax/engine-ecs';
2
2
  /**
3
- * Scene-owned malformed-edge fixture. The public relationship write accepts
4
- * stale handles as data; the projection then owns the liveness diagnostic.
5
- * Keeping this fixture on `world.set` avoids a private ECS graph seam.
3
+ * Scene-owned malformed-edge fixture. Production relationship writes reject
4
+ * stale targets and cycles before touching either side of the relationship.
5
+ * This test-only fixture deliberately corrupts the source column through the
6
+ * existing package-internal graph seam so projection can exercise its
7
+ * fail-closed diagnostics without weakening the public write contract.
6
8
  */
7
9
  export declare function setMalformedParentEdge(world: World, child: EntityHandle, parent: EntityHandle): void;
8
10
  //# sourceMappingURL=malformed-hierarchy-edge.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"malformed-hierarchy-edge.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fixtures/malformed-hierarchy-edge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAG/D;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,YAAY,GACnB,IAAI,CAEN"}
1
+ {"version":3,"file":"malformed-hierarchy-edge.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fixtures/malformed-hierarchy-edge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAM/D;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,YAAY,GACnB,IAAI,CAmBN"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hierarchy-propagation.perf.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchy-propagation.perf.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/hierarchy-propagation.perf.test.ts"],"names":[],"mappings":""}
@@ -3,15 +3,16 @@ export { ChildOf } from './children';
3
3
  * Hierarchy back-reference: pointer from child entity to its parent.
4
4
  *
5
5
  * Store the parent `Entity` handle (returned by `world.spawn(...).unwrap()`)
6
- * in the `parent` field; `propagateTransforms` reads it each frame to
7
- * compose the child's derived `GlobalTransform.world` mat4 (child.world =
8
- * parent.world x child local). `Transform` declares `GlobalTransform` as a
6
+ * in the `parent` field; `propagateTransforms` follows the ECS-maintained
7
+ * `Children` list each frame to compose the child's derived
8
+ * `GlobalTransform.world` mat4 (child.world = parent.world x child local).
9
+ * `Transform` declares `GlobalTransform` as a
9
10
  * generic ECS requirement, so normal spawn/add/command paths materialize the
10
11
  * pair without scene-specific repair. Propagation still fails closed when an
11
- * owner explicitly removes one half. Despawning a child auto-detaches it from the
12
- * parent's Children list (relationship `onRemove` hook). Despawning the
13
- * parent does not auto-clean the child's ChildOf; the consumer removes the
14
- * stale ChildOf (`world.removeComponent`) or checks `world.get(parent, Entity)` for liveness.
12
+ * owner explicitly removes one half. Despawning a child auto-detaches it from
13
+ * its parent's Children list; despawning the parent follows ChildOf's
14
+ * linkedSpawn cascade, while a malformed internal fixture can still leave a
15
+ * stale source for Scene diagnostics.
15
16
  *
16
17
  * Because ChildOf declares a `relationship` mirror and a structural
17
18
  * `Transform` requirement, the engine keeps the parent's `Children.entities`
@@ -22,8 +23,9 @@ export { ChildOf } from './children';
22
23
  * prior OOS-10 contract is retired).
23
24
  * Reparenting is a plain re-add: `world.addComponent(child, { component:
24
25
  * ChildOf, data: { parent: newParent } })` on an entity that already carries
25
- * ChildOf auto-reparents (exclusive arm), or use `world.reparent(child,
26
- * newParent)`.
26
+ * ChildOf auto-reparents (exclusive arm). The generic convenience method has
27
+ * the same owner path and requires the relationship component plus its data:
28
+ * `world.reparent(child, newParent, ChildOf, { parent: newParent })`.
27
29
  *
28
30
  * @example Spawn a child entity referencing an already-spawned root:
29
31
  * const root = world.spawn(
@@ -1 +1 @@
1
- {"version":3,"file":"child-of.d.ts","sourceRoot":"","sources":["../../src/components/child-of.ts"],"names":[],"mappings":"AA2DA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG"}
1
+ {"version":3,"file":"child-of.d.ts","sourceRoot":"","sources":["../../src/components/child-of.ts"],"names":[],"mappings":"AA6DA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG"}
@@ -2,25 +2,26 @@
2
2
  * Hierarchy forward-list of child entities.
3
3
  *
4
4
  * `entities` is a variable-length `array<entity>` field; each element is
5
- * an `Entity` u32 the AI user pushed via
6
- * `world.push(parent, Children, 'entities', child)`. The value returned by
7
- * `world.get(parent, Children).unwrap().entities` is a read-only
5
+ * an `Entity` u32 the ECS relationship owner materialized. The value returned
6
+ * by `world.get(parent, Children).unwrap().entities` is a detached read-only
8
7
  * `Uint32Array` snapshot rematerialised fresh on every read (D-4 no-cache);
9
- * the snapshot's `length` equals the live element count.
8
+ * mutating the returned array cannot change ECS-owned storage, and the
9
+ * snapshot's `length` equals the live element count. Internal Scene/ECS paths
10
+ * use the package-internal zero-copy array seams instead of this snapshot.
10
11
  *
11
12
  * Invariants:
12
- * - Children is NOT consumed by `propagateTransforms` (which walks ChildOf
13
- * upward, D-P2). The forward list is for AI-user traversal / debug /
14
- * inspection.
13
+ * - `propagateTransforms` consumes Children from the ECS-owned materialized
14
+ * buffer and expands each root parent-first. The forward list is also
15
+ * available for AI-user traversal / debug / inspection.
15
16
  * - Children <-> ChildOf consistency is maintained by the engine via the
16
17
  * ChildOf `relationship` mirror hook (see ./child-of.ts): adding /
17
18
  * removing / reparenting ChildOf on a child auto-updates the parent's
18
19
  * `entities` list. AI users do not hand-sync the two sides for the
19
20
  * hierarchy.
20
- * - Stored entity u32s are NOT auto-cleared when the referenced entity is
21
- * despawned (OOS-01 above); a `world.get(despawnedChild, ...)` call
22
- * returns `Result.err(...)` so AI users discover the dangling state
23
- * through the engine's structured-error channel.
21
+ * - ChildOf's linked lifecycle keeps ordinary Children entries aligned: a
22
+ * child despawn prunes its source slot and a parent despawn cascades. A
23
+ * deliberately malformed/non-linked edge remains observable as a dead
24
+ * handle and must be diagnosed through the structured error channel.
24
25
  *
25
26
  * @example Spawn a parent and two children via ChildOf (engine maintains Children):
26
27
  * const parent = world.spawn({ component: Transform, data: identityXf() }).unwrap();
@@ -36,8 +37,8 @@
36
37
  * const snap = world.get(parent, Children).unwrap().entities;
37
38
  * for (let i = 0; i < snap.length; i++) {
38
39
  * const child = snap[i];
39
- * // ... consume; world.get(child, ...) surfaces a structured error
40
- * // if the child has been despawned (OOS-01 dangling-entity surface).
40
+ * // ... consume; probe the handle before using it when reading a
41
+ * // deliberately malformed/non-linked relationship.
41
42
  * }
42
43
  */
43
44
  export declare const ChildOf: import("@forgeax/engine-ecs").Component<"ChildOf", {
@@ -1 +1 @@
1
- {"version":3,"file":"children.d.ts","sourceRoot":"","sources":["../../src/components/children.ts"],"names":[],"mappings":"AAgEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAgB,OAAO;;IAAU,QAAQ;;GAY9C,CAAC"}
1
+ {"version":3,"file":"children.d.ts","sourceRoot":"","sources":["../../src/components/children.ts"],"names":[],"mappings":"AAwDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAgB,OAAO;;IAAU,QAAQ;;GAY9C,CAAC"}