@vgai/engine 0.5.13 → 0.5.14

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 (192) hide show
  1. package/dist/adapter/adapter-module.d.ts +447 -0
  2. package/dist/adapter/adapter-module.d.ts.map +1 -0
  3. package/dist/adapter/adapter-module.js +308 -0
  4. package/dist/adapter/authoring.d.ts +357 -10
  5. package/dist/adapter/authoring.d.ts.map +1 -1
  6. package/dist/adapter/authoring.js +87 -1
  7. package/dist/adapter/constraint.d.ts +9 -3
  8. package/dist/adapter/constraint.d.ts.map +1 -1
  9. package/dist/adapter/finders/finder-result.d.ts +28 -0
  10. package/dist/adapter/finders/finder-result.d.ts.map +1 -0
  11. package/dist/adapter/finders/finder-result.js +10 -0
  12. package/dist/adapter/finders/index.d.ts +38 -0
  13. package/dist/adapter/finders/index.d.ts.map +1 -0
  14. package/dist/adapter/finders/index.js +41 -0
  15. package/dist/adapter/finders/prefabs-from-stories.d.ts +64 -0
  16. package/dist/adapter/finders/prefabs-from-stories.d.ts.map +1 -0
  17. package/dist/adapter/finders/prefabs-from-stories.js +92 -0
  18. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +67 -0
  19. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -0
  20. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +226 -0
  21. package/dist/adapter/first-party-systems.d.ts.map +1 -1
  22. package/dist/adapter/first-party-systems.js +29 -0
  23. package/dist/adapter/index.d.ts +7 -3
  24. package/dist/adapter/index.d.ts.map +1 -1
  25. package/dist/adapter/index.js +5 -2
  26. package/dist/adapter/ingest/contract-system-adapters.d.ts +24 -1
  27. package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
  28. package/dist/adapter/ingest/contract-system-adapters.js +100 -34
  29. package/dist/adapter/ingest/game-contract.d.ts +188 -2
  30. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  31. package/dist/adapter/ingest/game-contract.js +107 -0
  32. package/dist/adapter/ingest/mount-readiness.d.ts +84 -0
  33. package/dist/adapter/ingest/mount-readiness.d.ts.map +1 -0
  34. package/dist/adapter/ingest/mount-readiness.js +81 -0
  35. package/dist/adapter/ingest/scene-capture.d.ts +91 -2
  36. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  37. package/dist/adapter/ingest/scene-capture.js +110 -18
  38. package/dist/adapter/ingest/structural-ids.d.ts +11 -5
  39. package/dist/adapter/ingest/structural-ids.d.ts.map +1 -1
  40. package/dist/adapter/ingest/structural-ids.js +18 -9
  41. package/dist/adapter/ingest/upstream-pin.d.ts +4 -2
  42. package/dist/adapter/ingest/upstream-pin.d.ts.map +1 -1
  43. package/dist/adapter/ingest/upstream-pin.js +4 -2
  44. package/dist/adapter/ingest/visible-capture-window.d.ts +116 -0
  45. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -0
  46. package/dist/adapter/ingest/visible-capture-window.js +141 -0
  47. package/dist/adapter/loop-gate-report.d.ts +3 -4
  48. package/dist/adapter/loop-gate-report.d.ts.map +1 -1
  49. package/dist/adapter/loop-gate-report.js +3 -4
  50. package/dist/adapter/root-adapter.d.ts +22 -2
  51. package/dist/adapter/root-adapter.d.ts.map +1 -1
  52. package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
  53. package/dist/adapter/setup-three-root-adapter.js +15 -0
  54. package/dist/adapter/system-adapter.d.ts +161 -2
  55. package/dist/adapter/system-adapter.d.ts.map +1 -1
  56. package/dist/adapter/system-adapter.js +18 -1
  57. package/dist/animation/camera-ownership.d.ts +4 -2
  58. package/dist/animation/camera-ownership.d.ts.map +1 -1
  59. package/dist/animation/camera-ownership.js +58 -1
  60. package/dist/animation/runtime-inspection.d.ts +70 -0
  61. package/dist/animation/runtime-inspection.d.ts.map +1 -0
  62. package/dist/animation/runtime-inspection.js +25 -0
  63. package/dist/animation/xstate-animation-binding.d.ts +9 -1
  64. package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
  65. package/dist/animation/xstate-animation-binding.js +48 -1
  66. package/dist/asset-formats/material.d.ts +1 -1
  67. package/dist/asset-formats/mesh.d.ts +1 -1
  68. package/dist/asset-formats/particles.d.ts +13 -0
  69. package/dist/asset-formats/particles.d.ts.map +1 -1
  70. package/dist/asset-formats/particles.js +28 -0
  71. package/dist/behavior/xstate-inspection.d.ts +24 -0
  72. package/dist/behavior/xstate-inspection.d.ts.map +1 -0
  73. package/dist/behavior/xstate-inspection.js +25 -0
  74. package/dist/core/game-loop.d.ts +7 -5
  75. package/dist/core/game-loop.d.ts.map +1 -1
  76. package/dist/core/game-loop.js +39 -18
  77. package/dist/core/types.d.ts +11 -12
  78. package/dist/core/types.d.ts.map +1 -1
  79. package/dist/data/data-asset.js +1 -1
  80. package/dist/dev/instruments.d.ts.map +1 -1
  81. package/dist/dev/instruments.js +5 -1
  82. package/dist/dev/render-debug-adapter.d.ts +19 -2
  83. package/dist/dev/render-debug-adapter.d.ts.map +1 -1
  84. package/dist/dev/render-debug-adapter.js +10 -0
  85. package/dist/ecs/user-data.d.ts +10 -2
  86. package/dist/ecs/user-data.d.ts.map +1 -1
  87. package/dist/ecs/user-data.js +6 -2
  88. package/dist/input/binding-identity.d.ts +10 -0
  89. package/dist/input/binding-identity.d.ts.map +1 -0
  90. package/dist/input/binding-identity.js +36 -0
  91. package/dist/input/input-manager.d.ts +18 -185
  92. package/dist/input/input-manager.d.ts.map +1 -1
  93. package/dist/input/input-manager.js +78 -68
  94. package/dist/manifest/load.d.ts +14 -0
  95. package/dist/manifest/load.d.ts.map +1 -1
  96. package/dist/manifest/load.js +6 -1
  97. package/dist/manifest/schema.d.ts +18 -0
  98. package/dist/manifest/schema.d.ts.map +1 -1
  99. package/dist/manifest/schema.js +64 -7
  100. package/dist/pixi/authoring.d.ts +27 -0
  101. package/dist/pixi/authoring.d.ts.map +1 -1
  102. package/dist/pixi/authoring.js +10 -3
  103. package/dist/pixi/index.d.ts +1 -1
  104. package/dist/pixi/index.d.ts.map +1 -1
  105. package/dist/pixi/index.js +1 -1
  106. package/dist/pixi/ingest.d.ts +8 -0
  107. package/dist/pixi/ingest.d.ts.map +1 -1
  108. package/dist/pixi/ingest.js +10 -1
  109. package/dist/pixi/render-pass-bracket.d.ts +56 -0
  110. package/dist/pixi/render-pass-bracket.d.ts.map +1 -0
  111. package/dist/pixi/render-pass-bracket.js +98 -0
  112. package/dist/pixi/scene-capture.d.ts +32 -1
  113. package/dist/pixi/scene-capture.d.ts.map +1 -1
  114. package/dist/pixi/scene-capture.js +21 -9
  115. package/dist/pixi/system-adapters.d.ts +29 -0
  116. package/dist/pixi/system-adapters.d.ts.map +1 -1
  117. package/dist/pixi/system-adapters.js +27 -0
  118. package/dist/render/particles-factory.d.ts +16 -2
  119. package/dist/render/particles-factory.d.ts.map +1 -1
  120. package/dist/render/particles-factory.js +88 -5
  121. package/dist/render/soft-particle-depth.d.ts +149 -0
  122. package/dist/render/soft-particle-depth.d.ts.map +1 -0
  123. package/dist/render/soft-particle-depth.js +305 -0
  124. package/dist/render/voxel-surface.d.ts +117 -0
  125. package/dist/render/voxel-surface.d.ts.map +1 -0
  126. package/dist/render/voxel-surface.js +263 -0
  127. package/dist/runtime/create-runtime.js +7 -0
  128. package/dist/runtime/debug-bridge.d.ts +15 -15
  129. package/dist/runtime/debug-bridge.d.ts.map +1 -1
  130. package/dist/runtime/debug-bridge.js +27 -26
  131. package/dist/runtime/game.d.ts.map +1 -1
  132. package/dist/runtime/game.js +1 -2
  133. package/dist/setup/setup-particles.d.ts.map +1 -1
  134. package/dist/setup/setup-particles.js +9 -0
  135. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  136. package/dist/world3d-react/r3f-adapter.js +42 -3
  137. package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
  138. package/dist/world3d-react/rapier-physics-bridge.js +201 -11
  139. package/dist-config/config.js.map +1 -1
  140. package/package.json +4 -2
  141. package/schemas/engine-capabilities.json +12 -12
  142. package/schemas/vgai-project.schema.json +41 -2
  143. package/src/adapter/adapter-module.ts +607 -0
  144. package/src/adapter/authoring.ts +440 -10
  145. package/src/adapter/constraint.ts +10 -3
  146. package/src/adapter/finders/finder-result.ts +29 -0
  147. package/src/adapter/finders/index.ts +62 -0
  148. package/src/adapter/finders/prefabs-from-stories.ts +142 -0
  149. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +304 -0
  150. package/src/adapter/first-party-systems.ts +30 -0
  151. package/src/adapter/index.ts +32 -1
  152. package/src/adapter/ingest/contract-system-adapters.ts +116 -32
  153. package/src/adapter/ingest/game-contract.ts +265 -2
  154. package/src/adapter/ingest/mount-readiness.ts +140 -0
  155. package/src/adapter/ingest/scene-capture.ts +199 -22
  156. package/src/adapter/ingest/structural-ids.ts +21 -9
  157. package/src/adapter/ingest/upstream-pin.ts +4 -2
  158. package/src/adapter/ingest/visible-capture-window.ts +213 -0
  159. package/src/adapter/loop-gate-report.ts +3 -4
  160. package/src/adapter/root-adapter.ts +22 -2
  161. package/src/adapter/setup-three-root-adapter.ts +16 -0
  162. package/src/adapter/system-adapter.ts +188 -2
  163. package/src/animation/camera-ownership.ts +68 -3
  164. package/src/animation/runtime-inspection.ts +104 -0
  165. package/src/animation/xstate-animation-binding.ts +53 -1
  166. package/src/asset-formats/particles.ts +31 -0
  167. package/src/behavior/xstate-inspection.ts +51 -0
  168. package/src/core/game-loop.ts +39 -17
  169. package/src/core/types.ts +11 -12
  170. package/src/data/data-asset.ts +1 -1
  171. package/src/dev/instruments.ts +5 -1
  172. package/src/dev/render-debug-adapter.ts +22 -2
  173. package/src/ecs/user-data.ts +10 -2
  174. package/src/input/binding-identity.ts +37 -0
  175. package/src/input/input-manager.ts +93 -68
  176. package/src/manifest/load.ts +23 -2
  177. package/src/manifest/schema.ts +76 -7
  178. package/src/pixi/authoring.ts +37 -5
  179. package/src/pixi/index.ts +2 -0
  180. package/src/pixi/ingest.ts +15 -2
  181. package/src/pixi/render-pass-bracket.ts +117 -0
  182. package/src/pixi/scene-capture.ts +63 -13
  183. package/src/pixi/system-adapters.ts +41 -0
  184. package/src/render/particles-factory.ts +89 -6
  185. package/src/render/soft-particle-depth.ts +336 -0
  186. package/src/render/voxel-surface.ts +358 -0
  187. package/src/runtime/create-runtime.ts +7 -0
  188. package/src/runtime/debug-bridge.ts +32 -31
  189. package/src/runtime/game.ts +1 -2
  190. package/src/setup/setup-particles.ts +9 -0
  191. package/src/world3d-react/r3f-adapter.tsx +48 -2
  192. package/src/world3d-react/rapier-physics-bridge.tsx +249 -11
@@ -0,0 +1,308 @@
1
+ /**
2
+ * `vgai.adapter.ts` — THE ADAPTER MODULE CONTRACT.
3
+ *
4
+ * ARCHITECTURE-CORE §The editor protocol, "The adapter is the game's own
5
+ * module": the editor is a universal CLIENT of a fixed protocol and every game
6
+ * supplies a SERVER — its adapter. That adapter is the game's OWN module,
7
+ * `vgai.adapter.ts`, sitting beside `vgai.project.json`, default-exporting
8
+ * {@link defineAdapter}'s result.
9
+ *
10
+ * It is CODE (bindings are expressions — a scene entry may carry a closure the
11
+ * host evaluates against the mounted game) with a CONFIG's discipline: the top
12
+ * level is a STATICALLY EVALUABLE BINDING TABLE, readable without booting the
13
+ * game. Concretely, that is the split this module enforces:
14
+ *
15
+ * - the TABLE SHAPE — regions, the scene table, observation declarations —
16
+ * is plain data, validated by {@link AdapterDefinitionSchema} the moment
17
+ * the module is evaluated. `.strict()` throughout: an unrecognized key is
18
+ * an error naming the key, never a silent pass-through (CLAUDE.md,
19
+ * "Unknown input must REJECT LOUDLY").
20
+ * - individual FIELDS may be closures, evaluated lazily against the mounted
21
+ * game (`ObservationDeclaration.answer`). A closure is never required to
22
+ * read the table.
23
+ *
24
+ * **Zero inference.** A binding is a DECLARATION or a finder SELECTION the
25
+ * adapter makes — never something the host sniffs out. Finder algorithms
26
+ * (scenes from an entrypoint selection, prefabs from story registrations, …)
27
+ * ship host-side under `adapter/finders/`, and this module names them by
28
+ * SELECTION only ({@link FinderSelection}). That is deliberate and load-bearing
29
+ * twice over:
30
+ * 1. the engine never runs a finder nobody selected — enforced mechanically
31
+ * by `packages/engine/test/finder-import-boundary.test.ts`, whose only
32
+ * sanctioned importer of the finder namespace is the editor's adapter
33
+ * loader;
34
+ * 2. this module therefore imports NOTHING from `adapter/finders/`, so a
35
+ * game's `vgai.adapter.ts` pulls no finder implementation (and no source
36
+ * parser) into its own bundle. The dependency runs one way: finders
37
+ * import their parameter types from HERE.
38
+ *
39
+ * The manifest keeps only what must be readable without evaluating any module
40
+ * — identity, roots, boot mode, `server.room`. Everything that binds behavior
41
+ * is the adapter's. This wave adds NO manifest field.
42
+ */
43
+ import { z } from 'zod';
44
+ import { WRITE_ANCHOR_KINDS } from './authoring';
45
+ // ---------------------------------------------------------------------------
46
+ // Regions
47
+ // ---------------------------------------------------------------------------
48
+ /**
49
+ * The projector serving a surface — one per surface, host-owned
50
+ * (ARCHITECTURE-CORE: "Projection — nodes, stable identity, bounds, picking …
51
+ * one projector per surface"). Named, never imported: the adapter states WHICH
52
+ * library serves the region; the host binds the implementation.
53
+ */
54
+ export const PROJECTOR_NAMES = ['three', 'pixi', 'dom'];
55
+ /**
56
+ * The NATIVE basis — what {@link regionsFromManifestRoots} emits and what a
57
+ * region that declares none is read as. +Y up, ground at 0: three.js' own
58
+ * convention, which every first-party root is authored in. Stated as a value so
59
+ * "the default" has one home instead of a copy at each reader.
60
+ */
61
+ export const NATIVE_REGION_BASIS = Object.freeze({
62
+ up: 'y',
63
+ groundHeight: 0,
64
+ });
65
+ const AdapterRegionBasisSchema = z
66
+ .object({
67
+ up: z.enum(['y', 'z']).describe('World axis pointing away from the ground'),
68
+ groundHeight: z.number().describe('Ground offset along `up`, in world units'),
69
+ })
70
+ .strict();
71
+ const AdapterRegionSchema = z
72
+ .object({
73
+ id: z.string().min(1).describe('Manifest root id this region grades'),
74
+ surface: z.enum(['three', 'canvas', 'dom']).describe('Render surface the host hands this root'),
75
+ dev: z
76
+ .boolean()
77
+ .describe("true = this region grades the game's own dev layer, not shipped content (the " +
78
+ "manifest root's `dev: true`)"),
79
+ projector: z.enum(PROJECTOR_NAMES).describe('Projection library serving this surface, by name'),
80
+ dialect: z
81
+ .string()
82
+ .min(1)
83
+ .nullable()
84
+ .describe('Dialect writer serving this region, by name; null states outright that the host writes ' +
85
+ "no source here (the region's truth is the game's own code)"),
86
+ anchors: z
87
+ .array(z.enum(WRITE_ANCHOR_KINDS))
88
+ .describe('Anchor kinds this region’s writer serves'),
89
+ basis: AdapterRegionBasisSchema.optional().describe('World basis (up axis + ground height); omit for the native +Y / 0 default'),
90
+ include: z
91
+ .array(z.string().min(1))
92
+ .min(1)
93
+ .optional()
94
+ .describe('Project-relative globs (`**`, `*`, `?`) naming source files this region owns, for files ' +
95
+ 'no root entry’s import closure reaches; a declared include wins over reach'),
96
+ })
97
+ .strict();
98
+ const RegionBindingSchema = z.union([
99
+ z.literal('manifest-roots'),
100
+ z.array(AdapterRegionSchema).min(1),
101
+ ]);
102
+ /**
103
+ * The MECHANICAL derivation behind the `'manifest-roots'` rule: one region per
104
+ * declared root, its surface and adapter identity read straight off the
105
+ * RESOLVED manifest (`@engine/manifest/load` already decided both — this never
106
+ * re-derives them).
107
+ *
108
+ * The per-surface library table below is an ENGINE fact, stated once:
109
+ *
110
+ * | root `adapter` | projector | dialect | anchors |
111
+ * |-----------------------|-----------|---------------|------------------------------------|
112
+ * | `three` | `three` | `r3f` | source-prop, construction-literal |
113
+ * | `canvas` | `pixi` | `pixi-react` | source-prop, construction-literal |
114
+ * | `dom` | `dom` | `jsx` | source-prop |
115
+ * | `{module,surface}` | (surface) | `null` | live-only |
116
+ * | `{ingest,surface}` | (surface) | `null` | live-only |
117
+ *
118
+ * A module/ingest root's truth is the game's own source, which no first-party
119
+ * dialect writer authors — so it declares `live-only` rather than implying a
120
+ * write path it does not have. That is a red cell and therefore a work order,
121
+ * which is the point of stating it.
122
+ *
123
+ * Every derived region carries {@link NATIVE_REGION_BASIS} outright, so the
124
+ * editor's ground plane READS a declaration on the native path too — the
125
+ * default is published, never assumed at the read site.
126
+ */
127
+ export function regionsFromManifestRoots(roots) {
128
+ return roots.map((root) => {
129
+ const surface = root.surface;
130
+ const dev = root.dev;
131
+ const firstParty = root.adapter.type === 'builtin';
132
+ const projector = surface === 'canvas' ? 'pixi' : surface;
133
+ const basis = NATIVE_REGION_BASIS;
134
+ if (!firstParty) {
135
+ return {
136
+ id: root.id,
137
+ surface,
138
+ dev,
139
+ projector,
140
+ dialect: null,
141
+ anchors: ['live-only'],
142
+ basis,
143
+ };
144
+ }
145
+ if (surface === 'dom') {
146
+ return {
147
+ id: root.id,
148
+ surface,
149
+ dev,
150
+ projector,
151
+ dialect: 'jsx',
152
+ anchors: ['source-prop'],
153
+ basis,
154
+ };
155
+ }
156
+ return {
157
+ id: root.id,
158
+ surface,
159
+ dev,
160
+ projector,
161
+ dialect: surface === 'three' ? 'r3f' : 'pixi-react',
162
+ anchors: ['source-prop', 'construction-literal'],
163
+ basis,
164
+ };
165
+ });
166
+ }
167
+ // ---------------------------------------------------------------------------
168
+ // Finder selections
169
+ // ---------------------------------------------------------------------------
170
+ /**
171
+ * The rule form of a finder's region targeting: every region the host mounts
172
+ * from an exported composition (a root with an `entry`) that is not a declared
173
+ * DEV layer. A dev root is the game's own dev GUI — the manifest already says
174
+ * so (`dev: true`), so skipping it reads a declaration rather than guessing.
175
+ */
176
+ export const EXPORTED_COMPOSITION_REGIONS = 'exported-composition-regions';
177
+ /** The finder names, as a value — what the loader's dispatcher switches on. */
178
+ export const FINDER_NAMES = ['scenesFromEntrypointSelection', 'prefabsFromStories'];
179
+ const ScenesFromEntrypointSelectionSchema = z
180
+ .object({
181
+ finder: z.literal('scenesFromEntrypointSelection'),
182
+ regions: z
183
+ .union([z.array(z.string().min(1)).min(1), z.literal(EXPORTED_COMPOSITION_REGIONS)])
184
+ .describe('Region ids whose entrypoints carry the selection, or the rule form'),
185
+ selection: z
186
+ .string()
187
+ .min(1)
188
+ .optional()
189
+ .describe("Identifier of the entrypoint's module-level scene selection table; omit for a " +
190
+ 'single-composition entrypoint'),
191
+ })
192
+ .strict()
193
+ .refine((value) => value.selection === undefined || Array.isArray(value.regions), {
194
+ message: 'scenesFromEntrypointSelection: `selection` names an identifier in ONE entrypoint’s ' +
195
+ `source, so it cannot be paired with the \`${EXPORTED_COMPOSITION_REGIONS}\` rule — ` +
196
+ 'name the region explicitly (`regions: ["<rootId>"]`).',
197
+ });
198
+ const PrefabsFromStoriesSchema = z.object({ finder: z.literal('prefabsFromStories') }).strict();
199
+ const FinderSelectionSchema = z.union([
200
+ ScenesFromEntrypointSelectionSchema,
201
+ PrefabsFromStoriesSchema,
202
+ ]);
203
+ const SceneReachSchema = z.union([
204
+ z.object({ kind: z.literal('root-mount') }).strict(),
205
+ z
206
+ .object({
207
+ kind: z.literal('entrypoint-selection'),
208
+ selection: z.string().min(1),
209
+ key: z.string().min(1),
210
+ })
211
+ .strict(),
212
+ z.object({ kind: z.literal('story'), storyId: z.string().min(1) }).strict(),
213
+ z.object({ kind: z.literal('none'), reason: z.string().min(1) }).strict(),
214
+ ]);
215
+ const SceneSourceSchema = z
216
+ .object({ path: z.string().min(1), export: z.string().min(1).optional() })
217
+ .strict();
218
+ const SceneEntrySchema = z
219
+ .object({
220
+ id: z.string().min(1),
221
+ label: z.string().min(1),
222
+ kind: z.enum(['scene', 'prefab']),
223
+ region: z.string().min(1).nullable(),
224
+ authorable: z.boolean(),
225
+ reach: SceneReachSchema,
226
+ source: SceneSourceSchema.optional(),
227
+ finder: z.enum(FINDER_NAMES).optional(),
228
+ })
229
+ .strict();
230
+ const AdapterSceneTableSchema = z
231
+ .object({
232
+ entries: z.array(SceneEntrySchema).optional(),
233
+ find: z.array(FinderSelectionSchema).optional(),
234
+ default: z.string().min(1).optional(),
235
+ })
236
+ .strict();
237
+ const ObservationDeclarationSchema = z
238
+ .object({
239
+ id: z.string().min(1),
240
+ kind: z.enum(['system', 'command', 'state']),
241
+ answer: z
242
+ .custom((value) => typeof value === 'function', {
243
+ message: 'observation.answer must be a function evaluated against the mounted game',
244
+ })
245
+ .optional(),
246
+ })
247
+ .strict();
248
+ export const AdapterDefinitionSchema = z
249
+ .object({
250
+ regions: RegionBindingSchema.optional(),
251
+ scenes: AdapterSceneTableSchema.optional(),
252
+ observation: z.array(ObservationDeclarationSchema).optional(),
253
+ })
254
+ .strict();
255
+ /**
256
+ * Validate and freeze a game's binding table.
257
+ *
258
+ * Called TWICE by design and with one schema: once here, in the game's own
259
+ * module, so a malformed table fails at the author's own file with the key
260
+ * named; and once in the host's loader, because the loaded default export is
261
+ * untrusted input crossing a module boundary. Two call sites, one rule — not
262
+ * two ways to say the same thing.
263
+ */
264
+ export function defineAdapter(input = {}) {
265
+ const parsed = AdapterDefinitionSchema.safeParse(input);
266
+ if (!parsed.success) {
267
+ throw new Error(`vgai.adapter.ts: invalid adapter definition — ${parsed.error.issues
268
+ .map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`)
269
+ .join('; ')}`);
270
+ }
271
+ return Object.freeze({
272
+ regions: input.regions ?? 'manifest-roots',
273
+ scenes: Object.freeze({ ...(input.scenes ?? {}) }),
274
+ observation: Object.freeze([...(input.observation ?? [])]),
275
+ });
276
+ }
277
+ /**
278
+ * THE NATIVE DEFAULT — regions derived mechanically from the manifest's
279
+ * `roots[]`, and a scene table produced by the two shipped finders. This is
280
+ * what a first-party project ships (`export default nativeAdapter()`), and it
281
+ * is also what a project with NO `vgai.adapter.ts` gets: that absence is the
282
+ * declared native default, not a silent fallback.
283
+ */
284
+ export function nativeAdapter(options = {}) {
285
+ return defineAdapter({
286
+ regions: 'manifest-roots',
287
+ scenes: {
288
+ ...(options.scenes ? { entries: options.scenes } : {}),
289
+ find: [
290
+ { finder: 'scenesFromEntrypointSelection', regions: EXPORTED_COMPOSITION_REGIONS },
291
+ { finder: 'prefabsFromStories' },
292
+ ],
293
+ },
294
+ ...(options.observation ? { observation: options.observation } : {}),
295
+ });
296
+ }
297
+ /**
298
+ * Parse an untrusted default export (the loaded `vgai.adapter.ts`) into a
299
+ * definition. Rejects loudly — an unknown key, a bad finder name, a
300
+ * `selection` paired with the rule form all fail by name.
301
+ */
302
+ export function parseAdapterDefinition(value) {
303
+ if (value === null || typeof value !== 'object') {
304
+ throw new Error('vgai.adapter.ts must default-export defineAdapter({…}) / nativeAdapter() — ' +
305
+ `got ${value === null ? 'null' : typeof value}`);
306
+ }
307
+ return defineAdapter(value);
308
+ }