@forgeax/engine-runtime 0.1.21 → 0.1.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.
Files changed (57) hide show
  1. package/README.md +3 -3
  2. package/dist/__tests__/helpers/register-runtime-components.d.ts.map +1 -1
  3. package/dist/__tests__/helpers/register-scene-components.d.ts.map +1 -1
  4. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
  5. package/dist/createRenderer.d.ts.map +1 -1
  6. package/dist/index.mjs +3 -2
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/sprite-param-values.d.ts +1 -1
  9. package/package.json +30 -30
  10. package/src/__tests__/animation-graph-asset-union.test-d.ts +2 -0
  11. package/src/__tests__/asset-brand-and-name-retirement.test.ts +4 -3
  12. package/src/__tests__/asset-registry-scene.test-d.ts +1 -0
  13. package/src/__tests__/asset.unit.test.ts +5 -2
  14. package/src/__tests__/collect-transient-roundtrip.test.ts +14 -3
  15. package/src/__tests__/components.test-d.ts +9 -6
  16. package/src/__tests__/components.unit.test.ts +23 -19
  17. package/src/__tests__/errors.unit.test.ts +3 -0
  18. package/src/__tests__/extract-frames-merge.test.ts +3 -0
  19. package/src/__tests__/helpers/register-runtime-components.ts +9 -1
  20. package/src/__tests__/helpers/register-scene-components.ts +9 -2
  21. package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +8 -10
  22. package/src/__tests__/light-snapshot.test-d.ts +27 -1
  23. package/src/__tests__/lights-preservation.fixup.test.ts +19 -21
  24. package/src/__tests__/lights.unit.test.ts +2076 -1
  25. package/src/__tests__/loader-name-survival.test.ts +8 -9
  26. package/src/__tests__/package-promotion.test.ts +7 -11
  27. package/src/__tests__/pbr-pipeline.unit.test.ts +16 -16
  28. package/src/__tests__/pipeline.unit.test.ts +20 -24
  29. package/src/__tests__/point-light-shadow.browser.test.ts +1 -0
  30. package/src/__tests__/point-light-shadow.unit.test.ts +18 -98
  31. package/src/__tests__/propagate-transforms.dawn.test.ts +15 -16
  32. package/src/__tests__/record-worldid-isolation.test.ts +16 -32
  33. package/src/__tests__/render-error-exhaustive.test-d.ts +10 -0
  34. package/src/__tests__/render-system-extract.test.ts +5 -5
  35. package/src/__tests__/render-system-record-multi-material-textureview.test.ts +1 -0
  36. package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +1 -0
  37. package/src/__tests__/renderer-surface.unit.test.ts +0 -2
  38. package/src/__tests__/resolveName-unified-entry.test.ts +6 -6
  39. package/src/__tests__/resolveName.test.ts +14 -2
  40. package/src/__tests__/shadow-csm-cascade-loadop.test.ts +1 -0
  41. package/src/__tests__/shadow-csm-tile-consistency.test.ts +1 -0
  42. package/src/__tests__/shadow-csm-ubo.test.ts +3 -1
  43. package/src/__tests__/shadow-fields-observable.dawn.test.ts +15 -4
  44. package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +1 -1
  45. package/src/__tests__/skin-parented-double-transform.test.ts +5 -5
  46. package/src/__tests__/skinned-shadow-mixed.dawn.test.ts +1 -6
  47. package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +11 -13
  48. package/src/__tests__/ssao-bgl.test.ts +2 -1
  49. package/src/__tests__/systems.unit.test.ts +25 -174
  50. package/src/__tests__/template-game-default-api-contract.test.ts +45 -23
  51. package/src/__tests__/tilemap-chunk-extract.propagate-order.test.ts +10 -4
  52. package/src/__tests__/transform-hierarchy-pixel-diff.dawn.test.ts +2 -2
  53. package/src/collect-scene-asset.ts +1 -1
  54. package/src/components/__tests__/transform-vec-schema.test.ts +2 -2
  55. package/src/createRenderer.ts +1 -0
  56. package/src/sprite-param-values.ts +1 -1
  57. package/dist/.tsbuildinfo +0 -1
@@ -1,5 +1,5 @@
1
1
  // template-game-default-api-contract.test - the surface APIs that the
2
- // `templates/game-default/main.ts` `instantiateScenePack` helper relies on
2
+ // `apps/game-capability-lab/main.ts` scene instantiation relies on
3
3
  // must continue to exist and compose. Catches the API drift that produced
4
4
  // `TypeError: Cannot read properties of undefined (reading
5
5
  // 'setSceneAssetResolver')` at runtime when an earlier
@@ -30,7 +30,7 @@ import { fileURLToPath } from 'node:url';
30
30
  import { AssetRegistry } from '@forgeax/engine-assets-runtime';
31
31
  import { type EntityHandle, World } from '@forgeax/engine-ecs';
32
32
  import { componentDefinition } from '@forgeax/engine-ecs/internal';
33
- import { AssetGuid } from '@forgeax/engine-pack/guid';
33
+ import { AssetGuid, PackageId } from '@forgeax/engine-pack/guid';
34
34
  import { MeshRenderer, SceneInstance } from '@forgeax/engine-render';
35
35
  // Importing the runtime components barrel populates the global component
36
36
  // table consulted by `World._buildSceneEntityComponentDatas` — without
@@ -47,7 +47,7 @@ function lid(n: number): LocalEntityId {
47
47
  return n as LocalEntityId;
48
48
  }
49
49
 
50
- // Shape this fixture mirrors the `templates/game-default/scene.pack.json`
50
+ // Shape this fixture mirrors the `apps/game-capability-lab/assets/scene.pack.json`
51
51
  // SceneAsset payload after the template's `instantiateScenePack` helper has
52
52
  // rewritten ref-int → GUID strings + lifted MeshRenderer.material → materials[].
53
53
  // We register one MaterialAsset so the SceneAsset's MeshRenderer GUID slot
@@ -83,7 +83,7 @@ function buildSceneAssetWithGuidRef(): SceneAsset {
83
83
  return { kind: 'scene', entities: nodes };
84
84
  }
85
85
 
86
- describe('templates/game-default API contract (regression for `world.sceneInstances` drift)', () => {
86
+ describe('game-capability-lab API contract (regression for `world.sceneInstances` drift)', () => {
87
87
  it('(a) assets.instantiate returns the synthetic root Entity directly (no `byRef` indirection)', async () => {
88
88
  const reg = new AssetRegistry(makeMockShaderRegistry());
89
89
  const world = new World();
@@ -208,11 +208,11 @@ describe('templates/game-default API contract (regression for `world.sceneInstan
208
208
  // doesn't pass while runtime explodes (the template is a workspace
209
209
  // sibling that the engine packages don't import — typecheck never sees
210
210
  // it).
211
- it('(c2) [regression] templates/game-default/main.ts must not call `world.sceneInstances`', () => {
211
+ it('(c2) [regression] apps/game-capability-lab/main.ts must not call `world.sceneInstances`', () => {
212
212
  const here = fileURLToPath(import.meta.url);
213
213
  // here = packages/runtime/src/__tests__/<file> → 4 levels up = repo root
214
214
  const repoRoot = resolve(here, '..', '..', '..', '..', '..');
215
- const main = readFileSync(resolve(repoRoot, 'templates', 'game-default', 'main.ts'), 'utf-8');
215
+ const main = readFileSync(resolve(repoRoot, 'apps', 'game-capability-lab', 'main.ts'), 'utf-8');
216
216
  // Strip line comments so prose explaining the rename does not trip the
217
217
  // grep gate; the gate targets actual member access only.
218
218
  const noComments = main
@@ -267,17 +267,26 @@ describe('templates/game-default API contract (regression for `world.sceneInstan
267
267
 
268
268
  const here = fileURLToPath(import.meta.url);
269
269
  const repoRoot = resolve(here, '..', '..', '..', '..', '..');
270
- const packPath = resolve(repoRoot, 'templates', 'game-default', 'assets', 'scene.pack.json');
270
+ const packPath = resolve(repoRoot, 'apps', 'game-capability-lab', 'assets', 'scene.pack.json');
271
271
  const pack = JSON.parse(readFileSync(packPath, 'utf-8')) as {
272
- assets: {
273
- kind: string;
274
- payload?: { entities?: SceneEntity[]; nodes?: SceneEntity[] };
275
- }[];
272
+ assets:
273
+ | {
274
+ kind: string;
275
+ payload?: { entities?: SceneEntity[]; nodes?: SceneEntity[] };
276
+ }[]
277
+ | Record<
278
+ string,
279
+ {
280
+ kind: string;
281
+ payload?: { entities?: SceneEntity[]; nodes?: SceneEntity[] };
282
+ }
283
+ >;
276
284
  };
285
+ const assets = Array.isArray(pack.assets) ? pack.assets : Object.values(pack.assets);
277
286
 
278
287
  const offenders: string[] = [];
279
288
  let scanned = 0;
280
- for (const asset of pack.assets) {
289
+ for (const asset of assets) {
281
290
  // The envelope `kind` is the authoritative asset discriminant; the payload
282
291
  // no longer restates it (Derive, Don't Duplicate — the loader synthesises
283
292
  // Asset.kind from the envelope on load).
@@ -317,16 +326,22 @@ describe('templates/game-default API contract (regression for `world.sceneInstan
317
326
  it('(e2) [regression] every inline template material is referenced by a MeshRenderer', () => {
318
327
  const here = fileURLToPath(import.meta.url);
319
328
  const repoRoot = resolve(here, '..', '..', '..', '..', '..');
320
- const packPath = resolve(repoRoot, 'templates', 'game-default', 'assets', 'scene.pack.json');
329
+ const packPath = resolve(repoRoot, 'apps', 'game-capability-lab', 'assets', 'scene.pack.json');
330
+ type TemplatePackAsset = {
331
+ guid?: string;
332
+ kind: string;
333
+ payload?: { entities?: { components: { MeshRenderer?: { materials?: number[] } } }[] };
334
+ refs?: string[];
335
+ };
321
336
  const pack = JSON.parse(readFileSync(packPath, 'utf-8')) as {
322
- assets: {
323
- guid: string;
324
- kind: string;
325
- payload?: { entities?: { components: { MeshRenderer?: { materials?: number[] } } }[] };
326
- refs?: string[];
327
- }[];
337
+ packageId?: string;
338
+ assets: TemplatePackAsset[] | Record<string, TemplatePackAsset>;
328
339
  };
329
- const scene = pack.assets.find((asset) => asset.kind === 'scene');
340
+ const entries = Array.isArray(pack.assets)
341
+ ? pack.assets.map((asset) => ({ asset, sourceKey: undefined }))
342
+ : Object.entries(pack.assets).map(([sourceKey, asset]) => ({ asset, sourceKey }));
343
+ const sceneEntry = entries.find(({ asset }) => asset.kind === 'scene');
344
+ const scene = sceneEntry?.asset;
330
345
  expect(scene).toBeDefined();
331
346
  if (scene === undefined) return;
332
347
 
@@ -335,9 +350,16 @@ describe('templates/game-default API contract (regression for `world.sceneInstan
335
350
  (entity.components.MeshRenderer?.materials ?? []).map((index) => scene.refs?.[index]),
336
351
  ),
337
352
  );
338
- const orphanedMaterialGuids = pack.assets
339
- .filter((asset) => asset.kind === 'material')
340
- .map((asset) => asset.guid)
353
+ const packageId = pack.packageId === undefined ? undefined : PackageId.parse(pack.packageId);
354
+ const guidFor = ({ asset, sourceKey }: (typeof entries)[number]): string | undefined => {
355
+ if (asset.guid !== undefined) return asset.guid;
356
+ if (sourceKey === undefined || packageId === undefined || !packageId.ok) return undefined;
357
+ return AssetGuid.format(AssetGuid.derive(packageId.value, sourceKey));
358
+ };
359
+ const orphanedMaterialGuids = entries
360
+ .filter(({ asset }) => asset.kind === 'material')
361
+ .map(guidFor)
362
+ .filter((guid): guid is string => guid !== undefined)
341
363
  .filter((guid) => !referencedMaterialGuids.has(guid));
342
364
 
343
365
  expect(orphanedMaterialGuids).toEqual([]);
@@ -1,7 +1,13 @@
1
1
  import { type EntityHandle, Update, World } from '@forgeax/engine-ecs';
2
2
  import { MeshFilter } from '@forgeax/engine-render';
3
3
  import { TileLayer, Tilemap } from '@forgeax/engine-render/authoring';
4
- import { ChildOf, Children, registerPropagateTransforms, Transform } from '@forgeax/engine-scene';
4
+ import {
5
+ ChildOf,
6
+ Children,
7
+ GlobalTransform,
8
+ registerPropagateTransforms,
9
+ Transform,
10
+ } from '@forgeax/engine-scene';
5
11
  import { describe, expect, it } from 'vitest';
6
12
  import { tilemapChunkExtractSystem } from '../../../render/src/tilemap-chunk-extract-system';
7
13
  import { registerRuntimeComponents } from './helpers/register-runtime-components';
@@ -65,12 +71,12 @@ describe('tilemap derivation and Transform publication order', () => {
65
71
  const children = world.get(layer, Children).unwrap();
66
72
  const derived = children.entities[0] as EntityHandle | undefined;
67
73
  expect(derived).toBeDefined();
68
- const transform = derived === undefined ? undefined : world.get(derived, Transform);
74
+ const transform = derived === undefined ? undefined : world.get(derived, GlobalTransform);
69
75
  const matrix = transform?.ok ? transform.value.world : undefined;
70
76
  expect({
71
77
  derived: matrix?.[12],
72
- layer: world.get(layer, Transform).unwrap().world[12],
73
- map: world.get(map, Transform).unwrap().world[12],
78
+ layer: world.get(layer, GlobalTransform).unwrap().world[12],
79
+ map: world.get(map, GlobalTransform).unwrap().world[12],
74
80
  entityCount: world.inspect().entityCount,
75
81
  }).toEqual({ derived: 5, layer: 4, map: 4, entityCount: expect.any(Number) });
76
82
  expect(matrix?.[13]).toBeCloseTo(1);
@@ -7,7 +7,7 @@
7
7
  // through renderer.attach, spawns a non-identity parent + a ChildOf
8
8
  // child + a static reference sphere, then proves moving the PARENT moves the
9
9
  // CHILD's rendered world position (the child gets no Transform write of its
10
- // own between frames; the only change is the parent's resolved Transform.world
10
+ // own between frames; the only change is the parent's resolved GlobalTransform.world
11
11
  // mat4 propagated down the ChildOf edge). This is the vitest-dawn-project
12
12
  // counterpart to the CI
13
13
  // smoke step so `pnpm test:dawn` exercises the visual-evidence path
@@ -154,7 +154,7 @@ describe('feat-20260531 M3 w13: AC-08 parent moves -> child follows (dawn)', ()
154
154
  if (device === undefined) throw new Error('GPUDevice not captured');
155
155
 
156
156
  // Single World wires the consume path through renderer.attach so the
157
- // child's resolved Transform.world mat4 is derived each frame. feat-20260614
157
+ // child's resolved GlobalTransform.world mat4 is derived each frame. feat-20260614
158
158
  // M8: the material is a per-World column handle minted via allocSharedRef
159
159
  // (AssetRegistry has no handle concept).
160
160
  const world = new World();
@@ -336,7 +336,7 @@ export function rootsToSceneAsset(
336
336
  // mount.parent = P's ChildOf parent, and any ref to P resolves to the mount.
337
337
  const childOfTk0 = world.components.resolve('ChildOf');
338
338
  const childrenTk0 = world.components.resolve('Children');
339
- const carrierAllowed = new Set(['Transform', 'Children', 'ChildOf', 'Entity']);
339
+ const carrierAllowed = new Set(['Transform', 'GlobalTransform', 'Children', 'ChildOf', 'Entity']);
340
340
  const carrierForAnchor = new Map<number, number>(); // anchorRaw -> carrierRaw
341
341
  const carrierToAnchor = new Map<number, number>(); // carrierRaw -> anchorRaw
342
342
  const isMountCarrier = (p: number, anchorRaw: number): boolean => {
@@ -19,7 +19,7 @@
19
19
 
20
20
  import { World } from '@forgeax/engine-ecs';
21
21
  import { componentSchema } from '@forgeax/engine-ecs/internal';
22
- import { Transform } from '@forgeax/engine-scene';
22
+ import { GlobalTransform, Transform } from '@forgeax/engine-scene';
23
23
  import { describe, expect, it } from 'vitest';
24
24
 
25
25
  describe('w4 -- Transform vec schema (AC-05)', () => {
@@ -46,7 +46,7 @@ describe('w4 -- Transform vec schema (AC-05)', () => {
46
46
  });
47
47
 
48
48
  it('(b) world column keeps array<f32, 16> (outside the M2 migration surface)', () => {
49
- const schema = componentSchema(Transform) as Record<string, string>;
49
+ const schema = componentSchema(GlobalTransform) as Record<string, string>;
50
50
  expect(schema.world).toBe('array<f32, 16>');
51
51
  });
52
52
  });
@@ -40,6 +40,7 @@ export async function createRenderer(
40
40
  ? {}
41
41
  : { standardProfile: options.standardProfile }),
42
42
  ...(options.gpuPassTiming === undefined ? {} : { gpuPassTiming: options.gpuPassTiming }),
43
+ ...(options.ssrIdentity === undefined ? {} : { ssrIdentity: options.ssrIdentity }),
43
44
  };
44
45
  try {
45
46
  const constructed = await constructRuntimeRendererHost(canvas, rendererOptions, bundler);
@@ -31,7 +31,7 @@
31
31
  * replaces this asset-side region.
32
32
  * - `pivotAndSize`: vec4 `[pivotX, pivotY, sizeX, sizeY]` — `.xy` is the
33
33
  * pivot (0..1 normalised); `.zw` is the DEAD SLOT post-w11 (D-6 unit-
34
- * quad: world scale flows entirely through Transform.world, no longer
34
+ * quad: world scale flows entirely through GlobalTransform.world, no longer
35
35
  * double-applied via the UBO). Default `[0.5, 0.5, 1, 1]`.
36
36
  * - `slicesAndMode`: vec4 `[left, top, right, bottom-signed]` — 9-slice
37
37
  * border widths in UV (0..1) on the source texture region. Sum of