@vgai/engine 0.5.22 → 0.5.24

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 (303) hide show
  1. package/README.md +6 -3
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +2 -4
  4. package/dist/adapter/adapter-surface.d.ts +1 -1
  5. package/dist/adapter/adapter-surface.js +1 -1
  6. package/dist/adapter/authoring.d.ts +19 -21
  7. package/dist/adapter/authoring.d.ts.map +1 -1
  8. package/dist/adapter/authoring.js +2 -2
  9. package/dist/adapter/binding.d.ts +341 -0
  10. package/dist/adapter/binding.d.ts.map +1 -0
  11. package/dist/adapter/binding.js +148 -0
  12. package/dist/adapter/colyseus-networking-adapter.d.ts.map +1 -1
  13. package/dist/adapter/colyseus-networking-adapter.js +23 -4
  14. package/dist/adapter/entrypoint-selection-readers.d.ts +24 -0
  15. package/dist/adapter/entrypoint-selection-readers.d.ts.map +1 -0
  16. package/dist/adapter/entrypoint-selection-readers.js +63 -0
  17. package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -1
  18. package/dist/adapter/entrypoint-selection-source.js +1 -44
  19. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
  20. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +1 -52
  21. package/dist/adapter/host-context.d.ts +46 -15
  22. package/dist/adapter/host-context.d.ts.map +1 -1
  23. package/dist/adapter/host-context.js +2 -9
  24. package/dist/adapter/index.d.ts +11 -12
  25. package/dist/adapter/index.d.ts.map +1 -1
  26. package/dist/adapter/index.js +9 -10
  27. package/dist/adapter/ingest/game-contract-seams.d.ts +5 -0
  28. package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -1
  29. package/dist/adapter/ingest/game-contract-seams.js +1 -0
  30. package/dist/adapter/ingest/game-contract.d.ts +22 -3
  31. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  32. package/dist/adapter/ingest/game-contract.js +0 -3
  33. package/dist/adapter/ingest/scene-capture.d.ts +19 -23
  34. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  35. package/dist/adapter/ingest/scene-capture.js +143 -17
  36. package/dist/adapter/ingest/visible-capture-window.d.ts +34 -0
  37. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
  38. package/dist/adapter/native-debug-module.d.ts +40 -5
  39. package/dist/adapter/native-debug-module.d.ts.map +1 -1
  40. package/dist/adapter/native-debug-module.js +36 -9
  41. package/dist/adapter/rapier-physics-adapter.d.ts +31 -1
  42. package/dist/adapter/rapier-physics-adapter.d.ts.map +1 -1
  43. package/dist/adapter/rapier-physics-adapter.js +50 -36
  44. package/dist/adapter/renderer-config.d.ts +2 -2
  45. package/dist/adapter/renderer-config.js +2 -2
  46. package/dist/adapter/root-adapter.d.ts +7 -7
  47. package/dist/adapter/root-adapter.js +4 -4
  48. package/dist/adapter/system-adapter.d.ts +45 -7
  49. package/dist/adapter/system-adapter.d.ts.map +1 -1
  50. package/dist/adapter/system-seam-contract.d.ts +3 -3
  51. package/dist/adapter/system-seam-contract.js +3 -3
  52. package/dist/adapter/system-slot.d.ts +168 -0
  53. package/dist/adapter/system-slot.d.ts.map +1 -0
  54. package/dist/adapter/system-slot.js +229 -0
  55. package/dist/adapter/transform.d.ts +11 -1
  56. package/dist/adapter/transform.d.ts.map +1 -1
  57. package/dist/ai/navigation.d.ts +17 -1
  58. package/dist/ai/navigation.d.ts.map +1 -1
  59. package/dist/ai/navigation.js +26 -5
  60. package/dist/animation/anim-graph-types.d.ts +11 -3
  61. package/dist/animation/anim-graph-types.d.ts.map +1 -1
  62. package/dist/animation/theatre-clock-binding.d.ts +4 -4
  63. package/dist/animation/theatre-clock-binding.js +4 -4
  64. package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
  65. package/dist/animation/xstate-animation-binding.js +2 -2
  66. package/dist/animation/xstate-animation-meta.d.ts +13 -0
  67. package/dist/animation/xstate-animation-meta.d.ts.map +1 -1
  68. package/dist/animation/xstate-animation-meta.js +14 -0
  69. package/dist/asset-formats/index.d.ts +4 -7
  70. package/dist/asset-formats/index.d.ts.map +1 -1
  71. package/dist/asset-formats/index.js +4 -7
  72. package/dist/asset-formats/render-env.d.ts +5 -8
  73. package/dist/asset-formats/render-env.d.ts.map +1 -1
  74. package/dist/asset-formats/render-env.js +3 -6
  75. package/dist/asset-parse-error.d.ts +2 -5
  76. package/dist/asset-parse-error.d.ts.map +1 -1
  77. package/dist/asset-parse-error.js +2 -5
  78. package/dist/audio/bus-mixer.d.ts +101 -0
  79. package/dist/audio/bus-mixer.d.ts.map +1 -0
  80. package/dist/audio/bus-mixer.js +115 -0
  81. package/dist/core/frame-pacing.d.ts +1 -1
  82. package/dist/core/frame-pacing.js +2 -2
  83. package/dist/core/game-loop.d.ts +1 -3
  84. package/dist/core/game-loop.d.ts.map +1 -1
  85. package/dist/core/game-loop.js +1 -3
  86. package/dist/core/seeded-random.d.ts +1 -1
  87. package/dist/core/sim-clock.d.ts +5 -6
  88. package/dist/core/sim-clock.d.ts.map +1 -1
  89. package/dist/core/sim-clock.js +4 -5
  90. package/dist/core/types.d.ts +1 -3
  91. package/dist/core/types.d.ts.map +1 -1
  92. package/dist/defaults.d.ts +6 -14
  93. package/dist/defaults.d.ts.map +1 -1
  94. package/dist/defaults.js +6 -14
  95. package/dist/dev/register-render-vitals.d.ts +1 -4
  96. package/dist/dev/register-render-vitals.d.ts.map +1 -1
  97. package/dist/dev/register-render-vitals.js +1 -4
  98. package/dist/dev/render-debug-adapter.d.ts +2 -3
  99. package/dist/dev/render-debug-adapter.d.ts.map +1 -1
  100. package/dist/dev/render-debug-adapter.js +2 -3
  101. package/dist/dev/render-vitals.d.ts +3 -3
  102. package/dist/dev/render-vitals.js +3 -3
  103. package/dist/dev/webgl-frame-capture.d.ts +1 -1
  104. package/dist/dev/webgl-frame-capture.js +1 -1
  105. package/dist/index.d.ts +2 -5
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +2 -4
  108. package/dist/input/host-pointer.d.ts +122 -0
  109. package/dist/input/host-pointer.d.ts.map +1 -0
  110. package/dist/input/host-pointer.js +160 -0
  111. package/dist/input/input-manager.d.ts +18 -5
  112. package/dist/input/input-manager.d.ts.map +1 -1
  113. package/dist/input/input-manager.js +33 -13
  114. package/dist/input/rebind-controller.d.ts.map +1 -1
  115. package/dist/input/rebind-controller.js +6 -8
  116. package/dist/input/schema.d.ts.map +1 -1
  117. package/dist/input/schema.js +56 -28
  118. package/dist/manifest/filename.d.ts.map +1 -1
  119. package/dist/manifest/filename.js +8 -12
  120. package/dist/manifest/load.d.ts +10 -10
  121. package/dist/manifest/load.d.ts.map +1 -1
  122. package/dist/manifest/load.js +9 -6
  123. package/dist/manifest/schema.d.ts +17 -21
  124. package/dist/manifest/schema.d.ts.map +1 -1
  125. package/dist/manifest/schema.js +45 -35
  126. package/dist/physics/collider-dimensions.d.ts +8 -12
  127. package/dist/physics/collider-dimensions.d.ts.map +1 -1
  128. package/dist/physics/collider-dimensions.js +8 -12
  129. package/dist/pixi/scene-capture.d.ts +11 -13
  130. package/dist/pixi/scene-capture.d.ts.map +1 -1
  131. package/dist/react/world-state.d.ts +1 -1
  132. package/dist/react/world-state.d.ts.map +1 -1
  133. package/dist/render/light-camera-factory.d.ts +5 -7
  134. package/dist/render/light-camera-factory.d.ts.map +1 -1
  135. package/dist/render/light-camera-factory.js +8 -11
  136. package/dist/render/material-factory.js +1 -1
  137. package/dist/render/render-batch-system.d.ts.map +1 -1
  138. package/dist/render/render-features.d.ts +17 -6
  139. package/dist/render/render-features.d.ts.map +1 -1
  140. package/dist/render/render-features.js +0 -28
  141. package/dist/runtime/create-runtime.d.ts +34 -25
  142. package/dist/runtime/create-runtime.d.ts.map +1 -1
  143. package/dist/runtime/create-runtime.js +218 -93
  144. package/dist/runtime/debug-bridge.d.ts +12 -0
  145. package/dist/runtime/debug-bridge.d.ts.map +1 -1
  146. package/dist/runtime/debug-bridge.js +4 -0
  147. package/dist/runtime/debug-registry.d.ts +95 -8
  148. package/dist/runtime/debug-registry.d.ts.map +1 -1
  149. package/dist/runtime/debug-registry.js +24 -3
  150. package/dist/runtime/dev-build.d.ts +2 -2
  151. package/dist/runtime/dev-build.js +2 -2
  152. package/dist/runtime/game-input-seams.d.ts +2 -2
  153. package/dist/runtime/game-input-seams.d.ts.map +1 -1
  154. package/dist/runtime/game.d.ts +102 -41
  155. package/dist/runtime/game.d.ts.map +1 -1
  156. package/dist/runtime/game.js +80 -54
  157. package/dist/runtime/mount-game.d.ts +0 -16
  158. package/dist/runtime/mount-game.d.ts.map +1 -1
  159. package/dist/runtime/mount-game.js +8 -39
  160. package/dist/runtime/mount-manifest.d.ts +22 -14
  161. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  162. package/dist/runtime/mount-manifest.js +18 -18
  163. package/dist/runtime/playtest.d.ts +22 -0
  164. package/dist/runtime/playtest.d.ts.map +1 -0
  165. package/dist/runtime/playtest.js +12 -0
  166. package/dist/runtime/run-ticks-settled.d.ts +34 -0
  167. package/dist/runtime/run-ticks-settled.d.ts.map +1 -0
  168. package/dist/runtime/run-ticks-settled.js +59 -0
  169. package/dist/runtime/state-bridge.d.ts +2 -3
  170. package/dist/runtime/state-bridge.d.ts.map +1 -1
  171. package/dist/runtime/state-bridge.js +1 -0
  172. package/dist/setup/setup-audio.d.ts +12 -0
  173. package/dist/setup/setup-audio.d.ts.map +1 -1
  174. package/dist/setup/setup-audio.js +9 -10
  175. package/dist/setup/setup-renderer.d.ts +2 -25
  176. package/dist/setup/setup-renderer.d.ts.map +1 -1
  177. package/dist/setup/setup-renderer.js +2 -31
  178. package/dist/world3d-react/r3f-root-factory.d.ts +7 -7
  179. package/dist/world3d-react/r3f-root-factory.js +7 -7
  180. package/dist/world3d-react/rapier-physics-bridge.d.ts +9 -8
  181. package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
  182. package/dist/world3d-react/rapier-physics-bridge.js +100 -48
  183. package/package.json +1 -1
  184. package/schemas/engine-capabilities.json +16 -14
  185. package/schemas/vgai-project.schema.json +35 -26
  186. package/src/adapter/adapter-module.ts +2 -4
  187. package/src/adapter/adapter-surface.ts +1 -1
  188. package/src/adapter/authoring.ts +19 -21
  189. package/src/adapter/binding.ts +468 -0
  190. package/src/adapter/colyseus-networking-adapter.ts +20 -5
  191. package/src/adapter/entrypoint-selection-readers.ts +66 -0
  192. package/src/adapter/entrypoint-selection-source.ts +1 -40
  193. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +1 -48
  194. package/src/adapter/host-context.ts +45 -15
  195. package/src/adapter/index.ts +23 -11
  196. package/src/adapter/ingest/game-contract-seams.ts +1 -0
  197. package/src/adapter/ingest/game-contract.ts +22 -3
  198. package/src/adapter/ingest/scene-capture.ts +190 -39
  199. package/src/adapter/ingest/visible-capture-window.ts +35 -0
  200. package/src/adapter/native-debug-module.ts +66 -9
  201. package/src/adapter/rapier-physics-adapter.ts +76 -20
  202. package/src/adapter/renderer-config.ts +2 -2
  203. package/src/adapter/root-adapter.ts +7 -7
  204. package/src/adapter/system-adapter.ts +43 -8
  205. package/src/adapter/system-seam-contract.ts +3 -3
  206. package/src/adapter/system-slot.ts +291 -0
  207. package/src/adapter/transform.ts +18 -1
  208. package/src/ai/navigation.ts +30 -3
  209. package/src/animation/anim-graph-types.ts +11 -3
  210. package/src/animation/theatre-clock-binding.ts +4 -4
  211. package/src/animation/xstate-animation-binding.ts +2 -2
  212. package/src/animation/xstate-animation-meta.ts +16 -0
  213. package/src/asset-formats/index.ts +4 -7
  214. package/src/asset-formats/render-env.ts +3 -6
  215. package/src/asset-parse-error.ts +2 -5
  216. package/src/audio/bus-mixer.ts +161 -0
  217. package/src/core/frame-pacing.ts +2 -2
  218. package/src/core/game-loop.ts +1 -3
  219. package/src/core/seeded-random.ts +1 -1
  220. package/src/core/sim-clock.ts +5 -6
  221. package/src/core/types.ts +1 -3
  222. package/src/defaults.ts +6 -14
  223. package/src/dev/register-render-vitals.ts +1 -4
  224. package/src/dev/render-debug-adapter.ts +2 -3
  225. package/src/dev/render-vitals.ts +3 -3
  226. package/src/dev/webgl-frame-capture.ts +1 -1
  227. package/src/index.ts +2 -5
  228. package/src/input/host-pointer.ts +230 -0
  229. package/src/input/input-manager.ts +34 -13
  230. package/src/input/input-types.ts +1 -1
  231. package/src/input/rebind-controller.ts +6 -8
  232. package/src/input/schema.ts +216 -188
  233. package/src/manifest/filename.ts +8 -12
  234. package/src/manifest/load.ts +17 -13
  235. package/src/manifest/schema.ts +55 -47
  236. package/src/physics/collider-dimensions.ts +8 -12
  237. package/src/pixi/scene-capture.ts +12 -15
  238. package/src/react/world-state.tsx +1 -1
  239. package/src/render/light-camera-factory.ts +8 -11
  240. package/src/render/material-factory.ts +1 -1
  241. package/src/render/render-batch-system.ts +14 -6
  242. package/src/render/render-features.ts +17 -35
  243. package/src/runtime/create-runtime.ts +270 -108
  244. package/src/runtime/debug-bridge.ts +16 -0
  245. package/src/runtime/debug-registry.ts +121 -8
  246. package/src/runtime/dev-build.ts +2 -2
  247. package/src/runtime/game-input-seams.ts +2 -2
  248. package/src/runtime/game.ts +180 -80
  249. package/src/runtime/mount-game.ts +8 -56
  250. package/src/runtime/mount-manifest.ts +43 -34
  251. package/src/runtime/playtest.ts +22 -0
  252. package/src/runtime/run-ticks-settled.ts +72 -0
  253. package/src/runtime/state-bridge.ts +3 -3
  254. package/src/setup/setup-audio.ts +21 -11
  255. package/src/setup/setup-renderer.ts +2 -61
  256. package/src/world3d-react/r3f-root-factory.tsx +7 -7
  257. package/src/world3d-react/rapier-physics-bridge.tsx +107 -50
  258. package/dist/adapter/setup-three-root-adapter.d.ts +0 -86
  259. package/dist/adapter/setup-three-root-adapter.d.ts.map +0 -1
  260. package/dist/adapter/setup-three-root-adapter.js +0 -908
  261. package/dist/animation/clip-map.d.ts +0 -12
  262. package/dist/animation/clip-map.d.ts.map +0 -1
  263. package/dist/animation/clip-map.js +0 -31
  264. package/dist/asset-registry.d.ts +0 -38
  265. package/dist/asset-registry.d.ts.map +0 -1
  266. package/dist/asset-registry.js +0 -66
  267. package/dist/canvas-react/engine-bridge.d.ts +0 -45
  268. package/dist/canvas-react/engine-bridge.d.ts.map +0 -1
  269. package/dist/canvas-react/engine-bridge.js +0 -45
  270. package/dist/canvas-react/pixi-react-adapter.d.ts +0 -77
  271. package/dist/canvas-react/pixi-react-adapter.d.ts.map +0 -1
  272. package/dist/canvas-react/pixi-react-adapter.js +0 -294
  273. package/dist/canvas-react/world-context.d.ts +0 -98
  274. package/dist/canvas-react/world-context.d.ts.map +0 -1
  275. package/dist/canvas-react/world-context.js +0 -173
  276. package/dist/dev/debug-draw.d.ts +0 -24
  277. package/dist/dev/debug-draw.d.ts.map +0 -1
  278. package/dist/dev/debug-draw.js +0 -73
  279. package/dist/render/auto-batcher.d.ts +0 -34
  280. package/dist/render/auto-batcher.d.ts.map +0 -1
  281. package/dist/render/auto-batcher.js +0 -140
  282. package/dist/render/lod.d.ts +0 -13
  283. package/dist/render/lod.d.ts.map +0 -1
  284. package/dist/render/lod.js +0 -16
  285. package/dist/runtime/types.d.ts +0 -303
  286. package/dist/runtime/types.d.ts.map +0 -1
  287. package/dist/runtime/types.js +0 -1
  288. package/dist/world3d-react/engine-bridge.d.ts +0 -47
  289. package/dist/world3d-react/engine-bridge.d.ts.map +0 -1
  290. package/dist/world3d-react/engine-bridge.js +0 -73
  291. package/dist/world3d-react/r3f-adapter.d.ts +0 -58
  292. package/dist/world3d-react/r3f-adapter.d.ts.map +0 -1
  293. package/dist/world3d-react/r3f-adapter.js +0 -634
  294. package/dist/world3d-react/world-context.d.ts +0 -182
  295. package/dist/world3d-react/world-context.d.ts.map +0 -1
  296. package/dist/world3d-react/world-context.js +0 -235
  297. package/src/adapter/setup-three-root-adapter.ts +0 -1032
  298. package/src/animation/clip-map.ts +0 -34
  299. package/src/asset-registry.ts +0 -89
  300. package/src/dev/debug-draw.ts +0 -80
  301. package/src/render/auto-batcher.ts +0 -168
  302. package/src/render/lod.ts +0 -17
  303. package/src/runtime/types.ts +0 -328
@@ -1,17 +1,13 @@
1
1
  // The project manifest's FILENAME. There is exactly ONE.
2
2
  //
3
- // The manifest is `vgai.project.json`. It used to be `vgai.game.json`, and for
4
- // a while both names were read — WO-9 #62 recorded that dual-read as
5
- // permanent, on the presumption of an EXTERNAL constituency of projects
6
- // already on disk. The owner reversed that on 2026-08-02 (the legacy-removal
7
- // doctrine, `docs/ARCHITECTURE-CORE.md` §Vocabulary): this is pre-release
8
- // software, no such constituency exists, and a second way to spell the
9
- // manifest is the defect — two accepted names meant both kept being written,
10
- // and copies of the "try both" probe grew in five modules that agreed with the
11
- // real resolver only by coincidence.
3
+ // The manifest is `vgai.project.json`, and NOTHING reads a second spelling
4
+ // (the legacy-removal doctrine, `docs/ARCHITECTURE-CORE.md` §Vocabulary). Two
5
+ // accepted names is the defect, not the convenience: both get written, and
6
+ // copies of the "try both" probe spread into modules that agree with the real
7
+ // resolver only by coincidence.
12
8
  //
13
- // So the old name is REMOVED, and removed means it REJECTS LOUDLY naming the
14
- // exact one-line fix — never silently ignored, never redirected. A directory
9
+ // The removed name REJECTS LOUDLY, naming the exact one-line fix never
10
+ // silently ignored, never redirected. A directory
15
11
  // carrying only `vgai.game.json` is an error with a `git mv` in the message,
16
12
  // not a project that quietly fails to open.
17
13
  //
@@ -40,7 +36,7 @@ export function isManifestFilename(name: string): boolean {
40
36
  export function removedManifestFilenameMessage(where: string): string {
41
37
  return (
42
38
  `${where}: found \`${REMOVED_MANIFEST_FILENAME}\` and no \`${MANIFEST_FILENAME}\`. ` +
43
- `\`${REMOVED_MANIFEST_FILENAME}\` was REMOVED (WO-LR1; the legacy-removal doctrine, ` +
39
+ `\`${REMOVED_MANIFEST_FILENAME}\` was REMOVED (the legacy-removal doctrine, ` +
44
40
  'docs/ARCHITECTURE-CORE.md §Vocabulary) — nothing reads it any more, and it is ' +
45
41
  'deliberately NOT read as a fallback, because a second accepted name is the defect.\n' +
46
42
  `Fix: rename it — \`git mv ${REMOVED_MANIFEST_FILENAME} ${MANIFEST_FILENAME}\`. ` +
@@ -62,13 +62,6 @@ export type ResolvedAdapter =
62
62
  readonly assets: Record<string, string> | undefined;
63
63
  readonly domStubs: string[] | undefined;
64
64
  readonly captureTimeoutMs: number | undefined;
65
- /**
66
- * The component this root'''s WORLD is, in the game'''s own source (see the
67
- * field'''s schema description). Present ⇒ Edit mounts THAT component
68
- * alone as a still design-time document and Play mounts the game
69
- * through `entry`; absent ⇒ `entry` is what both modes mount.
70
- */
71
- readonly world?: { readonly entry: string; readonly export?: string | undefined } | undefined;
72
65
  };
73
66
 
74
67
  export interface ResolvedAdapterRoot {
@@ -77,6 +70,13 @@ export interface ResolvedAdapterRoot {
77
70
  readonly description: string | undefined;
78
71
  readonly adapter: ResolvedAdapter;
79
72
  readonly entry: string | undefined;
73
+ /**
74
+ * The component this root's WORLD is, in the game's own source (see the
75
+ * field's schema description). Present ⇒ Edit mounts THAT component alone as
76
+ * its design-time document and Play mounts the game through `entry`;
77
+ * absent ⇒ `entry` is what both modes mount.
78
+ */
79
+ readonly world?: { readonly entry: string; readonly export: string | undefined } | undefined;
80
80
  readonly zOrder: number;
81
81
  readonly pausable: boolean;
82
82
  readonly loop: 'gated' | 'self-driven';
@@ -231,7 +231,6 @@ function resolveAdapter(root: AdapterRoot): ResolvedAdapter {
231
231
  assets: ingest.assets,
232
232
  domStubs: ingest.domStubs,
233
233
  captureTimeoutMs: ingest.captureTimeoutMs,
234
- world: ingest.world,
235
234
  };
236
235
  }
237
236
 
@@ -251,11 +250,14 @@ function checkEngineVersionPin(version: string): void {
251
250
  * `id` drives uniqueness; `adapter` (incl. its surface and
252
251
  * `module`/`ingest.*` sub-fields) drives adapter identity
253
252
  * resolution; `entry` drives the entry cross-field rule and is
254
- * carried through; `zOrder` drives the final sort; `pausable`/`loop` are
253
+ * carried through; `world` is carried through for the ONE reader that mounts a
254
+ * design-time document (the editor's design session) and is deliberately not
255
+ * cross-checked against `entry` — the two name different modules on purpose;
256
+ * `zOrder` drives the final sort; `pausable`/`loop` are
255
257
  * carried through as-is (their consumers are the runtime/CLI, T3.2/T3.3);
256
258
  * `description` (T3.3
257
259
  * slice 3) is carried through as-is — its consumer today is
258
- * `resolveIngestDescriptor` (packages/editor/src/adapter-resolver.ts), which
260
+ * `resolveIngestDescriptor` (packages/editor/src/ingest/resolve-three.ts), which
259
261
  * threads an ingest-three root's description into `IngestGame.description`.
260
262
  */
261
263
  function resolveRoot(root: AdapterRoot): ResolvedAdapterRoot {
@@ -268,6 +270,8 @@ function resolveRoot(root: AdapterRoot): ResolvedAdapterRoot {
268
270
  description: root.description,
269
271
  adapter,
270
272
  entry: root.entry,
273
+ world:
274
+ root.world === undefined ? undefined : { entry: root.world.entry, export: root.world.export },
271
275
  zOrder: root.zOrder,
272
276
  pausable: root.pausable,
273
277
  loop: root.loop,
@@ -285,9 +289,9 @@ function resolveRoot(root: AdapterRoot): ResolvedAdapterRoot {
285
289
  * any Zod validation failure or cross-field rule violation.
286
290
  */
287
291
  export function loadGameManifest(raw: unknown): ResolvedGameManifest {
288
- // Removed-format guard FIRST, on the raw value: a legacy `.vscn.json` root
289
- // would otherwise fail Zod's `roots` discriminated union with a confusing
290
- // shape error instead of a migration message (WO-8).
292
+ // Removed-format guard FIRST, on the raw value: a root declaring a removed
293
+ // content field would otherwise fail Zod's `roots` discriminated union with
294
+ // a confusing shape error instead of the named migration message.
291
295
  const parsed = GameManifestSchema.safeParse(raw);
292
296
  if (!parsed.success) {
293
297
  // D-V6: the `roots` `superRefine`'s composition issues — a duplicate root
@@ -7,14 +7,9 @@
7
7
  // has a `.describe()` (repo policy — powers `scripts/generate-schema.ts` and
8
8
  // the T4.1 schema-walk coverage test).
9
9
  //
10
- // Naming: this file follows the repo's `XxxSchema` (Zod schema) / `Xxx`
11
- // (inferred type) convention used throughout `scene/schema/` the design
12
- // doc's §3 sketch names the bare consts (`AdapterRoot`) without the
13
- // `Schema` suffix; that sketch is explicitly a
14
- // "Zod sketch" the doc tells implementers to adapt to real code style
15
- // ("mirror the style in scene/schema/scene-file.ts" per CLAUDE.md), so the
16
- // schema consts here are suffixed and the bare names are reserved for the
17
- // inferred types, matching every other schema file in the repo.
10
+ // Naming: `XxxSchema` is the Zod schema, `Xxx` the inferred type. Every schema
11
+ // file in this repo reads that way, so the bare names stay reserved for the
12
+ // types.
18
13
  //
19
14
  // Adapter surface values mirror `AdapterSurface` in
20
15
  // `packages/engine/src/runtime/game.ts` (`'three' | 'canvas' | 'dom'`)
@@ -56,7 +51,7 @@ export const GAME_MANIFEST_VERSION = 2 as const;
56
51
  // which is exactly why react is a `dom` root rather than its own kind.
57
52
  //
58
53
  // The pre-rename spellings `threejs`/`pixijs`/`react` are REMOVED, not
59
- // normalized (WO-LR1 item 2, the legacy-removal doctrine at
54
+ // normalized (the legacy-removal doctrine at
60
55
  // ARCHITECTURE-CORE §Vocabulary). Normalization was a second way to say the
61
56
  // same thing: two spellings both parsed, so both kept being written, and
62
57
  // SECOND readers grew (the dev server's surface probe, the hosted-editor
@@ -83,8 +78,8 @@ function removedSpellingMessage(bad: string, where: 'adapter' | 'adapter.surface
83
78
  const canonical =
84
79
  where === 'adapter' ? REMOVED_BARE_ADAPTER_SPELLING[bad] : REMOVED_SURFACE_SPELLING[bad];
85
80
  return (
86
- `Game manifest: \`${where}\`: "${bad}" — that spelling was REMOVED (WO-LR1; ` +
87
- 'ARCHITECTURE-CORE §Vocabulary). The surface vocabulary is ' +
81
+ `Game manifest: \`${where}\`: "${bad}" — that spelling was REMOVED ` +
82
+ '(ARCHITECTURE-CORE §Vocabulary). The surface vocabulary is ' +
88
83
  "'three' | 'canvas' | 'dom' — what the HOST HANDS the root, never which " +
89
84
  `library it uses.\nFix: change \`${where}\` to "${canonical}".`
90
85
  );
@@ -219,34 +214,6 @@ export const RootAdapterSchema = z
219
214
  'FAILS by name. A game that needs a long boot (streamed world assets, a menu ' +
220
215
  'the player must clear) raises it; the default is 10s.',
221
216
  ),
222
- world: z
223
- .object({
224
- entry: z
225
- .string()
226
- .describe(
227
- "Project-relative path to the game's OWN module exporting that component",
228
- ),
229
- export: z
230
- .string()
231
- .optional()
232
- .describe(
233
- 'Named export to read from that module; omit when the module default-exports it',
234
- ),
235
- })
236
- .strict()
237
- .optional()
238
- .describe(
239
- "The component this root's WORLD is, in the game's own source — a three root " +
240
- 'whose scene is a React/R3F component tree names it here. Two readers, and ' +
241
- 'they mount different things: the editor mounts THIS component alone as the ' +
242
- "root's design-time document (a still scene — its frameloop runs ONE bounded " +
243
- 'settle at mount, so physics-owned bodies rest at their authored spawn, and ' +
244
- 'is then frozen forever; nothing moves in Edit), while `entry` ' +
245
- 'above stays what PLAY mounts (the whole game, its own composition, its own ' +
246
- 'HUD and loop). Declaring it is what lets a scene be a scene in Edit rather ' +
247
- "than the running game's mount surfaces. A game whose world is not a " +
248
- 'component omits it and keeps mounting through `entry` in both modes.',
249
- ),
250
217
  })
251
218
  .strict()
252
219
  .describe(
@@ -306,6 +273,37 @@ export const AdapterRootSchema = z
306
273
  "the HUD is styled on the UI board's story cards and on the game surface without the " +
307
274
  'editor seeing a single one of those rules. The files themselves are never modified.',
308
275
  ),
276
+ world: z
277
+ .object({
278
+ entry: z
279
+ .string()
280
+ .describe("Project-relative path to the game's OWN module exporting that component"),
281
+ export: z
282
+ .string()
283
+ .optional()
284
+ .describe(
285
+ 'Named export to read from that module; omit when the module default-exports it',
286
+ ),
287
+ })
288
+ .strict()
289
+ .optional()
290
+ .describe(
291
+ "The component this root's WORLD is, in the game's own source. Two readers, and they " +
292
+ "mount different things: the EDITOR mounts THIS component alone as the root's " +
293
+ 'design-time document, while `entry` above stays what PLAY mounts (the whole game — ' +
294
+ 'its own composition, its own HUD, its own loop). It sits on the ROOT rather than ' +
295
+ 'inside any one adapter because the split it names is not about ingest: a game whose ' +
296
+ 'runtime entry is a SHELL over the thing an author actually edits has this shape ' +
297
+ 'whether the source is vendored or first-party. Two shipped cases, one field: an ' +
298
+ 'ingested game whose `entry` mounts game+HUD+loop while the named export is the world ' +
299
+ 'alone; and a translated Unity/Godot port, whose `entry` is a scene HOST that mounts ' +
300
+ 'the scene the built player starts on (`EditorBuildSettings` index 0 — routinely a ' +
301
+ 'menu with no 3D content at all) while the named export opens the scene an author ' +
302
+ "edits. That is Unity's own split: build settings say where the PLAYER starts; the " +
303
+ 'Scene view opens a scene, and Unity keeps that fact somewhere else entirely. A game ' +
304
+ 'whose world is not a separate component omits this and keeps mounting through ' +
305
+ '`entry` in both modes.',
306
+ ),
309
307
  zOrder: z
310
308
  .number()
311
309
  .int()
@@ -424,6 +422,7 @@ export const GameManifestSchema = z
424
422
  'identity pin, not a range',
425
423
  ),
426
424
  })
425
+ .strict()
427
426
  .describe('Engine version pin'),
428
427
  roots: z
429
428
  .array(AdapterRootSchema)
@@ -498,21 +497,25 @@ export const GameManifestSchema = z
498
497
  .describe('Optional editor labels keyed by adapter-root id'),
499
498
  groups: z
500
499
  .array(
501
- z.object({
502
- id: z.string().describe('Unique editor-only group id'),
503
- label: z.string().describe('Editor-only group label'),
504
- roots: z
505
- .array(z.string())
506
- .min(1)
507
- .describe('Adapter-root ids shown under this editor-only group'),
508
- }),
500
+ z
501
+ .object({
502
+ id: z.string().describe('Unique editor-only group id'),
503
+ label: z.string().describe('Editor-only group label'),
504
+ roots: z
505
+ .array(z.string())
506
+ .min(1)
507
+ .describe('Adapter-root ids shown under this editor-only group'),
508
+ })
509
+ .strict(),
509
510
  )
510
511
  .optional()
511
512
  .describe('Optional editor-only hierarchy groups'),
512
513
  })
514
+ .strict()
513
515
  .optional()
514
516
  .describe('Editor projection for native adapter roots'),
515
517
  })
518
+ .strict()
516
519
  .optional()
517
520
  .describe('Editor-only authoring metadata; never runtime ownership'),
518
521
  server: z
@@ -558,6 +561,7 @@ export const GameManifestSchema = z
558
561
  'Deploy-baked managed-relay credentials. NEVER hand-authored in source — written only into the staged manifest by `vgai deploy` and fetched at runtime by the standalone client',
559
562
  ),
560
563
  })
564
+ .strict()
561
565
  .optional()
562
566
  .describe('Multiplayer server config (Colyseus), if this game registers a room'),
563
567
  resolution: z
@@ -565,6 +569,7 @@ export const GameManifestSchema = z
565
569
  width: z.number().describe('Canvas width in pixels'),
566
570
  height: z.number().describe('Canvas height in pixels'),
567
571
  })
572
+ .strict()
568
573
  .optional()
569
574
  .describe('Canvas resolution'),
570
575
  rendering: z
@@ -599,6 +604,7 @@ export const GameManifestSchema = z
599
604
  'runtime reader is the debug-bridge installer (D18).',
600
605
  ),
601
606
  })
607
+ .strict()
602
608
  .optional()
603
609
  .describe(
604
610
  'Debug-bridge production gating (D18) — governs whether ?vgai-debug=1 installs ' +
@@ -631,6 +637,7 @@ export const GameManifestSchema = z
631
637
  "omit to fall back to the runtime's own fixed default seed.",
632
638
  ),
633
639
  })
640
+ .strict()
634
641
  .optional()
635
642
  .describe(
636
643
  'Seeded-RNG determinism contract (D15) — governs ' +
@@ -689,6 +696,7 @@ export const GameManifestSchema = z
689
696
  'deterministic under a seed.',
690
697
  ),
691
698
  })
699
+ .strict()
692
700
  .refine((budget) => Object.values(budget).some((value) => value !== undefined), {
693
701
  message:
694
702
  'Game manifest: `budget` must set at least one metric (frameCpuMsP95, physicsMsP95, ' +
@@ -5,17 +5,13 @@
5
5
  * shape is defined in absolute world units at creation time. Three.js meshes,
6
6
  * by contrast, scale visually for free via the normal Object3D hierarchy. To
7
7
  * keep the physical collider matching what's on screen, the LOADER must bake
8
- * the entity's composed WORLD scale (self × every transformed ancestor — see
9
- * T1.1's world-transform composition) into the collider's dimensions at
10
- * creation time.
8
+ * the entity's composed WORLD scale (self × every transformed ancestor) into
9
+ * the collider's dimensions at creation time.
11
10
  *
12
- * This is the ONE place that does that math, so both the runtime loader
13
- * (scene-loader.ts `createPhysicsBody`) and the editor's collider gizmo
14
- * (gizmo-registry.ts) agree on exactly the same numbers previously the
15
- * editor gizmo only rendered the entity's own live Object3D scale (via normal
16
- * Three.js parenting) while the Rapier body ignored scale entirely, so the
17
- * two silently agreed by coincidence for uniform scale and disagreed for
18
- * everything else.
11
+ * This is the ONE place that does that math, so every caller — the physics
12
+ * body creation path and the editor's collider gizmo alike — agrees on
13
+ * exactly the same numbers. Two copies silently agree by coincidence for
14
+ * uniform scale and disagree for everything else.
19
15
  *
20
16
  * Round colliders (ball/capsule) can only represent a uniform radius — Rapier
21
17
  * has no ellipsoid/elliptical-capsule shape. A non-uniform world scale on a
@@ -40,8 +36,8 @@ export interface ColliderWorldDimensions {
40
36
 
41
37
  /**
42
38
  * Compute a collider's WORLD-space dimensions given the entity's composed
43
- * world scale (self × ancestors, per-axis, in the entity's own local frame
44
- * X/Y/Z order matches `SceneEntity.transform.scale`).
39
+ * world scale (self × ancestors, per-axis, in the entity's own local frame,
40
+ * in X/Y/Z order).
45
41
  *
46
42
  * @param collider - the entity's authored (unscaled) collider definition.
47
43
  * @param worldScale - composed world scale `[x, y, z]`.
@@ -19,25 +19,22 @@
19
19
  */
20
20
 
21
21
  import {
22
+ type CaptureWaitOptions,
22
23
  documentVisibilityClock,
23
24
  startVisibleCaptureWindow,
24
- type VisibilityClock,
25
- type VisibleCaptureWindow,
26
25
  } from '../adapter/ingest/visible-capture-window';
27
26
 
28
27
  /**
29
- * Options for {@link SceneCapture2DHandle.waitForCapture} — the same shape the
30
- * three lane's `CaptureWaitOptions` has, because it is the same wait. Passing a
31
- * bare number is `{ timeoutMs }`, which is what every existing caller does.
28
+ * Options for {@link SceneCapture2DHandle.waitForCapture} — the WAIT's options,
29
+ * not this lane's, so they carry no `2D` suffix and are not declared here.
30
+ * They configure `visible-capture-window.ts`'s window, which both surfaces
31
+ * share; this lane used to restate them field-for-field under a `2D` name.
32
+ * The suffix marks what a surface genuinely owns
33
+ * ({@link CapturedRuntime2D} is a Pixi stage/renderer/app, and stays), and a
34
+ * budget of visible milliseconds is not that. Passing a bare number is
35
+ * `{ timeoutMs }`, which is what every existing caller does.
32
36
  */
33
- export interface CaptureWait2DOptions {
34
- /** The capture window, in VISIBLE milliseconds (default 10s). */
35
- timeoutMs?: number | undefined;
36
- /** Injected in tests; defaults to the document's own visibility. */
37
- visibility?: VisibilityClock | undefined;
38
- /** Live view of the wait while it runs, `null` the moment it ends. */
39
- onWait?: ((wait: VisibleCaptureWindow | null) => void) | undefined;
40
- }
37
+ export type { CaptureWaitOptions };
41
38
 
42
39
  /** A live Pixi runtime captured from an external PixiJS game. */
43
40
  export interface CapturedRuntime2D {
@@ -68,7 +65,7 @@ export interface SceneCapture2DHandle {
68
65
  * frame the tab draws after it comes forward is trapped exactly as it would
69
66
  * have been at boot. Rejects only when the window is spent while VISIBLE.
70
67
  */
71
- waitForCapture(options?: number | CaptureWait2DOptions): Promise<CapturedRuntime2D>;
68
+ waitForCapture(options?: number | CaptureWaitOptions): Promise<CapturedRuntime2D>;
72
69
  getDrawCount(): number;
73
70
  /** Pause/resume the captured game's own ticker (loop-host gating). */
74
71
  setPaused(paused: boolean): void;
@@ -138,7 +135,7 @@ export function installSceneCapture2D(
138
135
  else app.ticker.start();
139
136
  },
140
137
  waitForCapture(options) {
141
- const opts: CaptureWait2DOptions =
138
+ const opts: CaptureWaitOptions =
142
139
  typeof options === 'number' ? { timeoutMs: options } : (options ?? {});
143
140
  const timeoutMs = opts.timeoutMs ?? 10_000;
144
141
  if (captured) return Promise.resolve(captured);
@@ -27,10 +27,10 @@ import {
27
27
  } from 'react';
28
28
  import type { z } from 'zod';
29
29
  import type { RootStateObserver } from '../adapter';
30
+ import type { DebugCommandArgs } from '../runtime/debug-registry';
30
31
  import { getDebugRegistry } from '../runtime/debug-registry';
31
32
  import { createFrameSelectorCache, type Equals, shallow } from '../runtime/frame-selector-cache';
32
33
  import type { Game } from '../runtime/game';
33
- import type { DebugCommandArgs } from '../runtime/types';
34
34
 
35
35
  /** Re-exported for convenience — the opt-in equality for selectors that
36
36
  * return a fresh object/array/tuple every call (default is `Object.is`). */
@@ -1,12 +1,10 @@
1
1
  /**
2
- * Shared light + camera factory (T1.13 DEFAULTS truth).
2
+ * Shared light + camera factory — the ONE place light/camera fallbacks are
3
+ * decided, and it decides them by READING `DEFAULTS.light`/`DEFAULTS.camera`.
3
4
  *
4
- * `createLight`/`createCamera` used to be duplicated verbatim between the
5
- * runtime loader (`scene-loader.ts`) and the editor preview
6
- * (`entity-factory.ts`)both hardcoded the same fallback literals (color
7
- * '#ffffff', fov 60, near 0.1, far 1000, etc.) that happened to agree with
8
- * `DEFAULTS.light`/`DEFAULTS.camera` by luck rather than by reading them. Any
9
- * edit to one copy (or to `DEFAULTS`) could silently diverge from the other.
5
+ * A second copy hardcoding the same literals (color '#ffffff', fov 60, near
6
+ * 0.1, far 1000) would agree by luck rather than by construction, and any
7
+ * edit to either copy or to `DEFAULTS` would silently diverge.
10
8
  *
11
9
  * This module is now the ONE place that builds a `THREE.Light`/`THREE.Camera`
12
10
  * from a `LightDescriptor`/`CameraDescriptor` descriptor, reading its fallbacks from
@@ -22,10 +20,9 @@ import * as THREE from 'three';
22
20
  // `three/addons/*` alias (package.json `exports`: `"./addons/*":
23
21
  // "./examples/jsm/*"` — the identical file as
24
22
  // `three/examples/jsm/lights/RectAreaLightUniformsLib.js`) because a literal
25
- // `/examples/` specifier trips `engine-host-no-game-imports.test.ts`'s
26
- // game/editor-example guard (a same-substring false positive against this
27
- // repo's OWN `packages/editor/template/src/examples/` convention, unrelated
28
- // to three.js's addon folder naming).
23
+ // `/examples/` specifier trips this repo's game/editor-example import guard —
24
+ // a same-substring false positive against our own `src/examples/` convention,
25
+ // unrelated to three.js's addon folder naming.
29
26
  import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js';
30
27
  import type { CameraDescriptor } from '../asset-formats/camera';
31
28
  import type { LightDescriptor } from '../asset-formats/light';
@@ -65,7 +65,7 @@ export function createMaterial(mat?: MaterialDescriptor): THREE.Material {
65
65
  // R1c — every fallback below reads DEFAULTS.material.* instead of
66
66
  // duplicating its value as a bare literal, so DEFAULTS stays the single
67
67
  // source of truth for what an omitted field renders as (see
68
- // defaults-out-of-scope-consistency.test.ts / material-factory.test.ts's
68
+ // `material-factory.test.ts`'s
69
69
  // "omitted-field" locks, which fail if either copy drifts from the other).
70
70
  const dMat = DEFAULTS.material;
71
71
  const sideValue = mat.side ?? dMat.side;
@@ -13,21 +13,29 @@ import { staticBatchSignature } from './structural-signature';
13
13
  * N logical objects → 1 draw call, ~0 CPU/frame. This is the classic "static
14
14
  * batching" technique (cf. Unity/PlayCanvas static batch groups).
15
15
  *
16
- * Strategy 1 (keep identity, don't merge). Moving entities (boids-style, all-frame churn) are a separate
17
- * concern see render/auto-batcher.ts (dynamic).
16
+ * Strategy 1 (keep identity, don't merge). Moving entities (boids-style,
17
+ * all-frame churn) are a separate concern, and the engine does not answer it:
18
+ * a game's production answer is the `static-batch` capability's `<Frozen>`
19
+ * (see the scope note below).
18
20
  *
19
21
  * Eligibility (anything failing stays an ordinary draw — degrade, don't break):
20
22
  * - plain THREE.Mesh (not Instanced/Skinned/Batched)
21
23
  * - single, opaque material (transparency needs per-object sorting)
22
24
  * - group of ≥2 sharing (geometry, material, shadow flags)
23
- * - not explicitly opted out (`entity.render.autoBatch === false`)
25
+ * - (there is no per-entity opt-out setting: this docblock used to name
26
+ * `entity.render.autoBatch === false`, a render-feature field NO CODE HERE
27
+ * OR ANYWHERE EVER READ. The field was deleted with R-2's coverage fix;
28
+ * opting a subtree out is the `static-batch` capability's `<Frozen>`
29
+ * declaration, per the scope note below.)
24
30
  * - STATIC members (no physics/components/animation) → detached; MOVING members →
25
31
  * kept in-graph hidden + dirty-synced each frame. A bucket may split into both.
26
32
  *
27
33
  * Scope / limitations (v1):
28
- * - NOT WIRED: nothing in the engine calls build() the rendering-scale
29
- * example's render lab is the only driver (via the hosted-module map), and
30
- * no adapter batches automatically. That is settled, not pending: a game's
34
+ * - NOT WIRED: nothing in the engine calls build(), and nothing constructs
35
+ * this class. The only remaining reference is the hosted editor's
36
+ * project-script module map (`editor/src/browser-project-scripts.ts`),
37
+ * which loads the module so a project script MAY drive it; the example
38
+ * that used to is gone. That is settled, not pending: a game's
31
39
  * production answer is the `static-batch` capability's `<Frozen>`, a
32
40
  * DECLARED mount-static subtree in the game's own TSX, and `dev/static-
33
41
  * batch-advisor.ts` is what routes an author to it by measurement. Full-
@@ -2,12 +2,23 @@ import { z } from 'zod';
2
2
 
3
3
  /**
4
4
  * The render-feature registry — single source of truth for every togglable render
5
- * setting. The scene schema (`SceneEnvironment.rendering`, `SceneEntity.render`),
6
- * the cascade resolver (`resolveRenderSettings`), the editor UI, and the stats
7
- * overlay are all derived from this list, so "everything togglable" stays
8
- * consistent by construction (same posture as the component/material/instancer
9
- * registries). Adding a feature here gives it a schema field + cascade entry for
10
- * free..
5
+ * setting. The render-env schema (`asset-formats/render-env.ts`) and the cascade
6
+ * resolver (`resolveRenderSettings`) are both derived from this list, so
7
+ * "everything togglable" stays consistent by construction (same posture as the
8
+ * component/material/instancer registries).
9
+ *
10
+ * EVERY ENTRY HERE MUST HAVE A RUNTIME READER of its resolved value, and that is
11
+ * now enforced: `schema-reader-coverage.test.ts` derives its walk from this
12
+ * array, so adding a feature adds a described schema path that fails coverage
13
+ * until `schema-consumption-map.ts` names the file that reads it.
14
+ *
15
+ * Three entries were deleted when that enforcement first ran, having reached it
16
+ * with no reader at all: `autoBatch` (its only mention was a COMMENT in
17
+ * `render-batch-system.ts`, a class whose own header says nothing calls it),
18
+ * `lod`, and `backend` (which offered a `webgpu` option while
19
+ * `setup-renderer.ts` unconditionally constructs a `THREE.WebGLRenderer`). A
20
+ * toggle that changes nothing is worse than a missing one — it tells an author
21
+ * the engine has a dial it does not have.
11
22
  */
12
23
 
13
24
  export type RenderFeatureValue = boolean | number | string;
@@ -37,16 +48,6 @@ export interface RenderFeature {
37
48
  }
38
49
 
39
50
  export const RENDER_FEATURES: readonly RenderFeature[] = [
40
- {
41
- key: 'autoBatch',
42
- label: 'Auto-batching',
43
- description:
44
- 'Automatically batch entities sharing geometry+material into one draw call (InstancedMesh/BatchedMesh) while keeping each Object3D as the handle. The headline perf win; opt out per-entity for custom-shader/skinned meshes.',
45
- kind: 'toggle',
46
- default: true,
47
- apply: 'live',
48
- perEntity: true,
49
- },
50
51
  {
51
52
  key: 'frustumCulling',
52
53
  label: 'Frustum culling',
@@ -56,15 +57,6 @@ export const RENDER_FEATURES: readonly RenderFeature[] = [
56
57
  apply: 'live',
57
58
  perEntity: true,
58
59
  },
59
- {
60
- key: 'lod',
61
- label: 'Level of detail',
62
- description: 'Distance-based geometry swap (LOD) for eligible entities.',
63
- kind: 'toggle',
64
- default: true,
65
- apply: 'live',
66
- perEntity: true,
67
- },
68
60
  {
69
61
  key: 'shadows',
70
62
  label: 'Shadows',
@@ -103,16 +95,6 @@ export const RENDER_FEATURES: readonly RenderFeature[] = [
103
95
  apply: 'reload',
104
96
  perEntity: false,
105
97
  },
106
- {
107
- key: 'backend',
108
- label: 'Renderer backend',
109
- description: 'GPU backend: auto picks WebGPU when available, else WebGL2. Applies on reload.',
110
- kind: 'enum',
111
- default: 'auto',
112
- apply: 'reload',
113
- perEntity: false,
114
- options: ['auto', 'webgl', 'webgpu'],
115
- },
116
98
  ];
117
99
 
118
100
  /** Map for O(1) lookup by key. */