@forgeax/engine-scene 0.1.26 → 0.1.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeax/engine-scene",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -22,10 +22,10 @@
22
22
  "LICENSE"
23
23
  ],
24
24
  "dependencies": {
25
- "@forgeax/engine-ecs": "0.1.26",
26
- "@forgeax/engine-math": "0.1.26",
27
- "@forgeax/engine-plugin": "0.1.26",
28
- "@forgeax/engine-types": "0.1.26"
25
+ "@forgeax/engine-ecs": "0.1.28",
26
+ "@forgeax/engine-math": "0.1.28",
27
+ "@forgeax/engine-plugin": "0.1.28",
28
+ "@forgeax/engine-types": "0.1.28"
29
29
  },
30
30
  "forgeax": {
31
31
  "metrics": {
@@ -1,9 +1,57 @@
1
1
  import type { EntityHandle, World } from '@forgeax/engine-ecs';
2
2
  import { componentId } from '../../../../ecs/src/component';
3
3
  import { entityIndex } from '../../../../ecs/src/entity-handle';
4
- import { worldInternal } from '../../../../ecs/src/world-internal';
5
4
  import { ChildOf } from '../../index';
6
5
 
6
+ const WORLD_INTERNAL_KEY: unique symbol = Symbol.for(
7
+ 'forgeax.ecs.worldInternal',
8
+ ) as unknown as typeof WORLD_INTERNAL_KEY;
9
+
10
+ interface MalformedRecord {
11
+ readonly archetypeId: number;
12
+ readonly archetypeRow: number;
13
+ }
14
+
15
+ interface MalformedColumn {
16
+ readonly view: { [index: number]: number };
17
+ }
18
+
19
+ interface MalformedComponentStorage {
20
+ readonly fields: Map<string, MalformedColumn>;
21
+ }
22
+
23
+ interface MalformedTable {
24
+ readonly storage: Map<number, MalformedComponentStorage>;
25
+ }
26
+
27
+ interface MalformedArchetype {
28
+ readonly tableId: number;
29
+ readonly rows: readonly number[];
30
+ }
31
+
32
+ interface MalformedGraph {
33
+ readonly archetypes: readonly (MalformedArchetype | undefined)[];
34
+ readonly tables: readonly (MalformedTable | undefined)[];
35
+ }
36
+
37
+ interface MalformedWorldInternal {
38
+ readonly getRecords: () => readonly (MalformedRecord | undefined)[];
39
+ readonly getGraph: () => MalformedGraph;
40
+ readonly markComponentChanged: (entity: EntityHandle, componentId: number) => void;
41
+ }
42
+
43
+ interface WorldWithMalformedInjection {
44
+ readonly [WORLD_INTERNAL_KEY]: MalformedWorldInternal;
45
+ }
46
+
47
+ function malformedInjection(world: World): MalformedWorldInternal {
48
+ const internal = (world as unknown as WorldWithMalformedInjection)[WORLD_INTERNAL_KEY];
49
+ if (internal === undefined) {
50
+ throw new Error('Malformed hierarchy fixture requires the ECS test injection seam.');
51
+ }
52
+ return internal;
53
+ }
54
+
7
55
  /**
8
56
  * Scene-owned malformed-edge fixture. Production relationship writes reject
9
57
  * stale targets and cycles before touching either side of the relationship.
@@ -16,11 +64,12 @@ export function setMalformedParentEdge(
16
64
  child: EntityHandle,
17
65
  parent: EntityHandle,
18
66
  ): void {
19
- const record = world[worldInternal].getRecords()[entityIndex(child)];
67
+ const internal = malformedInjection(world);
68
+ const record = internal.getRecords()[entityIndex(child)];
20
69
  if (record === undefined || record.archetypeId < 0) {
21
70
  throw new Error(`Malformed hierarchy fixture child ${child} is not live.`);
22
71
  }
23
- const graph = world[worldInternal].getGraph();
72
+ const graph = internal.getGraph();
24
73
  const archetype = graph.archetypes[record.archetypeId];
25
74
  if (archetype === undefined) throw new Error(`Missing archetype ${record.archetypeId}.`);
26
75
  const table = graph.tables[archetype.tableId];
@@ -33,5 +82,5 @@ export function setMalformedParentEdge(
33
82
  // Keep the corruption internal, but make the source mutation observable to
34
83
  // the normal change-token consumers so an already-built projection cannot
35
84
  // mask the deliberately malformed value.
36
- world[worldInternal].markComponentChanged(child, componentId(ChildOf));
85
+ internal.markComponentChanged(child, componentId(ChildOf));
37
86
  }
@@ -1,7 +1,6 @@
1
1
  import { type EntityHandle, FixedUpdate, Update, World } from '@forgeax/engine-ecs';
2
2
  import { describe, expect, it } from 'vitest';
3
3
  import { ChangeEpochExhaustedError } from '../../../ecs/src/errors';
4
- import { worldInternal } from '../../../ecs/src/world-internal';
5
4
  import { ChildOf, GlobalTransform, propagateTransforms, Transform } from '../index';
6
5
  import { registerPropagateTransforms } from '../systems';
7
6
  import {
@@ -10,6 +9,18 @@ import {
10
9
  } from '../systems/propagate-transforms';
11
10
  import { setMalformedParentEdge } from './fixtures/malformed-hierarchy-edge';
12
11
 
12
+ const WORLD_INTERNAL_KEY: unique symbol = Symbol.for(
13
+ 'forgeax.ecs.worldInternal',
14
+ ) as unknown as typeof WORLD_INTERNAL_KEY;
15
+
16
+ interface RangePublicationInjection {
17
+ readonly markComponentRangeChanged: (...args: never[]) => void;
18
+ }
19
+
20
+ interface WorldWithRangePublicationInjection {
21
+ readonly [WORLD_INTERNAL_KEY]: RangePublicationInjection;
22
+ }
23
+
13
24
  function spawnTransform(world: World, pos: number[], parent?: EntityHandle) {
14
25
  const components = [{ component: Transform, data: { pos } }];
15
26
  if (parent === undefined) return world.spawn(...components).unwrap();
@@ -67,7 +78,7 @@ describe('scene propagation', () => {
67
78
  it('preserves the ECS range-publication error in flat propagation diagnostics', () => {
68
79
  const world = new World();
69
80
  spawnTransform(world, [2, 0, 0]);
70
- const internal = world[worldInternal];
81
+ const internal = (world as unknown as WorldWithRangePublicationInjection)[WORLD_INTERNAL_KEY];
71
82
  const originalMarkRange = internal.markComponentRangeChanged;
72
83
  Object.defineProperty(internal, 'markComponentRangeChanged', {
73
84
  configurable: true,
@@ -15,8 +15,8 @@ import {
15
15
  type DerivedRangeCursor,
16
16
  type DerivedRangeWriter,
17
17
  getDerivedWriter,
18
- worldInternal,
19
18
  } from '@forgeax/engine-ecs/internal';
19
+ import { worldRead } from '@forgeax/engine-ecs/world-read';
20
20
  import { type Mat4, mat4 } from '@forgeax/engine-math';
21
21
  import { err, ok, type Result } from '@forgeax/engine-types';
22
22
  import { ChildOf } from '../components/child-of';
@@ -374,7 +374,7 @@ function propagateFlat(world: World, scratch: Scratch): Result<void, SceneError>
374
374
  return pairError(0 as EntityHandle, 'dense Transform and GlobalTransform derived bindings');
375
375
  }
376
376
  const transformBindings = transformWriter.bindings as readonly TransformBinding[];
377
- const structureEpoch = world[worldInternal].getStructureEpoch();
377
+ const structureEpoch = world.getStructureEpoch();
378
378
  if (scratch.flatStructureEpoch === structureEpoch) return ok(undefined);
379
379
 
380
380
  ensureFlatBuffers(scratch, transformBindings);
@@ -385,7 +385,7 @@ function propagateFlat(world: World, scratch: Scratch): Result<void, SceneError>
385
385
  if (binding === undefined || changed === undefined) continue;
386
386
  for (let row = 0; row < binding.rowCapacity; row += 1) {
387
387
  const entity = (binding.entities[row] ?? 0) as EntityHandle;
388
- const parentRaw = world[worldInternal].getFieldValue(entity, ChildOf, 'parent');
388
+ const parentRaw = world[worldRead].getFieldValue(entity, ChildOf, 'parent');
389
389
  if (parentRaw !== undefined && parentRaw !== ENTITY_NULL_RAW) continue;
390
390
  countPropagation('flatStructuralRootRows');
391
391
  composeBindingRow(binding, row, undefined, 0, scratch, changed);
@@ -838,7 +838,7 @@ function walkChildren(
838
838
  continue;
839
839
  }
840
840
 
841
- const childrenLength = world[worldInternal].getArrayLength(current, Children, 'entities') ?? 0;
841
+ const childrenLength = world[worldRead].getArrayLength(current, Children, 'entities') ?? 0;
842
842
  if (nextChild >= childrenLength) {
843
843
  stackEntities.pop();
844
844
  stackChildren.pop();
@@ -857,7 +857,7 @@ function walkChildren(
857
857
  }
858
858
  stackChildren[top] = nextChild + 1;
859
859
  countPropagation('hierarchyEdgesVisited');
860
- const childRaw = world[worldInternal].getArrayElement(current, Children, 'entities', nextChild);
860
+ const childRaw = world[worldRead].getArrayElement(current, Children, 'entities', nextChild);
861
861
  if (childRaw === undefined || childRaw === ENTITY_NULL_RAW) {
862
862
  report(
863
863
  hierarchyError(
@@ -1145,7 +1145,7 @@ function propagateHierarchy(world: World, scratch: Scratch): Result<void, SceneE
1145
1145
  const entities = binding.entities;
1146
1146
  for (let row = 0; row < binding.rowCapacity; row += 1) {
1147
1147
  const entity = (entities[row] ?? 0) as EntityHandle;
1148
- const parentRaw = world[worldInternal].getFieldValue(entity, ChildOf, 'parent');
1148
+ const parentRaw = world[worldRead].getFieldValue(entity, ChildOf, 'parent');
1149
1149
  if (parentRaw === undefined || parentRaw === ENTITY_NULL_RAW) {
1150
1150
  walkChildren(
1151
1151
  world,