@vgai/engine 0.5.14 → 0.5.16

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 (177) hide show
  1. package/dist/adapter/adapter-module.d.ts +228 -17
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +224 -42
  4. package/dist/adapter/authoring-seam-contract.d.ts +530 -0
  5. package/dist/adapter/authoring-seam-contract.d.ts.map +1 -0
  6. package/dist/adapter/authoring-seam-contract.js +162 -0
  7. package/dist/adapter/authoring.d.ts +237 -50
  8. package/dist/adapter/authoring.d.ts.map +1 -1
  9. package/dist/adapter/authoring.js +2 -1
  10. package/dist/adapter/entrypoint-selection-source.d.ts +35 -0
  11. package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -0
  12. package/dist/adapter/entrypoint-selection-source.js +151 -0
  13. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +7 -1
  14. package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
  15. package/dist/adapter/finders/scenes-from-entrypoint-selection.js +142 -21
  16. package/dist/adapter/first-party-systems.d.ts +13 -3
  17. package/dist/adapter/first-party-systems.d.ts.map +1 -1
  18. package/dist/adapter/first-party-systems.js +10 -3
  19. package/dist/adapter/index.d.ts +7 -2
  20. package/dist/adapter/index.d.ts.map +1 -1
  21. package/dist/adapter/index.js +4 -0
  22. package/dist/adapter/ingest/contract-system-adapters.d.ts +1 -1
  23. package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
  24. package/dist/adapter/ingest/contract-system-adapters.js +13 -25
  25. package/dist/adapter/ingest/game-contract-seams.d.ts +214 -0
  26. package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -0
  27. package/dist/adapter/ingest/game-contract-seams.js +62 -0
  28. package/dist/adapter/ingest/game-contract.d.ts +5 -3
  29. package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
  30. package/dist/adapter/ingest/game-contract.js +4 -1
  31. package/dist/adapter/ingest/merge-debug-adapters.d.ts +52 -0
  32. package/dist/adapter/ingest/merge-debug-adapters.d.ts.map +1 -0
  33. package/dist/adapter/ingest/merge-debug-adapters.js +141 -0
  34. package/dist/adapter/ingest/observation-debug-adapter.d.ts +56 -0
  35. package/dist/adapter/ingest/observation-debug-adapter.d.ts.map +1 -0
  36. package/dist/adapter/ingest/observation-debug-adapter.js +131 -0
  37. package/dist/adapter/ingest/scene-capture.d.ts +38 -13
  38. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  39. package/dist/adapter/ingest/scene-capture.js +101 -24
  40. package/dist/adapter/ingest/visible-capture-window.d.ts +22 -5
  41. package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
  42. package/dist/adapter/ingest/visible-capture-window.js +46 -7
  43. package/dist/adapter/native-debug-module.d.ts +43 -0
  44. package/dist/adapter/native-debug-module.d.ts.map +1 -0
  45. package/dist/adapter/native-debug-module.js +230 -0
  46. package/dist/adapter/object3d-authoring-subject.d.ts +32 -0
  47. package/dist/adapter/object3d-authoring-subject.d.ts.map +1 -0
  48. package/dist/adapter/object3d-authoring-subject.js +25 -0
  49. package/dist/adapter/root-adapter.d.ts +21 -56
  50. package/dist/adapter/root-adapter.d.ts.map +1 -1
  51. package/dist/adapter/root-adapter.js +4 -20
  52. package/dist/adapter/root-seam-contract.d.ts +124 -0
  53. package/dist/adapter/root-seam-contract.d.ts.map +1 -0
  54. package/dist/adapter/root-seam-contract.js +36 -0
  55. package/dist/adapter/seam-evidence.d.ts +118 -0
  56. package/dist/adapter/seam-evidence.d.ts.map +1 -0
  57. package/dist/adapter/seam-evidence.js +231 -0
  58. package/dist/adapter/setup-three-root-adapter.d.ts +2 -3
  59. package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
  60. package/dist/adapter/setup-three-root-adapter.js +2 -3
  61. package/dist/adapter/system-adapter.d.ts +6 -1
  62. package/dist/adapter/system-adapter.d.ts.map +1 -1
  63. package/dist/adapter/system-seam-contract.d.ts +354 -0
  64. package/dist/adapter/system-seam-contract.d.ts.map +1 -0
  65. package/dist/adapter/system-seam-contract.js +95 -0
  66. package/dist/animation/xstate-animation-binding.d.ts +6 -0
  67. package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
  68. package/dist/animation/xstate-animation-binding.js +142 -4
  69. package/dist/asset-formats/collider.d.ts +1 -1
  70. package/dist/asset-formats/mesh.d.ts +2 -2
  71. package/dist/asset-formats/particles.d.ts +1 -1
  72. package/dist/canvas-react/engine-bridge.d.ts +6 -6
  73. package/dist/canvas-react/engine-bridge.d.ts.map +1 -1
  74. package/dist/canvas-react/engine-bridge.js +2 -2
  75. package/dist/canvas-react/index.d.ts +2 -2
  76. package/dist/canvas-react/index.d.ts.map +1 -1
  77. package/dist/canvas-react/index.js +1 -1
  78. package/dist/canvas-react/pixi-react-adapter.d.ts +15 -9
  79. package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
  80. package/dist/canvas-react/pixi-react-adapter.js +43 -29
  81. package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
  82. package/dist/canvas-react/pixi-react-root-factory.js +4 -0
  83. package/dist/canvas-react/world-context.d.ts +22 -12
  84. package/dist/canvas-react/world-context.d.ts.map +1 -1
  85. package/dist/canvas-react/world-context.js +12 -51
  86. package/dist/ecs/user-data.d.ts +5 -0
  87. package/dist/ecs/user-data.d.ts.map +1 -1
  88. package/dist/ecs/user-data.js +3 -0
  89. package/dist/input/input-manager.d.ts +49 -0
  90. package/dist/input/input-manager.d.ts.map +1 -1
  91. package/dist/input/input-manager.js +127 -0
  92. package/dist/manifest/schema.d.ts +9 -9
  93. package/dist/manifest/schema.js +1 -1
  94. package/dist/pixi/authoring.d.ts.map +1 -1
  95. package/dist/pixi/authoring.js +220 -1
  96. package/dist/pixi/ingest.d.ts +2 -1
  97. package/dist/pixi/ingest.d.ts.map +1 -1
  98. package/dist/pixi/ingest.js +4 -2
  99. package/dist/pixi/scene-capture.d.ts +9 -1
  100. package/dist/pixi/scene-capture.d.ts.map +1 -1
  101. package/dist/pixi/scene-capture.js +9 -6
  102. package/dist/render/particles-factory.d.ts +13 -6
  103. package/dist/render/particles-factory.d.ts.map +1 -1
  104. package/dist/render/particles-factory.js +6 -3
  105. package/dist/runtime/create-runtime.d.ts +23 -46
  106. package/dist/runtime/create-runtime.d.ts.map +1 -1
  107. package/dist/runtime/create-runtime.js +22 -67
  108. package/dist/runtime/game.d.ts +29 -71
  109. package/dist/runtime/game.d.ts.map +1 -1
  110. package/dist/runtime/game.js +15 -54
  111. package/dist/runtime/mount-game.d.ts.map +1 -1
  112. package/dist/runtime/mount-game.js +12 -1
  113. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  114. package/dist/runtime/mount-manifest.js +4 -5
  115. package/dist/runtime/types.d.ts +18 -0
  116. package/dist/runtime/types.d.ts.map +1 -1
  117. package/dist/setup/setup-renderer.d.ts +3 -4
  118. package/dist/setup/setup-renderer.d.ts.map +1 -1
  119. package/dist/setup/setup-renderer.js +3 -4
  120. package/dist/world3d-react/engine-bridge.d.ts +1 -1
  121. package/dist/world3d-react/engine-bridge.js +1 -1
  122. package/dist/world3d-react/r3f-adapter.d.ts +7 -0
  123. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
  124. package/dist/world3d-react/r3f-adapter.js +50 -21
  125. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  126. package/dist/world3d-react/r3f-root-factory.js +4 -0
  127. package/dist/world3d-react/world-context.d.ts +9 -3
  128. package/dist/world3d-react/world-context.d.ts.map +1 -1
  129. package/dist/world3d-react/world-context.js +5 -18
  130. package/package.json +1 -1
  131. package/schemas/engine-capabilities.json +8 -8
  132. package/schemas/vgai-project.schema.json +1 -1
  133. package/src/adapter/adapter-module.ts +407 -50
  134. package/src/adapter/authoring-seam-contract.ts +211 -0
  135. package/src/adapter/authoring.ts +242 -50
  136. package/src/adapter/entrypoint-selection-source.ts +159 -0
  137. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +159 -28
  138. package/src/adapter/first-party-systems.ts +17 -3
  139. package/src/adapter/index.ts +51 -1
  140. package/src/adapter/ingest/contract-system-adapters.ts +26 -30
  141. package/src/adapter/ingest/game-contract-seams.ts +85 -0
  142. package/src/adapter/ingest/game-contract.ts +9 -4
  143. package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
  144. package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
  145. package/src/adapter/ingest/scene-capture.ts +126 -23
  146. package/src/adapter/ingest/visible-capture-window.ts +63 -9
  147. package/src/adapter/native-debug-module.ts +307 -0
  148. package/src/adapter/object3d-authoring-subject.ts +50 -0
  149. package/src/adapter/root-adapter.ts +21 -56
  150. package/src/adapter/root-seam-contract.ts +56 -0
  151. package/src/adapter/seam-evidence.ts +336 -0
  152. package/src/adapter/setup-three-root-adapter.ts +4 -6
  153. package/src/adapter/system-adapter.ts +6 -1
  154. package/src/adapter/system-seam-contract.ts +121 -0
  155. package/src/animation/xstate-animation-binding.ts +151 -4
  156. package/src/canvas-react/engine-bridge.ts +6 -6
  157. package/src/canvas-react/index.ts +2 -1
  158. package/src/canvas-react/pixi-react-adapter.tsx +55 -34
  159. package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
  160. package/src/canvas-react/world-context.ts +26 -101
  161. package/src/ecs/user-data.ts +5 -0
  162. package/src/input/input-manager.ts +122 -0
  163. package/src/manifest/schema.ts +1 -1
  164. package/src/pixi/authoring.ts +236 -1
  165. package/src/pixi/ingest.ts +9 -2
  166. package/src/pixi/scene-capture.ts +20 -5
  167. package/src/render/particles-factory.ts +16 -6
  168. package/src/runtime/create-runtime.ts +41 -106
  169. package/src/runtime/game.ts +41 -111
  170. package/src/runtime/mount-game.ts +19 -1
  171. package/src/runtime/mount-manifest.ts +4 -5
  172. package/src/runtime/types.ts +22 -0
  173. package/src/setup/setup-renderer.ts +3 -4
  174. package/src/world3d-react/engine-bridge.ts +1 -1
  175. package/src/world3d-react/r3f-adapter.tsx +64 -24
  176. package/src/world3d-react/r3f-root-factory.tsx +4 -0
  177. package/src/world3d-react/world-context.ts +11 -36
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Evidence that a protocol seam tells the truth.
3
+ *
4
+ * A property existing on an object proves only SHAPE. It does not prove that
5
+ * calling it works, that its effect reaches the consumer, or that an authored
6
+ * effect survives a cold remount. Those are deliberately separate stages so a
7
+ * declaration can never be promoted to a green capability merely because it
8
+ * has the right spelling.
9
+ *
10
+ * Receipts are scoped to a SUBJECT and an EPOCH. A provider from a prior mount
11
+ * is not evidence about the current mount, even when both mounts use the same
12
+ * project and node ids. This is the generic answer to stale adapter registries,
13
+ * late provider registration, and project switching: old receipts simply do
14
+ * not participate in the new verdict.
15
+ */
16
+
17
+ export const SEAM_PROOF_STAGES = ['claim', 'shape', 'operation', 'effect', 'round-trip'] as const;
18
+
19
+ export type SeamProofStage = (typeof SEAM_PROOF_STAGES)[number];
20
+ export type SeamEvidenceOutcome = 'pass' | 'fail' | 'absent';
21
+ export type SeamEvidenceSource =
22
+ | 'declaration'
23
+ | 'shape-check'
24
+ | 'consumer'
25
+ | 'conformance-probe'
26
+ | 'round-trip';
27
+
28
+ /** One directly observed fact. `detail` names the observation, never a claim
29
+ * such as "supported" with no described bar behind it. */
30
+ export interface SeamEvidenceReceipt {
31
+ readonly seam: string;
32
+ readonly subject: string;
33
+ readonly epoch: string;
34
+ readonly stage: SeamProofStage;
35
+ readonly outcome: SeamEvidenceOutcome;
36
+ readonly source: SeamEvidenceSource;
37
+ readonly detail: string;
38
+ }
39
+
40
+ export type SeamEvidenceState = 'verified' | 'failed' | 'absent' | 'unverified' | 'not-applicable';
41
+
42
+ export interface SeamEvidenceVerdict {
43
+ readonly seam: string;
44
+ readonly subject: string;
45
+ readonly epoch: string;
46
+ readonly required: SeamProofStage;
47
+ readonly state: SeamEvidenceState;
48
+ /** The strongest current-epoch receipt, or null when nobody has measured the
49
+ * seam. A not-applicable verdict carries its reason here instead. */
50
+ readonly receipt: SeamEvidenceReceipt | null;
51
+ readonly detail: string;
52
+ }
53
+
54
+ const STAGE_RANK: Readonly<Record<SeamProofStage, number>> = {
55
+ claim: 0,
56
+ shape: 1,
57
+ operation: 2,
58
+ effect: 3,
59
+ 'round-trip': 4,
60
+ };
61
+
62
+ export function seamStageAtLeast(observed: SeamProofStage, required: SeamProofStage): boolean {
63
+ return STAGE_RANK[observed] >= STAGE_RANK[required];
64
+ }
65
+
66
+ /**
67
+ * Grade one seam from receipts for ONE subject epoch.
68
+ *
69
+ * The strongest stage wins. At the same stage, the last receipt wins so a
70
+ * successful retry can close a failure and a later failure can reopen it.
71
+ * Lower-stage evidence can never overrule a stronger observation: a completed
72
+ * cold-remount round trip is not invalidated by a later shape census.
73
+ */
74
+ export function gradeSeamEvidence(options: {
75
+ readonly seam: string;
76
+ readonly subject: string;
77
+ readonly epoch: string;
78
+ readonly required: SeamProofStage;
79
+ readonly receipts: readonly SeamEvidenceReceipt[];
80
+ readonly notApplicable?: string | undefined;
81
+ }): SeamEvidenceVerdict {
82
+ const { seam, subject, epoch, required } = options;
83
+ if (options.notApplicable !== undefined) {
84
+ return {
85
+ seam,
86
+ subject,
87
+ epoch,
88
+ required,
89
+ state: 'not-applicable',
90
+ receipt: null,
91
+ detail: options.notApplicable,
92
+ };
93
+ }
94
+
95
+ let strongest: SeamEvidenceReceipt | null = null;
96
+ for (const receipt of options.receipts) {
97
+ if (receipt.seam !== seam || receipt.subject !== subject || receipt.epoch !== epoch) continue;
98
+ if (!strongest || STAGE_RANK[receipt.stage] >= STAGE_RANK[strongest.stage]) {
99
+ strongest = receipt;
100
+ }
101
+ }
102
+
103
+ if (!strongest) {
104
+ return {
105
+ seam,
106
+ subject,
107
+ epoch,
108
+ required,
109
+ state: 'unverified',
110
+ receipt: null,
111
+ detail: `no current-epoch evidence has reached the required ${required} stage`,
112
+ };
113
+ }
114
+ if (strongest.outcome === 'fail') {
115
+ return {
116
+ seam,
117
+ subject,
118
+ epoch,
119
+ required,
120
+ state: 'failed',
121
+ receipt: strongest,
122
+ detail: strongest.detail,
123
+ };
124
+ }
125
+ if (strongest.outcome === 'absent') {
126
+ return {
127
+ seam,
128
+ subject,
129
+ epoch,
130
+ required,
131
+ state: 'absent',
132
+ receipt: strongest,
133
+ detail: strongest.detail,
134
+ };
135
+ }
136
+ if (!seamStageAtLeast(strongest.stage, required)) {
137
+ return {
138
+ seam,
139
+ subject,
140
+ epoch,
141
+ required,
142
+ state: 'unverified',
143
+ receipt: strongest,
144
+ detail: `${strongest.detail}; this proves ${strongest.stage}, but this seam requires ${required}`,
145
+ };
146
+ }
147
+ return {
148
+ seam,
149
+ subject,
150
+ epoch,
151
+ required,
152
+ state: 'verified',
153
+ receipt: strongest,
154
+ detail: strongest.detail,
155
+ };
156
+ }
157
+
158
+ /** In-memory current-session ledger. It is evidence, not a project file and
159
+ * therefore introduces no VGAI data format. */
160
+ export class SeamEvidenceLedger {
161
+ readonly #receipts: SeamEvidenceReceipt[] = [];
162
+
163
+ record(receipt: SeamEvidenceReceipt): void {
164
+ if (!receipt.seam || !receipt.subject || !receipt.epoch || !receipt.detail.trim()) {
165
+ throw new Error('seam evidence requires non-empty seam, subject, epoch, and detail');
166
+ }
167
+ this.#receipts.push(receipt);
168
+ }
169
+
170
+ receipts(options?: {
171
+ readonly subject?: string | undefined;
172
+ readonly epoch?: string | undefined;
173
+ }): readonly SeamEvidenceReceipt[] {
174
+ return this.#receipts.filter(
175
+ (receipt) =>
176
+ (options?.subject === undefined || receipt.subject === options.subject) &&
177
+ (options?.epoch === undefined || receipt.epoch === options.epoch),
178
+ );
179
+ }
180
+
181
+ verdict(options: {
182
+ readonly seam: string;
183
+ readonly subject: string;
184
+ readonly epoch: string;
185
+ readonly required: SeamProofStage;
186
+ readonly notApplicable?: string | undefined;
187
+ }): SeamEvidenceVerdict {
188
+ return gradeSeamEvidence({ ...options, receipts: this.#receipts });
189
+ }
190
+
191
+ clearSubject(subject: string): void {
192
+ for (let index = this.#receipts.length - 1; index >= 0; index -= 1) {
193
+ if (this.#receipts[index]?.subject === subject) this.#receipts.splice(index, 1);
194
+ }
195
+ }
196
+
197
+ clear(): void {
198
+ this.#receipts.length = 0;
199
+ }
200
+ }
201
+
202
+ type NonUndefined<T> = Exclude<T, undefined>;
203
+ type MemberKind<T> = NonUndefined<T> extends (...args: never[]) => unknown ? 'function' : 'value';
204
+
205
+ /** A compiler-pinned description of every member of `T`. Adding, removing, or
206
+ * changing the optionality/kind of a contract member breaks its descriptor. */
207
+ export type SeamShape<T> = {
208
+ readonly [K in keyof T]-?: {
209
+ readonly optional: undefined extends T[K] ? true : false;
210
+ readonly kind: MemberKind<T[K]>;
211
+ readonly required: SeamProofStage;
212
+ };
213
+ };
214
+
215
+ export function defineSeamShape<T>() {
216
+ return <const TShape extends SeamShape<T>>(shape: TShape): TShape => shape;
217
+ }
218
+
219
+ function memberShapeReceipt(options: {
220
+ readonly seam: string;
221
+ readonly value: unknown;
222
+ readonly optional: boolean;
223
+ readonly kind: 'function' | 'value';
224
+ readonly subject: string;
225
+ readonly epoch: string;
226
+ }): SeamEvidenceReceipt {
227
+ if (options.value === undefined) {
228
+ return {
229
+ seam: options.seam,
230
+ subject: options.subject,
231
+ epoch: options.epoch,
232
+ stage: 'shape',
233
+ outcome: options.optional ? 'absent' : 'fail',
234
+ source: 'shape-check',
235
+ detail: options.optional
236
+ ? `${options.seam} is not exposed by this subject`
237
+ : `${options.seam} is required by the contract but is absent`,
238
+ };
239
+ }
240
+ const actual = typeof options.value;
241
+ const valid = options.kind === 'function' ? actual === 'function' : actual !== 'function';
242
+ return {
243
+ seam: options.seam,
244
+ subject: options.subject,
245
+ epoch: options.epoch,
246
+ stage: 'shape',
247
+ outcome: valid ? 'pass' : 'fail',
248
+ source: 'shape-check',
249
+ detail: valid
250
+ ? `${options.seam} has the contract's ${options.kind} shape`
251
+ : `${options.seam} is ${actual}; the contract requires a ${options.kind}`,
252
+ };
253
+ }
254
+
255
+ /** Shape is intentionally only shape. Successful members produce `shape`
256
+ * receipts; callers must add operation/effect/round-trip receipts from actual
257
+ * consumers or conformance probes before those seams can verify. */
258
+ export function inspectSeamShape<T extends object>(options: {
259
+ readonly prefix: string;
260
+ readonly value: unknown;
261
+ readonly shape: SeamShape<T>;
262
+ readonly subject: string;
263
+ readonly epoch: string;
264
+ }): readonly SeamEvidenceReceipt[] {
265
+ const receipts: SeamEvidenceReceipt[] = [];
266
+ const object =
267
+ options.value !== null &&
268
+ (typeof options.value === 'object' || typeof options.value === 'function')
269
+ ? (options.value as Record<string, unknown>)
270
+ : null;
271
+ const members = Object.entries(options.shape) as Array<
272
+ [string, { readonly optional: boolean; readonly kind: 'function' | 'value' }]
273
+ >;
274
+ for (const [member, spec] of members) {
275
+ const seam = `${options.prefix}.${member}`;
276
+ const value = object?.[member];
277
+ receipts.push(
278
+ memberShapeReceipt({
279
+ seam,
280
+ subject: options.subject,
281
+ epoch: options.epoch,
282
+ value,
283
+ optional: spec.optional,
284
+ kind: spec.kind,
285
+ }),
286
+ );
287
+ }
288
+ return receipts;
289
+ }
290
+
291
+ /**
292
+ * Grade a carrier and every member it actually advertises.
293
+ *
294
+ * Optional-and-absent is honest degradation. Optional-and-present is a real
295
+ * promise and is held to exactly the same proof bar as a required member. This
296
+ * is the shared rule that prevents each coverage family from inventing a
297
+ * different meaning for “provider present”.
298
+ */
299
+ export function gradeSeamCarrier(options: {
300
+ readonly seam: string;
301
+ readonly required: SeamProofStage;
302
+ readonly shape?: SeamShape<object> | undefined;
303
+ readonly subject: string;
304
+ readonly epoch: string;
305
+ readonly receipts: readonly SeamEvidenceReceipt[];
306
+ }): SeamEvidenceVerdict {
307
+ const base = gradeSeamEvidence(options);
308
+ if (base.state === 'absent' || base.state === 'failed' || !options.shape) return base;
309
+
310
+ for (const [member, spec] of Object.entries(options.shape)) {
311
+ const memberVerdict = gradeSeamEvidence({
312
+ seam: `${options.seam}.${member}`,
313
+ subject: options.subject,
314
+ epoch: options.epoch,
315
+ required: spec.required,
316
+ receipts: options.receipts,
317
+ });
318
+ if (spec.optional && memberVerdict.state === 'absent') continue;
319
+ if (memberVerdict.state === 'failed' || memberVerdict.state === 'absent') {
320
+ return {
321
+ ...base,
322
+ state: 'failed',
323
+ receipt: memberVerdict.receipt,
324
+ detail: `${options.seam} is malformed: ${memberVerdict.detail}`,
325
+ };
326
+ }
327
+ if (memberVerdict.state !== 'verified') {
328
+ return {
329
+ ...base,
330
+ state: 'unverified',
331
+ detail: `${options.seam} has the required shape, but ${memberVerdict.seam} is unverified: ${memberVerdict.detail}`,
332
+ };
333
+ }
334
+ }
335
+ return base;
336
+ }
@@ -119,9 +119,8 @@ export interface MountedSetupThreeRoot extends MountedThreeRoot {
119
119
  * `Game.runFrame` is the CANONICAL driver going forward: it calls
120
120
  * `frame.runPhase` once per (phase, substep) for every world, then
121
121
  * `frame.endFrame` once per substep after all roots finish all phases.
122
- * `update` (below) remains the legacy/direct entry point to the exact same
123
- * `SystemRunner` direct callers (editor HMR, adapter conformance tests)
124
- * keep using it unchanged.
122
+ * `update` is the direct entry point to the same `SystemRunner` for hosts
123
+ * that do not partition phases themselves.
125
124
  */
126
125
  readonly frame: RootFrameHooks;
127
126
  }
@@ -168,9 +167,8 @@ export class SetupThreeRootAdapter implements RootAdapter {
168
167
 
169
168
  setAssetPrefix(assetPrefix);
170
169
 
171
- // --- Scene view (scene/camera/composer). Browser: the canonical builder, so
172
- // behavior is byte-identical to the old create-runtime. Headless: plain
173
- // scene/camera + a no-op composer (no GPU). ---
170
+ // --- Scene view (scene/camera/composer). Browser: canonical builder.
171
+ // Headless: plain scene/camera + a no-op composer (no GPU). ---
174
172
  let scene: THREE.Scene;
175
173
  let camera: THREE.PerspectiveCamera;
176
174
  let composer: EffectComposer;
@@ -340,7 +340,9 @@ export interface NavCrowdAgentState {
340
340
  * editor's Navigation panel (re)build and persist a navmesh through the seam —
341
341
  * the first-party recast implementation (`createNavigationAdapter`) is the
342
342
  * blessed implementer — while an external game that owns its build simply
343
- * omits them. `crowdAgents` feeds the play-mode crowd debug draw.
343
+ * omits them. `clear` lets that same owner release its navmesh; the editor never
344
+ * disposes implementation handles itself. `crowdAgents` feeds the play-mode
345
+ * crowd debug draw.
344
346
  */
345
347
  export interface NavigationAdapter {
346
348
  hasNavMesh(): boolean;
@@ -352,6 +354,9 @@ export interface NavigationAdapter {
352
354
  /** Serialize the current navmesh to a binary blob (the editor persists it
353
355
  * as the scene's `.navmesh` sidecar). Throws when nothing is built. */
354
356
  exportData?(): Uint8Array;
357
+ /** Release the current navmesh and any debug object it attached to `scene`.
358
+ * Optional because an external game's navigation may be inspect-only. */
359
+ clear?(scene: THREE.Scene): void;
355
360
  /** Live crowd-agent snapshots for debug draw. Empty when no crowd. */
356
361
  crowdAgents?(): NavCrowdAgentState[];
357
362
  }
@@ -0,0 +1,121 @@
1
+ /** Exhaustive proof requirements for mounted SystemAdapters. A bound slot is
2
+ * availability, not success: each nested operation has its own required proof
3
+ * stage and therefore cannot hide behind `active[slot] != null`. */
4
+
5
+ import type { PhysicsAdapter2D } from '../pixi/system-adapters';
6
+ import { defineSeamShape, type SeamShape } from './seam-evidence';
7
+ import type {
8
+ AudioAdapter,
9
+ CameraAdapter,
10
+ DebugAdapter,
11
+ NavigationAdapter,
12
+ NetworkingAdapter,
13
+ PhysicsAdapter,
14
+ RenderDebugAdapter,
15
+ SystemAdapters,
16
+ } from './system-adapter';
17
+
18
+ export const SYSTEM_ADAPTERS_SHAPE = defineSeamShape<SystemAdapters>()({
19
+ physics: { optional: true, kind: 'value', required: 'effect' },
20
+ networking: { optional: true, kind: 'value', required: 'operation' },
21
+ navigation: { optional: true, kind: 'value', required: 'operation' },
22
+ audio: { optional: true, kind: 'value', required: 'effect' },
23
+ camera: { optional: true, kind: 'value', required: 'effect' },
24
+ debug: { optional: true, kind: 'value', required: 'operation' },
25
+ renderDebug: { optional: true, kind: 'value', required: 'effect' },
26
+ });
27
+
28
+ export const PHYSICS_ADAPTER_SHAPE = defineSeamShape<PhysicsAdapter>()({
29
+ keyedBy: { optional: true, kind: 'value', required: 'shape' },
30
+ ownerOf: { optional: false, kind: 'function', required: 'operation' },
31
+ freeze: { optional: false, kind: 'function', required: 'effect' },
32
+ commit: { optional: false, kind: 'function', required: 'effect' },
33
+ unfreeze: { optional: false, kind: 'function', required: 'effect' },
34
+ debugDraw: { optional: true, kind: 'function', required: 'operation' },
35
+ setDebugDrawEnabled: { optional: true, kind: 'function', required: 'effect' },
36
+ contactPoints: { optional: true, kind: 'function', required: 'operation' },
37
+ colliders: { optional: true, kind: 'function', required: 'operation' },
38
+ previewCollider: { optional: true, kind: 'function', required: 'effect' },
39
+ joints: { optional: true, kind: 'function', required: 'operation' },
40
+ previewJointAnchor: { optional: true, kind: 'function', required: 'effect' },
41
+ });
42
+
43
+ export const PHYSICS_2D_ADAPTER_SHAPE = defineSeamShape<PhysicsAdapter2D>()({
44
+ keyedBy: { optional: false, kind: 'value', required: 'shape' },
45
+ ownerOf: { optional: false, kind: 'function', required: 'operation' },
46
+ freeze: { optional: false, kind: 'function', required: 'effect' },
47
+ commit: { optional: false, kind: 'function', required: 'effect' },
48
+ unfreeze: { optional: false, kind: 'function', required: 'effect' },
49
+ });
50
+
51
+ export const NETWORKING_ADAPTER_SHAPE = defineSeamShape<NetworkingAdapter>()({
52
+ peers: { optional: false, kind: 'function', required: 'operation' },
53
+ networkId: { optional: false, kind: 'function', required: 'operation' },
54
+ authority: { optional: false, kind: 'function', required: 'operation' },
55
+ editable: { optional: false, kind: 'function', required: 'operation' },
56
+ getConnectionState: { optional: false, kind: 'function', required: 'operation' },
57
+ getRoomInfo: { optional: false, kind: 'function', required: 'operation' },
58
+ getReplicationStats: { optional: false, kind: 'function', required: 'operation' },
59
+ subscribe: { optional: false, kind: 'function', required: 'effect' },
60
+ getStateSnapshot: { optional: true, kind: 'function', required: 'operation' },
61
+ messageEvents: { optional: true, kind: 'function', required: 'operation' },
62
+ getRates: { optional: true, kind: 'function', required: 'operation' },
63
+ getConditioning: { optional: true, kind: 'function', required: 'operation' },
64
+ setConditioning: { optional: true, kind: 'function', required: 'effect' },
65
+ getServerConfig: { optional: true, kind: 'function', required: 'operation' },
66
+ getPlayerIdentity: { optional: true, kind: 'function', required: 'operation' },
67
+ setPlayerIdentity: { optional: true, kind: 'function', required: 'effect' },
68
+ });
69
+
70
+ export const NAVIGATION_ADAPTER_SHAPE = defineSeamShape<NavigationAdapter>()({
71
+ hasNavMesh: { optional: false, kind: 'function', required: 'operation' },
72
+ findPath: { optional: false, kind: 'function', required: 'effect' },
73
+ debugMesh: { optional: false, kind: 'function', required: 'operation' },
74
+ bake: { optional: true, kind: 'function', required: 'effect' },
75
+ exportData: { optional: true, kind: 'function', required: 'operation' },
76
+ clear: { optional: true, kind: 'function', required: 'effect' },
77
+ crowdAgents: { optional: true, kind: 'function', required: 'operation' },
78
+ });
79
+
80
+ export const AUDIO_ADAPTER_SHAPE = defineSeamShape<AudioAdapter>()({
81
+ resume: { optional: true, kind: 'function', required: 'effect' },
82
+ setMuted: { optional: false, kind: 'function', required: 'effect' },
83
+ isMuted: { optional: false, kind: 'function', required: 'operation' },
84
+ graphSnapshot: { optional: true, kind: 'function', required: 'operation' },
85
+ transportState: { optional: true, kind: 'function', required: 'operation' },
86
+ acquireMeters: { optional: true, kind: 'function', required: 'effect' },
87
+ acquireRecordingStream: { optional: true, kind: 'function', required: 'effect' },
88
+ audioEvents: { optional: true, kind: 'function', required: 'operation' },
89
+ });
90
+
91
+ export const CAMERA_ADAPTER_SHAPE = defineSeamShape<CameraAdapter>()({
92
+ snapshot: { optional: false, kind: 'function', required: 'operation' },
93
+ subscribe: { optional: false, kind: 'function', required: 'effect' },
94
+ });
95
+
96
+ export const DEBUG_ADAPTER_SHAPE = defineSeamShape<DebugAdapter>()({
97
+ providers: { optional: false, kind: 'function', required: 'operation' },
98
+ state: { optional: false, kind: 'function', required: 'operation' },
99
+ stateAll: { optional: false, kind: 'function', required: 'operation' },
100
+ commands: { optional: false, kind: 'function', required: 'operation' },
101
+ invoke: { optional: false, kind: 'function', required: 'effect' },
102
+ events: { optional: false, kind: 'function', required: 'operation' },
103
+ });
104
+
105
+ export const RENDER_DEBUG_ADAPTER_SHAPE = defineSeamShape<RenderDebugAdapter>()({
106
+ captureFrame: { optional: false, kind: 'function', required: 'effect' },
107
+ memorySnapshot: { optional: true, kind: 'function', required: 'operation' },
108
+ });
109
+
110
+ type NonPhysicsSystemSlot = Exclude<keyof SystemAdapters, 'physics'>;
111
+
112
+ export const SYSTEM_PROVIDER_SHAPES: {
113
+ readonly [K in NonPhysicsSystemSlot]: SeamShape<NonNullable<SystemAdapters[K]>>;
114
+ } = {
115
+ networking: NETWORKING_ADAPTER_SHAPE,
116
+ navigation: NAVIGATION_ADAPTER_SHAPE,
117
+ audio: AUDIO_ADAPTER_SHAPE,
118
+ camera: CAMERA_ADAPTER_SHAPE,
119
+ debug: DEBUG_ADAPTER_SHAPE,
120
+ renderDebug: RENDER_DEBUG_ADAPTER_SHAPE,
121
+ };