@forgeax/engine-app 0.1.4 → 0.1.6

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 (145) hide show
  1. package/README.md +20 -14
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/create-app-error-retention.integration.test.d.ts +2 -0
  4. package/dist/__tests__/create-app-error-retention.integration.test.d.ts.map +1 -0
  5. package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts +2 -0
  6. package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts.map +1 -0
  7. package/dist/animation-asset-lookup.d.ts +1 -2
  8. package/dist/animation-asset-lookup.d.ts.map +1 -1
  9. package/dist/create-app.d.ts +31 -10
  10. package/dist/create-app.d.ts.map +1 -1
  11. package/dist/engine-worker-runtime.mjs +72 -129
  12. package/dist/engine-worker-runtime.mjs.map +1 -1
  13. package/dist/errors.d.ts +3 -12
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/execution/attached-world-swap.d.ts +1 -12
  16. package/dist/execution/attached-world-swap.d.ts.map +1 -1
  17. package/dist/execution/bootstrap-url.d.ts +5 -0
  18. package/dist/execution/bootstrap-url.d.ts.map +1 -0
  19. package/dist/execution/control.d.ts +0 -2
  20. package/dist/execution/control.d.ts.map +1 -1
  21. package/dist/execution/engine-worker.d.ts +1 -0
  22. package/dist/execution/engine-worker.d.ts.map +1 -1
  23. package/dist/execution/host-controller.d.ts.map +1 -1
  24. package/dist/execution/index.d.ts +1 -1
  25. package/dist/execution/index.d.ts.map +1 -1
  26. package/dist/execution/protocol.d.ts +4 -1
  27. package/dist/execution/protocol.d.ts.map +1 -1
  28. package/dist/execution/types.d.ts +17 -1
  29. package/dist/execution/types.d.ts.map +1 -1
  30. package/dist/game-context.d.ts +1 -1
  31. package/dist/game-context.d.ts.map +1 -1
  32. package/dist/index.d.ts +4 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.mjs +331 -362
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/internal/assembled-engine-profile.d.ts.map +1 -1
  37. package/dist/internal/assets-world-plugin.d.ts +3 -17
  38. package/dist/internal/assets-world-plugin.d.ts.map +1 -1
  39. package/dist/internal/browser-remote-bridge.d.ts +1 -2
  40. package/dist/internal/browser-remote-bridge.d.ts.map +1 -1
  41. package/dist/internal/browser-rhi-debug-runtime.d.ts +1 -1
  42. package/dist/internal/browser-rhi-debug-runtime.d.ts.map +1 -1
  43. package/dist/internal/debug-draw.d.ts +9 -0
  44. package/dist/internal/debug-draw.d.ts.map +1 -0
  45. package/dist/internal/engine-profile-common.d.ts +2 -2
  46. package/dist/internal/engine-profile-common.d.ts.map +1 -1
  47. package/dist/internal/frame-loop.d.ts +7 -4
  48. package/dist/internal/frame-loop.d.ts.map +1 -1
  49. package/dist/internal/main-engine-profile.d.ts +4 -0
  50. package/dist/internal/main-engine-profile.d.ts.map +1 -1
  51. package/dist/internal/remote-serve-flag.d.ts +3 -7
  52. package/dist/internal/remote-serve-flag.d.ts.map +1 -1
  53. package/dist/internal/rhi-capture.d.ts +1 -1
  54. package/dist/internal/rhi-capture.d.ts.map +1 -1
  55. package/dist/internal/worker-engine-profile.d.ts +2 -0
  56. package/dist/internal/worker-engine-profile.d.ts.map +1 -1
  57. package/dist/renderer-plugin.d.ts +25 -0
  58. package/dist/renderer-plugin.d.ts.map +1 -0
  59. package/dist/tool-preview/bootstrap.d.ts.map +1 -1
  60. package/dist/types.d.ts +21 -61
  61. package/dist/types.d.ts.map +1 -1
  62. package/package.json +25 -26
  63. package/src/__tests__/callback-inference.test-d.ts +7 -3
  64. package/src/__tests__/consumer-migration.unit.test.ts +13 -0
  65. package/src/__tests__/create-app-error-retention.integration.test.ts +125 -0
  66. package/src/__tests__/create-app-features.test.ts +21 -10
  67. package/src/__tests__/create-app-listener-sync.test.ts +23 -19
  68. package/src/__tests__/create-app-physics-plugin-failure.integration.test.ts +142 -0
  69. package/src/__tests__/create-app-stop.test.ts +4 -12
  70. package/src/__tests__/create-app.test-d.ts +12 -11
  71. package/src/__tests__/create-app.test.ts +1 -60
  72. package/src/__tests__/create-renderer-error.integration.test.ts +3 -2
  73. package/src/__tests__/create-renderer-lifecycle.integration.test.ts +10 -5
  74. package/src/__tests__/draw-source-injected-world-update-visibility.test.ts +7 -13
  75. package/src/__tests__/draw-source-single-world-regression.test.ts +23 -24
  76. package/src/__tests__/engine-worker-bootstrap-channel.unit.test.ts +8 -0
  77. package/src/__tests__/engine-worker-world-swap.unit.test.ts +15 -22
  78. package/src/__tests__/errors-pointer-lock-failed.test.ts +4 -7
  79. package/src/__tests__/execution-bootstrap-isolation.unit.test.ts +5 -6
  80. package/src/__tests__/execution-frame-order.unit.test.ts +2 -2
  81. package/src/__tests__/execution-frame-protocol.unit.test.ts +3 -4
  82. package/src/__tests__/execution-public-api.test-d.ts +4 -0
  83. package/src/__tests__/frame-loop-time-elapsed.test.ts +1 -1
  84. package/src/__tests__/frame-loop-time.unit.test.ts +1 -1
  85. package/src/__tests__/frame-loop-world-array.test.ts +82 -73
  86. package/src/__tests__/plugin-profiles.unit.test.ts +3 -3
  87. package/src/__tests__/profiler-default-off.test.ts +1 -1
  88. package/src/__tests__/profiler-frame-token.test.ts +17 -30
  89. package/src/__tests__/profiler-phase-integration.test.ts +15 -27
  90. package/src/__tests__/surface-handoff.test.ts +1 -4
  91. package/src/__tests__/visibility-remote.integration.test.ts +2 -5
  92. package/src/__tests__/worker-execution-stop.test.ts +64 -7
  93. package/src/animation-asset-lookup.ts +2 -2
  94. package/src/create-app.ts +197 -296
  95. package/src/errors.ts +27 -39
  96. package/src/execution/attached-world-swap.ts +4 -38
  97. package/src/execution/bootstrap-url.ts +25 -0
  98. package/src/execution/control.ts +3 -7
  99. package/src/execution/engine-worker-runtime.ts +23 -40
  100. package/src/execution/engine-worker.ts +2 -0
  101. package/src/execution/host-controller.ts +8 -2
  102. package/src/execution/index.ts +1 -0
  103. package/src/execution/protocol.ts +4 -1
  104. package/src/execution/types.ts +18 -1
  105. package/src/game-context.ts +1 -1
  106. package/src/index.ts +11 -4
  107. package/src/internal/assembled-engine-profile.ts +3 -11
  108. package/src/internal/assets-world-plugin.ts +7 -53
  109. package/src/internal/browser-remote-bridge.ts +2 -3
  110. package/src/internal/browser-rhi-debug-runtime.ts +1 -1
  111. package/src/internal/debug-draw.ts +38 -0
  112. package/src/internal/engine-profile-common.ts +2 -3
  113. package/src/internal/frame-loop.ts +77 -63
  114. package/src/internal/main-engine-profile.ts +21 -17
  115. package/src/internal/remote-serve-flag.ts +5 -14
  116. package/src/internal/rhi-capture.ts +1 -1
  117. package/src/internal/worker-engine-profile.ts +10 -10
  118. package/src/renderer-plugin.ts +96 -0
  119. package/src/tool-preview/bootstrap.ts +1 -14
  120. package/src/types.ts +22 -77
  121. package/src/vite-env.d.ts +0 -2
  122. package/dist/__tests__/app-rebuild.test.d.ts +0 -2
  123. package/dist/__tests__/app-rebuild.test.d.ts.map +0 -1
  124. package/dist/__tests__/asset-registry-types.test-d.d.ts +0 -2
  125. package/dist/__tests__/asset-registry-types.test-d.d.ts.map +0 -1
  126. package/dist/__tests__/assets-runtime-assembly.integration.test.d.ts +0 -2
  127. package/dist/__tests__/assets-runtime-assembly.integration.test.d.ts.map +0 -1
  128. package/dist/__tests__/draw-source-headless-full-chain.test.d.ts +0 -2
  129. package/dist/__tests__/draw-source-headless-full-chain.test.d.ts.map +0 -1
  130. package/dist/__tests__/ecs-import.unit.test.d.ts +0 -2
  131. package/dist/__tests__/ecs-import.unit.test.d.ts.map +0 -1
  132. package/dist/__tests__/execution-world-health-owner.test-d.d.ts +0 -2
  133. package/dist/__tests__/execution-world-health-owner.test-d.d.ts.map +0 -1
  134. package/dist/__tests__/remote-serve-flag.test.d.ts +0 -2
  135. package/dist/__tests__/remote-serve-flag.test.d.ts.map +0 -1
  136. package/dist/assets-runtime-assembly.d.ts +0 -19
  137. package/dist/assets-runtime-assembly.d.ts.map +0 -1
  138. package/src/__tests__/app-rebuild.test.ts +0 -31
  139. package/src/__tests__/asset-registry-types.test-d.ts +0 -16
  140. package/src/__tests__/assets-runtime-assembly.integration.test.ts +0 -68
  141. package/src/__tests__/draw-source-headless-full-chain.test.ts +0 -226
  142. package/src/__tests__/ecs-import.unit.test.ts +0 -40
  143. package/src/__tests__/execution-world-health-owner.test-d.ts +0 -9
  144. package/src/__tests__/remote-serve-flag.test.ts +0 -24
  145. package/src/assets-runtime-assembly.ts +0 -70
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=create-app-error-retention.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-app-error-retention.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/create-app-error-retention.integration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=create-app-physics-plugin-failure.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-app-physics-plugin-failure.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/create-app-physics-plugin-failure.integration.test.ts"],"names":[],"mappings":""}
@@ -1,7 +1,6 @@
1
1
  import type { AnimationPayloadLookup } from '@forgeax/engine-animation';
2
- import type { Asset } from '@forgeax/engine-types';
3
2
  export interface AnimationPayloadSource {
4
- lookup<T extends Asset = Asset>(guid: string): T | undefined;
3
+ lookup<T = unknown>(guid: string): T | undefined;
5
4
  }
6
5
  /** Bridge the renderer-owned GUID catalogue into the animation plugin. */
7
6
  export declare function createAnimationPayloadLookup(source: AnimationPayloadSource | undefined): AnimationPayloadLookup;
@@ -1 +1 @@
1
- {"version":3,"file":"animation-asset-lookup.d.ts","sourceRoot":"","sources":["../src/animation-asset-lookup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAiB,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAElE,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CAC9D;AAED,0EAA0E;AAC1E,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,sBAAsB,GAAG,SAAS,GACzC,sBAAsB,CAExB"}
1
+ {"version":3,"file":"animation-asset-lookup.d.ts","sourceRoot":"","sources":["../src/animation-asset-lookup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAGxE,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CAClD;AAED,0EAA0E;AAC1E,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,sBAAsB,GAAG,SAAS,GACzC,sBAAsB,CAExB"}
@@ -1,6 +1,6 @@
1
+ import { World } from '@forgeax/engine-ecs';
1
2
  import { type Result } from '@forgeax/engine-types';
2
- export { syncCameraAspect } from './canvas-policy';
3
- import type { App, AppAssembleArgs, AssembleAppError, BundlerOptions, CanvasAppError, CanvasDrawingBufferSize, CreateAppOptions, ExecutionApp } from './types';
3
+ import type { App, AppAssembleArgs, AssembleAppError, BundlerOptions, CanvasAppError, CreateAppOptions, ExecutionApp } from './types';
4
4
  /**
5
5
  * createApp(canvas, opts?, bundler?) -- canvas thin wrapper SSOT (per
6
6
  * plan-strategy D-5). Resolves with Result.ok(app) on success; failure routes
@@ -27,7 +27,8 @@ export declare function createApp(canvas: HTMLCanvasElement, opts: CreateAppOpti
27
27
  }, bundler?: BundlerOptions): Promise<Result<ExecutionApp, CanvasAppError>>;
28
28
  export declare function createApp(canvas: HTMLCanvasElement, opts?: CreateAppOptions, bundler?: BundlerOptions): Promise<Result<App, CanvasAppError>>;
29
29
  /**
30
- * createApp({ renderer, world, input?, schedule?, ... }) -- assemble-form
30
+ * Create the browser host for the single RenderScene -> FrameReceipt path.
31
+ * `createApp({ renderer, world, input?, schedule?, ... })` is the assemble form
31
32
  * SSOT (per plan-strategy D-5). Host already owns renderer / world; the
32
33
  * returned App holds them by reference equality (per AC-02).
33
34
  *
@@ -45,13 +46,33 @@ export declare function createApp(args: AppAssembleArgs): Promise<Result<App, As
45
46
  */
46
47
  export declare function syncCanvasDrawingBuffer(canvas: Pick<HTMLCanvasElement, 'clientWidth' | 'clientHeight' | 'width' | 'height'> & {
47
48
  readonly style?: Pick<CSSStyleDeclaration, 'width' | 'height'>;
48
- }, maxCanvasPixelRatio?: number): CanvasDrawingBufferSize;
49
- /**
50
- * Measure the next effective drawing buffer without writing the DOM canvas.
51
- * This is the Worker host path because transferControlToOffscreen makes the
52
- * HTML canvas dimensions immutable; the Worker owns the transferred surface.
53
- */
49
+ }): void;
50
+ /** Measure a CSS-sized canvas without mutating its drawing buffer. */
54
51
  export declare function measureCanvasDrawingBuffer(canvas: Pick<HTMLCanvasElement, 'clientWidth' | 'clientHeight' | 'width' | 'height'> & {
55
52
  readonly style?: Pick<CSSStyleDeclaration, 'width' | 'height'>;
56
- }, maxCanvasPixelRatio?: number): CanvasDrawingBufferSize;
53
+ }, maxCanvasPixelRatio?: number): {
54
+ readonly width: number;
55
+ readonly height: number;
56
+ };
57
+ /**
58
+ * Per-frame aspect-sync body for the createApp(canvas) path (feat-20260617
59
+ * M3 / w13). Walks every Camera entity and, for perspective cameras with
60
+ * `autoAspect === true`, writes `canvasW / canvasH` into `Camera.aspect`.
61
+ *
62
+ * Read discipline (D-5 / research Finding 2): `autoAspect` is read through
63
+ * `world.get` (the readRow path narrows the bool column to a JS boolean).
64
+ * The query bundle is used only to enumerate the entity handles -- reading
65
+ * the bool column off the bundle would return a raw 0/1 number, so a
66
+ * `!== 0` test is always true (the
67
+ * bool-field-compared-with-not-equal-zero-always-true trap).
68
+ *
69
+ * Best-effort + side-effect-isolated:
70
+ * - canvas size 0 (detached / display:none) -> skip entirely so `aspect`
71
+ * never becomes NaN / 0.
72
+ * - orthographic cameras and `autoAspect === false` cameras are left
73
+ * untouched.
74
+ *
75
+ * @see {@link https://github.com/Ubpa/forgeax-engine/blob/main/docs/how-to/2026-06-18-host-engine-contract.md | Host-engine contract SSOT}
76
+ */
77
+ export declare function syncCameraAspect(world: World, canvasW: number, canvasH: number): void;
57
78
  //# sourceMappingURL=create-app.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-app.d.ts","sourceRoot":"","sources":["../src/create-app.ts"],"names":[],"mappings":"AA+CA,OAAO,EAA6C,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAI/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAqCnD,OAAO,KAAK,EACV,GAAG,EACH,eAAe,EAEf,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,uBAAuB,EACvB,gBAAgB,EAChB,YAAY,EACb,MAAM,SAAS,CAAC;AAiFjB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,gBAAgB,GAAG;IAAE,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAA;CAAE,EAC3F,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC,CAAC;AACjD,wBAAgB,SAAS,CACvB,MAAM,EAAE,iBAAiB,EACzB,IAAI,CAAC,EAAE,gBAAgB,EACvB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;AAExC;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAuhBzF;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC,GAAG;IACrF,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,QAAQ,CAAC,CAAC;CAChE,EACD,mBAAmB,CAAC,EAAE,MAAM,GAC3B,uBAAuB,CAMzB;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC,GAAG;IACrF,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,QAAQ,CAAC,CAAC;CAChE,EACD,mBAAmB,CAAC,EAAE,MAAM,GAC3B,uBAAuB,CAOzB"}
1
+ {"version":3,"file":"create-app.d.ts","sourceRoot":"","sources":["../src/create-app.ts"],"names":[],"mappings":"AA4BA,OAAO,EAA8B,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAkBxE,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAkC7D,OAAO,KAAK,EACV,GAAG,EACH,eAAe,EAEf,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,YAAY,EACb,MAAM,SAAS,CAAC;AAyCjB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,gBAAgB,GAAG;IAAE,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAA;CAAE,EAC3F,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC,CAAC;AACjD,wBAAgB,SAAS,CACvB,MAAM,EAAE,iBAAiB,EACzB,IAAI,CAAC,EAAE,gBAAgB,EACvB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;AAExC;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAwezF;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC,GAAG;IACrF,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,QAAQ,CAAC,CAAC;CAChE,GACA,IAAI,CA2CN;AAED,sEAAsE;AACtE,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC,GAAG;IACrF,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,QAAQ,CAAC,CAAC;CAChE,EACD,mBAAmB,CAAC,EAAE,MAAM,GAC3B;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAsCrD;AAYD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAiBrF"}
@@ -1,13 +1,12 @@
1
- import { createAssetRegistry, createCatalogSource } from '@forgeax/engine-assets-runtime';
2
1
  import { createAudioIntentBackend, audioBackendPlugin } from '@forgeax/engine-audio';
3
2
  import { World, createWorldContext, Update } from '@forgeax/engine-ecs';
4
- import { createRenderer, defaultAssetDecoderContributions } from '@forgeax/engine-runtime';
5
- import { err, ok } from '@forgeax/engine-types';
6
- import { ownedRendererPlugin, renderComponentsPlugin, Camera, CAMERA_PROJECTION_PERSPECTIVE } from '@forgeax/engine-render';
3
+ import { constructRuntimeRendererHost } from '@forgeax/engine-runtime/internal/renderer-host';
4
+ import { renderComponentsPlugin, Camera, CAMERA_PROJECTION_PERSPECTIVE } from '@forgeax/engine-render';
7
5
  import { animationPayloadsPlugin, animationRuntimePlugin } from '@forgeax/engine-animation';
8
6
  import { inputBackendPlugin, INPUT_BACKEND_KEY, InputSet, InputFrameStartScan } from '@forgeax/engine-input';
9
7
  import { scenePlugin } from '@forgeax/engine-scene';
10
8
  import { statePlugin } from '@forgeax/engine-state';
9
+ import { err, ok } from '@forgeax/engine-types';
11
10
  import { splitSharedSpan, bindSharedSpan, isSharedSpan } from '@forgeax/engine-ecs/shared';
12
11
 
13
12
  // src/execution/engine-worker-runtime.ts
@@ -16,49 +15,6 @@ import { splitSharedSpan, bindSharedSpan, isSharedSpan } from '@forgeax/engine-e
16
15
  function createAnimationPayloadLookup(source) {
17
16
  return (guid) => source?.lookup(guid);
18
17
  }
19
- function assembleAssetRuntime(registry, contributions) {
20
- const leases = [];
21
- const overrides = new Set(contributions.map((contribution) => contribution.kind.kind));
22
- const defaults = defaultAssetDecoderContributions ?? [];
23
- const assembledContributions = [
24
- ...defaults.filter((contribution) => !overrides.has(contribution.kind.kind)),
25
- ...contributions
26
- ];
27
- try {
28
- const installedKinds = /* @__PURE__ */ new Set();
29
- for (const contribution of assembledContributions) {
30
- if (installedKinds.has(contribution.kind.kind)) {
31
- throw new TypeError(`duplicate decoder kind "${contribution.kind.kind}"`);
32
- }
33
- installedKinds.add(contribution.kind.kind);
34
- leases.push(
35
- registry.installDecoder(
36
- contribution.kind,
37
- contribution.decoder
38
- )
39
- );
40
- }
41
- } catch (cause) {
42
- for (const lease of leases) lease.dispose();
43
- const failed = assembledContributions[leases.length];
44
- return err({
45
- code: "asset-assembly-failed",
46
- expected: "each owner decoder contribution to install exactly once in the realm registry",
47
- hint: "remove the conflicting owner contribution and assemble the current realm again",
48
- detail: { kind: failed?.kind.kind ?? "unknown", cause }
49
- });
50
- }
51
- let disposed = false;
52
- return ok({
53
- registry,
54
- leases: Object.freeze([...leases]),
55
- dispose() {
56
- if (disposed) return;
57
- disposed = true;
58
- for (const lease of leases) lease.dispose();
59
- }
60
- });
61
- }
62
18
  function syncCameraAspect(world, canvasW, canvasH) {
63
19
  if (canvasW <= 0 || canvasH <= 0) return;
64
20
  const aspect = canvasW / canvasH;
@@ -93,61 +49,70 @@ function inputPlugin() {
93
49
  };
94
50
  }
95
51
 
96
- // src/internal/assets-world-plugin.ts
97
- var RENDER_ASSET_PORT_RESOURCE_KEY = "RenderAssetPort";
98
- var ASSET_REGISTRY_RESOURCE_KEY = "AssetRegistry";
99
- function rendererAssetsPlugin() {
52
+ // src/renderer-plugin.ts
53
+ function createRenderFeatureHost(host) {
100
54
  return {
101
- name: "renderer-assets",
102
- inject: ["renderer"],
103
- provide: "assets",
55
+ async installFeature(feature) {
56
+ const installed = await host.installRenderFeature(feature);
57
+ if (!installed.ok) return installed;
58
+ let released = false;
59
+ return {
60
+ ok: true,
61
+ value: {
62
+ release: async () => {
63
+ if (released) return { ok: true, value: void 0 };
64
+ released = true;
65
+ return host.uninstallRenderFeature(feature);
66
+ }
67
+ }
68
+ };
69
+ }
70
+ };
71
+ }
72
+ function ownedRendererPlugin(renderer2) {
73
+ return {
74
+ name: "renderer",
75
+ provide: "renderer",
104
76
  apply(ctx) {
105
- if (ctx.renderer === void 0) throw new Error("assets require the renderer service");
106
- ctx.provide("assets", ctx.renderer.assets);
77
+ ctx.provide("renderer", renderer2);
78
+ ctx.effect(() => () => renderer2.dispose(), "render/renderer");
107
79
  }
108
80
  };
109
81
  }
110
- function assetRegistryPlugin(assembly) {
82
+ function renderFeatureHostPlugin(host) {
111
83
  return {
112
- name: "asset-registry",
113
- provide: "assetRegistry",
84
+ name: "render-feature-host",
85
+ provide: "renderFeatureHost",
114
86
  apply(ctx) {
115
- ctx.provide("assetRegistry", assembly.registry);
116
- ctx.effect(
117
- () => () => {
118
- assembly.dispose();
119
- assembly.registry.dispose();
120
- },
121
- "assets/registry"
122
- );
87
+ ctx.provide("renderFeatureHost", host);
123
88
  }
124
89
  };
125
90
  }
126
- function assetsWorldPlugin() {
91
+
92
+ // src/internal/assets-world-plugin.ts
93
+ var ASSET_REGISTRY_RESOURCE_KEY = "AssetRegistry";
94
+ function rendererAssetsPlugin(assets2) {
127
95
  return {
128
- name: "assets-world",
129
- inject: ["world", "assets"],
96
+ name: "renderer-assets",
97
+ inject: ["renderer"],
98
+ provide: "assets",
130
99
  apply(ctx) {
131
- ctx.effect(() => {
132
- ctx.world.insertResource(RENDER_ASSET_PORT_RESOURCE_KEY, ctx.assets);
133
- return () => {
134
- ctx.world.removeResource(RENDER_ASSET_PORT_RESOURCE_KEY);
135
- };
136
- }, "assets/world-render-projection");
100
+ if (assets2 !== void 0) ctx.provide("assets", assets2);
137
101
  }
138
102
  };
139
103
  }
140
- function assetRegistryWorldPlugin() {
104
+ function assetsWorldPlugin() {
141
105
  return {
142
- name: "asset-registry-world",
143
- inject: ["world", "assetRegistry"],
106
+ name: "assets-world",
107
+ inject: ["world", "assets"],
144
108
  apply(ctx) {
109
+ if (ctx.assets === void 0) return;
145
110
  ctx.effect(() => {
146
- ctx.world.insertResource(ASSET_REGISTRY_RESOURCE_KEY, ctx.assetRegistry);
111
+ ctx.world.insertResource(ASSET_REGISTRY_RESOURCE_KEY, ctx.assets);
147
112
  return () => {
148
113
  ctx.world.removeResource(ASSET_REGISTRY_RESOURCE_KEY);
149
114
  };
150
- }, "assets/world-registry");
115
+ }, "assets/world-resource");
151
116
  }
152
117
  };
153
118
  }
@@ -157,8 +122,8 @@ function workerEngineProfile(options) {
157
122
  return [
158
123
  ownedRendererPlugin(options.renderer),
159
124
  renderComponentsPlugin(),
160
- rendererAssetsPlugin(),
161
- ...options.assetAssembly === void 0 ? [] : [assetRegistryPlugin(options.assetAssembly), assetRegistryWorldPlugin()],
125
+ ...options.rendererFeatureHost === void 0 ? [] : [renderFeatureHostPlugin(options.rendererFeatureHost)],
126
+ rendererAssetsPlugin(options.assets),
162
127
  assetsWorldPlugin(),
163
128
  inputBackendPlugin(options.input),
164
129
  audioBackendPlugin(options.audio),
@@ -180,19 +145,18 @@ var SerializedRebuildQueue = class {
180
145
  return current;
181
146
  }
182
147
  };
183
- async function commitAttachedWorld(renderer2, previousWorld, nextWorld, initializeCandidate) {
184
- const attached = renderer2.attachWorld(nextWorld);
148
+ async function commitAttachedWorld(renderer2, nextWorld, initializeCandidate) {
149
+ const attached = renderer2.attach(nextWorld);
185
150
  if (!attached.ok) throw attached.error;
186
151
  try {
187
152
  if (!await initializeCandidate()) {
188
- renderer2.detachWorld(nextWorld);
153
+ attached.value.dispose();
189
154
  return false;
190
155
  }
191
156
  } catch (cause) {
192
- renderer2.detachWorld(nextWorld);
157
+ attached.value.dispose();
193
158
  throw cause;
194
159
  }
195
- if (previousWorld !== void 0) renderer2.detachWorld(previousWorld);
196
160
  return true;
197
161
  }
198
162
 
@@ -257,10 +221,6 @@ var appErrorPolicy = {
257
221
  expected: "canvas.isConnected === true at createApp(canvas) entry",
258
222
  hint: "append the canvas to the document tree before calling createApp(canvas), or use the assemble entry createApp({ renderer, world }) when the host already manages canvas lifetime"
259
223
  },
260
- "app-invalid-canvas-pixel-ratio": {
261
- expected: "maxCanvasPixelRatio is finite and greater than 0",
262
- hint: "omit maxCanvasPixelRatio to keep the device pixel ratio, or pass a finite value > 0 from the Host policy"
263
- },
264
224
  "app-frame-step-invalid": {
265
225
  expected: "stepFrame(deltaSeconds) runs only while the App is paused and deltaSeconds is finite and non-negative",
266
226
  hint: "pause the App before deterministic stepping and pass an explicit finite delta; resume after the bounded step sequence completes"
@@ -561,6 +521,7 @@ function createKernelPool(options = {}) {
561
521
  // src/execution/engine-worker-runtime.ts
562
522
  var scope = globalThis;
563
523
  var renderer;
524
+ var assets;
564
525
  var currentSample = {
565
526
  downKeys: /* @__PURE__ */ new Set(),
566
527
  upKeys: /* @__PURE__ */ new Set(),
@@ -613,13 +574,6 @@ function serializableDetail(cause) {
613
574
  if (cause instanceof Error) return serializableCause(cause);
614
575
  if (Array.isArray(cause)) return cause.map(serializableDetail);
615
576
  if (typeof cause === "object" && cause !== null) {
616
- const named = cause;
617
- if (typeof named.name === "string" || typeof named.message === "string") {
618
- return {
619
- name: typeof named.name === "string" ? named.name : "Error",
620
- message: typeof named.message === "string" ? named.message : String(cause)
621
- };
622
- }
623
577
  return Object.fromEntries(
624
578
  Object.entries(cause).map(([key, value]) => [key, serializableDetail(value)])
625
579
  );
@@ -669,7 +623,6 @@ async function createRealm(init) {
669
623
  emit: (intent) => candidate.pendingAudioIntents.push(intent)
670
624
  });
671
625
  let candidateRenderer;
672
- let assetAssembly;
673
626
  let rendererLifecycleTransferred = false;
674
627
  const previousRenderer = renderer;
675
628
  let previousSurfaceReleased = false;
@@ -679,33 +632,24 @@ async function createRealm(init) {
679
632
  if (!released.ok) throw released.error;
680
633
  previousSurfaceReleased = true;
681
634
  }
682
- candidateRenderer = await createRenderer(
635
+ const constructed = await constructRuntimeRendererHost(
683
636
  init.canvas,
684
637
  prepared.features === void 0 ? {} : { features: prepared.features },
685
638
  init.shaderManifestUrl === void 0 ? void 0 : { shaderManifestUrl: init.shaderManifestUrl }
686
639
  );
687
- const ready = await candidateRenderer.ready;
688
- if (!ready.ok) throw ready.error;
640
+ if (!constructed.ok) throw constructed.error;
641
+ candidateRenderer = constructed.value.renderer;
642
+ assets = constructed.value.assets;
689
643
  rendererLifecycleTransferred = true;
690
- const assetRegistry = createAssetRegistry({
691
- catalog: createCatalogSource({
692
- url: new URL("pack-index.json", globalThis.location?.href ?? "http://localhost/").href
693
- })
694
- });
695
- const assembledAssets = assembleAssetRuntime(assetRegistry, []);
696
- if (!assembledAssets.ok) {
697
- assetRegistry.dispose();
698
- throw assembledAssets.error;
699
- }
700
- assetAssembly = assembledAssets.value;
701
644
  const pluginContext = await createWorldContext(
702
645
  nextWorld,
703
646
  workerEngineProfile({
704
647
  renderer: candidateRenderer,
705
- assetAssembly,
648
+ rendererFeatureHost: createRenderFeatureHost(constructed.value.featureHost),
649
+ assets,
706
650
  input: inputBackend,
707
651
  audio: audioBackend,
708
- animationPayloads: createAnimationPayloadLookup(candidateRenderer.assets),
652
+ animationPayloads: createAnimationPayloadLookup(assets),
709
653
  extensions: [
710
654
  ...init.tier === "shared" ? [sharedKernelPlugin(candidate)] : [],
711
655
  executionBootstrapHostPlugin({
@@ -725,15 +669,10 @@ async function createRealm(init) {
725
669
  candidate.pluginContext = pluginContext;
726
670
  const activeRenderer = candidateRenderer;
727
671
  const previousRealm = realm;
728
- const committed = await commitAttachedWorld(
729
- candidateRenderer,
730
- previousRealm?.world,
731
- nextWorld,
732
- async () => {
733
- await candidate.kernelPool?.ready();
734
- return true;
735
- }
736
- );
672
+ const committed = await commitAttachedWorld(candidateRenderer, nextWorld, async () => {
673
+ await candidate.kernelPool?.ready();
674
+ return true;
675
+ });
737
676
  if (!committed) {
738
677
  await disposeRealm(candidate);
739
678
  if (previousSurfaceReleased) previousRenderer?.restoreSurface();
@@ -746,8 +685,6 @@ async function createRealm(init) {
746
685
  return true;
747
686
  } catch (cause) {
748
687
  await disposeRealm(candidate);
749
- assetAssembly?.dispose();
750
- assetAssembly?.registry.dispose();
751
688
  if (!rendererLifecycleTransferred) candidateRenderer?.dispose();
752
689
  if (previousSurfaceReleased) previousRenderer?.restoreSurface();
753
690
  throw cause;
@@ -805,8 +742,14 @@ function runFrame(message) {
805
742
  return;
806
743
  }
807
744
  const updateFinished = performance.now();
808
- const draw = renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
809
- if (draw !== void 0 && !draw.ok) throw draw.error;
745
+ const attached = renderer.attach(world);
746
+ if (!attached.ok) throw attached.error;
747
+ const draw = renderer.draw({
748
+ leases: [attached.value],
749
+ camera: { lease: attached.value },
750
+ environment: { lease: attached.value }
751
+ });
752
+ if (!draw.ok) throw draw.error;
810
753
  lastFrameId = message.frameId;
811
754
  const kernelDispatch = activeRealm.kernelPool?.takeLastDispatch() ?? null;
812
755
  const audioIntents = activeRealm.pendingAudioIntents;