@volter/editor-game 0.5.65

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 (302) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +23 -0
  3. package/contributions/asset-budget-asset.menu.ts +26 -0
  4. package/contributions/asset-budget.action.ts +18 -0
  5. package/contributions/asset-budget.document.tsx +21 -0
  6. package/contributions/asset-budget.menu.ts +22 -0
  7. package/contributions/audio-unlock.service.ts +17 -0
  8. package/contributions/audio.utility.tsx +15 -0
  9. package/contributions/autoplay.service.ts +48 -0
  10. package/contributions/bridge.command.ts +172 -0
  11. package/contributions/build-profiles.document.tsx +21 -0
  12. package/contributions/build-progress.status.tsx +45 -0
  13. package/contributions/build.action.ts +26 -0
  14. package/contributions/build.command.ts +27 -0
  15. package/contributions/build.header.tsx +37 -0
  16. package/contributions/build.menu.ts +31 -0
  17. package/contributions/build.service.ts +31 -0
  18. package/contributions/connection.status.tsx +43 -0
  19. package/contributions/coverage.service.ts +118 -0
  20. package/contributions/edit-mode-audio.service.ts +23 -0
  21. package/contributions/edit-mode-networking.service.ts +30 -0
  22. package/contributions/game-document.service.ts +25 -0
  23. package/contributions/game-eval.command.ts +210 -0
  24. package/contributions/game.layout.ts +19 -0
  25. package/contributions/gameplay.command.ts +255 -0
  26. package/contributions/generation.service.ts +96 -0
  27. package/contributions/generations.status.tsx +53 -0
  28. package/contributions/ingest.service.ts +80 -0
  29. package/contributions/instances.command.ts +75 -0
  30. package/contributions/navmesh.menu.ts +39 -0
  31. package/contributions/navmesh.service.ts +19 -0
  32. package/contributions/network.utility.tsx +17 -0
  33. package/contributions/play.command.ts +355 -0
  34. package/contributions/profiler.action.ts +16 -0
  35. package/contributions/profiler.menu.ts +22 -0
  36. package/contributions/profiler.utility.tsx +17 -0
  37. package/contributions/react/component-board.service.ts +21 -0
  38. package/contributions/react/design-time-mount.service.ts +60 -0
  39. package/contributions/react/pasteboard.action.ts +41 -0
  40. package/contributions/react/react-inspector.service.ts +85 -0
  41. package/contributions/react/story-documents.service.ts +44 -0
  42. package/contributions/scene-document.service.ts +32 -0
  43. package/contributions/state-watch.action.ts +17 -0
  44. package/contributions/state-watch.menu.ts +23 -0
  45. package/contributions/state-watch.utility.tsx +20 -0
  46. package/contributions/team-playtest.service.ts +124 -0
  47. package/contributions/three/camera-runtime.inspector.tsx +34 -0
  48. package/contributions/three/component-board.service.ts +24 -0
  49. package/contributions/three/component-verbs.command.ts +110 -0
  50. package/contributions/three/component-verbs.service.ts +92 -0
  51. package/contributions/three/constraints.inspector.tsx +34 -0
  52. package/contributions/three/model-asset-sections.service.ts +26 -0
  53. package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
  54. package/contributions/three/story-documents.service.ts +31 -0
  55. package/contributions/three/three-authoring.service.ts +66 -0
  56. package/contributions/transport.header.tsx +19 -0
  57. package/contributions/xstate-behavior.action.ts +42 -0
  58. package/contributions/xstate-behavior.document.tsx +73 -0
  59. package/contributions/xstate-behavior.inspector.tsx +28 -0
  60. package/contributions/xstate-behavior.menu.ts +23 -0
  61. package/package.json +144 -0
  62. package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
  63. package/src/asset-budget/asset-budget-model.ts +799 -0
  64. package/src/asset-budget/basis-encoder.ts +165 -0
  65. package/src/asset-budget/gltf-io.ts +154 -0
  66. package/src/asset-budget/gltf-optimize.ts +384 -0
  67. package/src/asset-budget/image-dims.ts +78 -0
  68. package/src/asset-budget/optimize-apply.ts +221 -0
  69. package/src/audio/AudioDebuggerPanel.tsx +457 -0
  70. package/src/audio/audio-debugger-model.ts +87 -0
  71. package/src/bridge/call.ts +60 -0
  72. package/src/bridge/dispatch.ts +459 -0
  73. package/src/bridge/live-frames.ts +25 -0
  74. package/src/bridge/screenshot.ts +316 -0
  75. package/src/build/BuildProfilesPanel.tsx +476 -0
  76. package/src/build/build-session.ts +247 -0
  77. package/src/build/format-bytes.ts +14 -0
  78. package/src/command-results.ts +36 -0
  79. package/src/coverage/live-authoring-surface.ts +30 -0
  80. package/src/coverage/live-project-verbs.ts +162 -0
  81. package/src/coverage/native-system-coverage.ts +143 -0
  82. package/src/coverage/root-coverage.ts +79 -0
  83. package/src/coverage/session-coverage.ts +193 -0
  84. package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
  85. package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
  86. package/src/edit-mode/edit-mode-audio.ts +56 -0
  87. package/src/edit-mode/edit-mode-networking.ts +112 -0
  88. package/src/game-document/DevicePresetPicker.tsx +84 -0
  89. package/src/game-document/GameCaptureFrameButton.tsx +45 -0
  90. package/src/game-document/GameDocument.tsx +215 -0
  91. package/src/game-document/GamePanel.tsx +662 -0
  92. package/src/game-document/InstanceInspectorPicker.tsx +183 -0
  93. package/src/game-document/crowd-debug.ts +183 -0
  94. package/src/game-document/device-preview.ts +336 -0
  95. package/src/game-document/game-view-store.ts +107 -0
  96. package/src/game-document/physics-debug.ts +187 -0
  97. package/src/generation/GenerationActivity.tsx +421 -0
  98. package/src/generation/GenerationGallery.css +231 -0
  99. package/src/generation/generation-documents.tsx +338 -0
  100. package/src/generation/generation-jobs.ts +128 -0
  101. package/src/generation/generation-presentation.ts +257 -0
  102. package/src/host/adapter-reach.ts +445 -0
  103. package/src/host/adapter-runtime-bindings.ts +303 -0
  104. package/src/host/after-paint.ts +112 -0
  105. package/src/host/api/configurations.ts +66 -0
  106. package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
  107. package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
  108. package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
  109. package/src/host/authoring/contract-scenes-stories.ts +204 -0
  110. package/src/host/authoring/creation-site-related.ts +55 -0
  111. package/src/host/authoring/ephemeral-persistence.ts +29 -0
  112. package/src/host/authoring/gesture-persist.ts +84 -0
  113. package/src/host/authoring/ingest-data-writer.ts +232 -0
  114. package/src/host/authoring/ingest-source-persistence.ts +826 -0
  115. package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
  116. package/src/host/authoring/mounted-authoring.ts +41 -0
  117. package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
  118. package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
  119. package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
  120. package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
  121. package/src/host/authoring/pixi-isolation-assets.ts +25 -0
  122. package/src/host/authoring/pixi-live-write-target.ts +979 -0
  123. package/src/host/authoring/pixi-source-identity.ts +141 -0
  124. package/src/host/authoring/pixi-still-presentation.ts +71 -0
  125. package/src/host/authoring/pixi-structure-history.ts +237 -0
  126. package/src/host/authoring/pixi-transform-channels.ts +205 -0
  127. package/src/host/authoring/selection-remount-handoff.ts +23 -0
  128. package/src/host/authoring/source-persistence-backend.ts +373 -0
  129. package/src/host/authoring/source-refresh-revisions.ts +81 -0
  130. package/src/host/authoring/struct-write-pipe.ts +143 -0
  131. package/src/host/auto-frame-window.ts +89 -0
  132. package/src/host/binding-resolver.ts +393 -0
  133. package/src/host/browser-transpile.ts +631 -0
  134. package/src/host/canvas-entry-runtime.ts +95 -0
  135. package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
  136. package/src/host/components/HeaderTelemetry.tsx +341 -0
  137. package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
  138. package/src/host/components/ResolutionPicker.tsx +89 -0
  139. package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
  140. package/src/host/components/frame-debugger-model.ts +579 -0
  141. package/src/host/components/header-telemetry-model.ts +74 -0
  142. package/src/host/components/scene-document.tsx +447 -0
  143. package/src/host/components/utility-view-state.ts +87 -0
  144. package/src/host/components/world-root-stage-binding.tsx +133 -0
  145. package/src/host/components/world-root-stage.ts +1046 -0
  146. package/src/host/coverage/authoring-read-probe.ts +583 -0
  147. package/src/host/coverage/capability-coverage.ts +1587 -0
  148. package/src/host/coverage/coverage-accounting.ts +261 -0
  149. package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
  150. package/src/host/coverage/project-verb-coverage.ts +148 -0
  151. package/src/host/coverage/system-adapter-coverage.ts +373 -0
  152. package/src/host/design-system-stories/StoryLayout.tsx +104 -0
  153. package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
  154. package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
  155. package/src/host/document-preview-three.ts +110 -0
  156. package/src/host/entry-adjudication.ts +89 -0
  157. package/src/host/game-location-guard.ts +150 -0
  158. package/src/host/game-module-access.ts +196 -0
  159. package/src/host/game-realm-page.ts +358 -0
  160. package/src/host/game-realm-reclaim.ts +55 -0
  161. package/src/host/game-realm-storage.ts +104 -0
  162. package/src/host/gameplay-export.ts +288 -0
  163. package/src/host/gameplay-recording.ts +717 -0
  164. package/src/host/gated-globals.ts +1511 -0
  165. package/src/host/history/json-history-resource.ts +254 -0
  166. package/src/host/ingest/registry.ts +261 -0
  167. package/src/host/instance-extract-actions.ts +120 -0
  168. package/src/host/instance-fork-actions.ts +120 -0
  169. package/src/host/play-control-hook.ts +26 -0
  170. package/src/host/playwright-shim.ts +479 -0
  171. package/src/host/projection/dom.ts +295 -0
  172. package/src/host/projection/pixi.ts +288 -0
  173. package/src/host/r3f-entry-runtime.ts +77 -0
  174. package/src/host/react-mount-runtime.ts +162 -0
  175. package/src/host/realm-services.ts +148 -0
  176. package/src/host/recording-preview.ts +106 -0
  177. package/src/host/roots/module-root.ts +203 -0
  178. package/src/host/roots/react-root.ts +181 -0
  179. package/src/host/same-realm-loop-gate.ts +544 -0
  180. package/src/host/scene-view-drawability.ts +69 -0
  181. package/src/host/sdk/tools.ts +31 -0
  182. package/src/host/served-bundle-runtime-modules.ts +331 -0
  183. package/src/host/server-log-bridge.ts +60 -0
  184. package/src/host/staged-projects.ts +24 -0
  185. package/src/host/stories/mounted-story-viewport-source.ts +64 -0
  186. package/src/host/stories/story-arg-descriptors.ts +50 -0
  187. package/src/host/stories/story-media-presence.ts +152 -0
  188. package/src/host/surface-content.ts +87 -0
  189. package/src/host/take-named-export.ts +23 -0
  190. package/src/host/three-ingest-runtime.ts +76 -0
  191. package/src/host/types-fastnoise-lite.d.ts +7 -0
  192. package/src/host/types-mikktspace.d.ts +20 -0
  193. package/src/host/types-troika-three-text.d.ts +7 -0
  194. package/src/host/use-active-performance-source.ts +53 -0
  195. package/src/host/viewport-pose-memory.ts +48 -0
  196. package/src/host/viewport-root-presentation.ts +40 -0
  197. package/src/ingest/active-ingest.ts +251 -0
  198. package/src/ingest/active-scene-navigation.ts +43 -0
  199. package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
  200. package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
  201. package/src/ingest/capture-wait-report.ts +122 -0
  202. package/src/ingest/deferred-ingest-play.ts +216 -0
  203. package/src/ingest/deferred-ingest-session.ts +23 -0
  204. package/src/ingest/discovery-public-ingest.ts +242 -0
  205. package/src/ingest/dom-stub-mark.ts +7 -0
  206. package/src/ingest/entry-load.ts +56 -0
  207. package/src/ingest/game-contract-realm.ts +34 -0
  208. package/src/ingest/game-pointer-lock.ts +89 -0
  209. package/src/ingest/held-scene-repaint.ts +73 -0
  210. package/src/ingest/host-surface-box.ts +174 -0
  211. package/src/ingest/ingest-boot-viewport.ts +54 -0
  212. package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
  213. package/src/ingest/ingest-canvas-scene.ts +50 -0
  214. package/src/ingest/ingest-evidence-hook.ts +131 -0
  215. package/src/ingest/ingest-frame-snapshot.ts +183 -0
  216. package/src/ingest/ingest-play-commands.ts +134 -0
  217. package/src/ingest/ingest-play-control.ts +299 -0
  218. package/src/ingest/ingest-render-debug.ts +320 -0
  219. package/src/ingest/ingest-siblings.ts +487 -0
  220. package/src/ingest/ingest-status.ts +62 -0
  221. package/src/ingest/live-ingest-facet.ts +46 -0
  222. package/src/ingest/module-mode.ts +229 -0
  223. package/src/ingest/mount-canvas-ingest-root.ts +899 -0
  224. package/src/ingest/mount-coverage.ts +296 -0
  225. package/src/ingest/mount-dom-ingest-root.ts +282 -0
  226. package/src/ingest/mount-ingest-root.ts +744 -0
  227. package/src/ingest/mount-three-ingest-root.ts +366 -0
  228. package/src/ingest/resolve-canvas.ts +47 -0
  229. package/src/ingest/resolve-three.ts +123 -0
  230. package/src/ingest/served-bundle.ts +109 -0
  231. package/src/ingest/served-html-boot.ts +292 -0
  232. package/src/ingest/surface-canvas.ts +84 -0
  233. package/src/ingest/surface-dom.ts +84 -0
  234. package/src/ingest/surface-three.ts +128 -0
  235. package/src/ingest/types.ts +76 -0
  236. package/src/ingest/unmount-ingest-root.ts +224 -0
  237. package/src/navmesh/navmesh-actions.ts +27 -0
  238. package/src/navmesh/navmesh-handler.ts +237 -0
  239. package/src/navmesh/navmesh-workflow-store.ts +78 -0
  240. package/src/network/NetworkInspectorPanel.tsx +644 -0
  241. package/src/network/network-inspector-model.ts +225 -0
  242. package/src/play/play-boot-stall.ts +118 -0
  243. package/src/play/play-log-events.ts +26 -0
  244. package/src/play/play-mode.ts +2553 -0
  245. package/src/play/play-recording.ts +335 -0
  246. package/src/play/react-play-live-authoring.ts +165 -0
  247. package/src/play-bar/PlayBar.tsx +488 -0
  248. package/src/play-bar/PlayerCountPicker.tsx +100 -0
  249. package/src/profiler/FrameDebuggerPanel.tsx +458 -0
  250. package/src/profiler/PerformancePanel.tsx +1068 -0
  251. package/src/profiler/ProfilerPanel.tsx +53 -0
  252. package/src/profiler/frame-debugger-store.ts +97 -0
  253. package/src/profiler/main-thread-busy.ts +90 -0
  254. package/src/react/design-time-react-mount.ts +782 -0
  255. package/src/react/dom-authoring-adapter.ts +764 -0
  256. package/src/react/pasteboard-materialize.ts +154 -0
  257. package/src/react/react-inspector-section.tsx +2496 -0
  258. package/src/react/react-world-authoring-adapter.ts +3795 -0
  259. package/src/react/story-documents/story-args-section.ts +19 -0
  260. package/src/react/story-documents/story-documents.tsx +1380 -0
  261. package/src/react/story-paint-bounds.ts +94 -0
  262. package/src/react/ui-board-document.tsx +101 -0
  263. package/src/react/ui-board-title.ts +9 -0
  264. package/src/react/ui-component-board.ts +72 -0
  265. package/src/services/audio-pose-guard.ts +81 -0
  266. package/src/services/game-audio-unlock.ts +48 -0
  267. package/src/state-watch/StateWatchPanel.tsx +535 -0
  268. package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
  269. package/src/three/authoring/constraint-inspector-section.tsx +189 -0
  270. package/src/three/authoring/design-time-renderer.ts +189 -0
  271. package/src/three/authoring/model-asset-inspector-section.css +41 -0
  272. package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
  273. package/src/three/authoring/oid-source-persistence.ts +557 -0
  274. package/src/three/authoring/r3f-design-session.ts +1174 -0
  275. package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
  276. package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
  277. package/src/three/authoring/spatial-audio-handles.ts +215 -0
  278. package/src/three/authoring/spatial-collider-handles.ts +229 -0
  279. package/src/three/authoring/spatial-joint-handles.ts +114 -0
  280. package/src/three/authoring/spatial-light-handles.ts +178 -0
  281. package/src/three/authoring/spatial-lod-handles.ts +93 -0
  282. package/src/three/authoring/spatial-particle-handles.ts +368 -0
  283. package/src/three/authoring/three-authoring-adapter.ts +1953 -0
  284. package/src/three/authoring/three-scene-identity.ts +19 -0
  285. package/src/three/authoring/three-spatial-handles.ts +161 -0
  286. package/src/three/authoring/typed-three-inspector.ts +528 -0
  287. package/src/three/component-verbs/extract-menu.ts +71 -0
  288. package/src/three/component-verbs/fork-menu.ts +78 -0
  289. package/src/three/component-verbs/internals-menu.ts +91 -0
  290. package/src/three/story-documents/three-story-documents.tsx +607 -0
  291. package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
  292. package/src/three/three-board/board-framing.ts +412 -0
  293. package/src/three/three-board/board-layout.ts +401 -0
  294. package/src/three/three-board/board-scene.ts +901 -0
  295. package/src/three/three-board/three-component-board.ts +62 -0
  296. package/src/xstate/XStateBehaviorSection.tsx +130 -0
  297. package/src/xstate/XStateMachineInspector.tsx +566 -0
  298. package/src/xstate/character-animation-machine.fixture.ts +74 -0
  299. package/src/xstate/live-behaviors.ts +106 -0
  300. package/src/xstate/use-live-actor-state.ts +44 -0
  301. package/src/xstate/xstate-graph.ts +235 -0
  302. package/src/xstate/xstate-layout.ts +76 -0
@@ -0,0 +1,583 @@
1
+ /**
2
+ * Side-effect-free conformance probes for AuthoringAdapter reads.
3
+ *
4
+ * Writes, subscriptions, and gesture brackets are deliberately not exercised
5
+ * here: status/coverage reads must not edit the project or perturb selection.
6
+ * Those seams therefore remain unverified until a real consumer or Doctor's
7
+ * transactional write walk records the stronger receipt.
8
+ */
9
+
10
+ import type {
11
+ AuthoringAdapter,
12
+ EditorNode,
13
+ SeamEvidenceReceipt,
14
+ SeamProofStage,
15
+ } from '@volter/editor-project/adapter';
16
+
17
+ type ReadRecorder = (
18
+ seam: string,
19
+ stage: SeamProofStage,
20
+ run: () => unknown,
21
+ valid?: (value: unknown) => boolean,
22
+ ) => unknown;
23
+
24
+ function reciprocalChildren(
25
+ adapter: AuthoringAdapter,
26
+ node: EditorNode,
27
+ ): { readonly valid: boolean; readonly children: readonly EditorNode[] } {
28
+ const children: EditorNode[] = [];
29
+ let valid = true;
30
+ for (const childId of node.childIds) {
31
+ const child = adapter.hierarchy.node(childId);
32
+ if (!child || child.parentId !== node.id) valid = false;
33
+ else children.push(child);
34
+ }
35
+ return { valid, children };
36
+ }
37
+
38
+ /** Duck-typed rather than `instanceof THREE.Object3D`: this module is imported
39
+ * by headless Node suites and by the coverage service, and neither should pull
40
+ * three in to ask whether a value has the shape the seam promises. */
41
+ function isObject3DLike(value: unknown): value is object {
42
+ return (
43
+ typeof value === 'object' &&
44
+ value !== null &&
45
+ (value as { isObject3D?: unknown }).isObject3D === true
46
+ );
47
+ }
48
+
49
+ /** Sampled, not exhaustive — see {@link objectLookupReceipts}. */
50
+ const OBJECT_LOOKUP_SAMPLE = 64;
51
+
52
+ type SeamPush = (seam: string, outcome: 'pass' | 'fail', detail: string) => void;
53
+
54
+ /** `object3D` over the sample: every answer must be an Object3D or `null`, and
55
+ * the objects that came back are what the inverse lookup is then asked about. */
56
+ function probeObject3D(
57
+ object3D: (id: string) => unknown,
58
+ sample: readonly string[],
59
+ push: SeamPush,
60
+ ): { readonly objects: ReadonlyArray<{ readonly id: string; readonly object: object }> } {
61
+ const objects: Array<{ readonly id: string; readonly object: object }> = [];
62
+ let failure: string | null = null;
63
+ for (const id of sample) {
64
+ let answer: unknown;
65
+ try {
66
+ answer = object3D(id);
67
+ } catch (error) {
68
+ failure = `hierarchy.object3D threw on the walked node ${id}: ${
69
+ error instanceof Error ? error.message : String(error)
70
+ }`;
71
+ break;
72
+ }
73
+ if (answer === null || answer === undefined) continue;
74
+ if (!isObject3DLike(answer)) {
75
+ failure = `hierarchy.object3D answered the walked node ${id} with a ${typeof answer} that is not an Object3D`;
76
+ break;
77
+ }
78
+ objects.push({ id, object: answer });
79
+ }
80
+ push(
81
+ 'editor.hierarchy.object3D',
82
+ failure ? 'fail' : 'pass',
83
+ failure ??
84
+ `hierarchy.object3D answered ${objects.length} of ${sample.length} walked node(s) with an Object3D and the rest with null`,
85
+ );
86
+ return { objects: failure ? [] : objects };
87
+ }
88
+
89
+ /**
90
+ * One question to `idForObject3D`: `true` when it named a node this walk
91
+ * listed, `null` when it answered `null` (legitimate — the object is not a
92
+ * row), and the refusing SENTENCE when the answer disagrees with the walk.
93
+ */
94
+ function askIdForObject3D(
95
+ idForObject3D: (object: never) => unknown,
96
+ id: string,
97
+ object: object,
98
+ walked: ReadonlySet<string>,
99
+ ): true | null | string {
100
+ let answer: unknown;
101
+ try {
102
+ answer = idForObject3D(object as never);
103
+ } catch (error) {
104
+ return `hierarchy.idForObject3D threw on the object hierarchy.object3D returned for ${id}: ${
105
+ error instanceof Error ? error.message : String(error)
106
+ }`;
107
+ }
108
+ if (answer === null || answer === undefined) return null;
109
+ if (typeof answer !== 'string' || !walked.has(answer)) {
110
+ return `hierarchy.idForObject3D answered ${JSON.stringify(answer)} for the object hierarchy.object3D returned for ${id}, which is not a node this walk listed`;
111
+ }
112
+ return true;
113
+ }
114
+
115
+ /** The inverse, checked against the walk: `null`, or an id this walk listed. */
116
+ function probeIdForObject3D(
117
+ idForObject3D: (object: never) => unknown,
118
+ objects: ReadonlyArray<{ readonly id: string; readonly object: object }>,
119
+ walked: ReadonlySet<string>,
120
+ push: SeamPush,
121
+ ): void {
122
+ if (objects.length === 0) {
123
+ push(
124
+ 'editor.hierarchy.idForObject3D',
125
+ 'fail',
126
+ 'hierarchy.idForObject3D could not be asked about anything: hierarchy.object3D handed back no Object3D for any walked node',
127
+ );
128
+ return;
129
+ }
130
+ let roundTripped = 0;
131
+ let failure: string | null = null;
132
+ for (const { id, object } of objects) {
133
+ const answered = askIdForObject3D(idForObject3D, id, object, walked);
134
+ if (answered === true) {
135
+ roundTripped += 1;
136
+ continue;
137
+ }
138
+ if (answered === null) continue;
139
+ failure = answered;
140
+ break;
141
+ }
142
+ if (!failure && roundTripped === 0) {
143
+ failure = `hierarchy.idForObject3D answered null for all ${objects.length} object(s) hierarchy.object3D handed back, so nothing it returned could be checked against the walk`;
144
+ }
145
+ push(
146
+ 'editor.hierarchy.idForObject3D',
147
+ failure ? 'fail' : 'pass',
148
+ failure ??
149
+ `hierarchy.idForObject3D named ${roundTripped} of ${objects.length} object(s) hierarchy.object3D returned with an id this walk listed`,
150
+ );
151
+ }
152
+
153
+ /**
154
+ * THE HIERARCHY'S TWO PURE LOOKUPS — `object3D` and its inverse
155
+ * `idForObject3D` — verified the only way a pure lookup can be: CALL it and
156
+ * check the answer against the walk that just ran.
157
+ *
158
+ * `idForObject3D` was declared `required: 'effect'` and could therefore never
159
+ * be verified (`@volter/editor-project/adapter/authoring-seam-contract`'s note, and the
160
+ * orchestrator's ruling of 2026-09-19): it has no side effect for a consumer
161
+ * to record a receipt for, so the hierarchy CARRIER graded `unverified` on
162
+ * every three adapter forever while printing a sentence that said the seam was
163
+ * unproven. Both are `operation` now, and this is what proves them.
164
+ *
165
+ * What "checked against the walk" means, precisely:
166
+ * - `object3D(id)` must answer with an Object3D or `null` — `null` is the
167
+ * honest answer for a row with no object behind it (the composite's world
168
+ * and organization rows), so it is a pass, and the detail says how many
169
+ * rows answered with one.
170
+ * - `idForObject3D(o)`, asked about an object `object3D` itself handed back,
171
+ * must answer with `null` or with an id THE WALK SAW. A row the adapter
172
+ * projects through a spatial target it does not index answers `null`
173
+ * legitimately (`r3f-source-authoring-adapter`'s component boundaries);
174
+ * an id no node in this tree carries is the lookup disagreeing with the
175
+ * hierarchy, which is a real failure. At least one object must round-trip,
176
+ * or nothing it returned could be checked at all.
177
+ *
178
+ * Sampled, not exhaustive: a Money-Masters-class tree is tens of thousands of
179
+ * rows and these two answers do not vary per row in a way a bigger sample
180
+ * would reveal. The count is in the detail either way.
181
+ */
182
+ function objectLookupReceipts(options: {
183
+ readonly adapter: AuthoringAdapter;
184
+ readonly subject: string;
185
+ readonly epoch: string;
186
+ readonly receipts: SeamEvidenceReceipt[];
187
+ readonly walked: readonly string[];
188
+ }): void {
189
+ const { adapter, subject, epoch, receipts, walked } = options;
190
+ const { object3D, idForObject3D } = adapter.hierarchy;
191
+ if (!object3D) return;
192
+ const push: SeamPush = (seam, outcome, detail) => {
193
+ receipts.push({
194
+ seam,
195
+ subject,
196
+ epoch,
197
+ stage: 'operation',
198
+ outcome,
199
+ source: 'conformance-probe',
200
+ detail,
201
+ });
202
+ };
203
+ const { objects } = probeObject3D(object3D, walked.slice(0, OBJECT_LOOKUP_SAMPLE), push);
204
+ if (idForObject3D) probeIdForObject3D(idForObject3D, objects, new Set(walked), push);
205
+ }
206
+
207
+ function hierarchyReceipts(options: {
208
+ readonly adapter: AuthoringAdapter;
209
+ readonly subject: string;
210
+ readonly epoch: string;
211
+ readonly record: ReadRecorder;
212
+ readonly receipts: SeamEvidenceReceipt[];
213
+ }): readonly EditorNode[] {
214
+ const { adapter, subject, epoch, record, receipts } = options;
215
+ const rootsValue = record(
216
+ 'editor.hierarchy.roots',
217
+ 'operation',
218
+ () => adapter.hierarchy.roots(),
219
+ (value) => Array.isArray(value) && value.length > 0,
220
+ );
221
+ const roots = Array.isArray(rootsValue) ? (rootsValue as EditorNode[]) : [];
222
+ let hierarchyValid = roots.length > 0;
223
+ const seen = new Set<string>();
224
+ const pending = [...roots];
225
+ // A walk BUDGET, not a size limit: a scene larger than this (Money Masters' translated
226
+ // Workspace is ~24,000 authored nodes) is still a finite, reciprocal tree — the probe has simply
227
+ // checked the first 10,000 of it. Running out of budget with work pending is reported as that,
228
+ // never as a failed seam.
229
+ const WALK_BUDGET = 10_000;
230
+ while (pending.length > 0 && seen.size < WALK_BUDGET) {
231
+ const declared = pending.shift();
232
+ if (!declared || seen.has(declared.id)) {
233
+ hierarchyValid = false;
234
+ continue;
235
+ }
236
+ seen.add(declared.id);
237
+ const node = record(
238
+ 'editor.hierarchy.node',
239
+ 'operation',
240
+ () => adapter.hierarchy.node(declared.id),
241
+ (value) =>
242
+ value !== null &&
243
+ typeof value === 'object' &&
244
+ (value as EditorNode).id === declared.id &&
245
+ Array.isArray((value as EditorNode).childIds),
246
+ ) as EditorNode | null | undefined;
247
+ if (!node) {
248
+ hierarchyValid = false;
249
+ continue;
250
+ }
251
+ const children = reciprocalChildren(adapter, node);
252
+ hierarchyValid &&= children.valid;
253
+ pending.push(...children.children);
254
+ }
255
+ const budgetReached = pending.length > 0 && seen.size >= WALK_BUDGET;
256
+ receipts.push({
257
+ seam: 'editor.hierarchy',
258
+ subject,
259
+ epoch,
260
+ stage: 'operation',
261
+ outcome: hierarchyValid ? 'pass' : 'fail',
262
+ source: 'conformance-probe',
263
+ detail: hierarchyValid
264
+ ? `hierarchy roots/node walked ${seen.size} unique node(s) with reciprocal parent/child ids${
265
+ budgetReached
266
+ ? ` (walk budget reached with ${pending.length} node(s) still pending — a larger tree, not a broken one)`
267
+ : ''
268
+ }`
269
+ : 'hierarchy roots/node did not form a finite, non-empty, reciprocal tree',
270
+ });
271
+ // The carrier's other members, all of them pure reads over the tree just
272
+ // walked. They are probed HERE and not left to a consumer because none of
273
+ // them has a consumer that records a receipt — which is exactly how the
274
+ // carrier came to grade `unverified` on a perfectly healthy adapter.
275
+ if (adapter.hierarchy.crossSurfaceStructureSignature) {
276
+ record(
277
+ 'editor.hierarchy.crossSurfaceStructureSignature',
278
+ 'operation',
279
+ () => adapter.hierarchy.crossSurfaceStructureSignature?.(),
280
+ (value) => value === null || typeof value === 'string',
281
+ );
282
+ }
283
+ objectLookupReceipts({ adapter, subject, epoch, receipts, walked: [...seen] });
284
+ return roots;
285
+ }
286
+
287
+ function probeCoreProviders(adapter: AuthoringAdapter, id: string, record: ReadRecorder): void {
288
+ if (adapter.selection) {
289
+ record('editor.selection.get', 'operation', () => adapter.selection?.get(), Array.isArray);
290
+ if (adapter.selection.resolve) {
291
+ record(
292
+ 'editor.selection.resolve',
293
+ 'operation',
294
+ () => adapter.selection?.resolve?.(id),
295
+ (value) =>
296
+ value === null ||
297
+ (typeof value === 'object' && typeof (value as { id?: unknown }).id === 'string'),
298
+ );
299
+ }
300
+ }
301
+ if (adapter.transforms) {
302
+ let transformApplies = true;
303
+ if (adapter.transforms.dimensions) {
304
+ const dimensions = record(
305
+ 'editor.transforms.dimensions',
306
+ 'operation',
307
+ () => adapter.transforms?.dimensions?.(id),
308
+ (value) => value === null || value === '2d' || value === '3d',
309
+ );
310
+ // `null` is the provider's explicit "this hierarchy row has no
311
+ // transform" answer (composite world/group rows are the canonical
312
+ // case). `get` is non-nullable only for APPLICABLE rows; calling it after
313
+ // `dimensions` declined the subject manufactures a conformance failure
314
+ // against an operation no real consumer performs.
315
+ transformApplies = dimensions === '2d' || dimensions === '3d';
316
+ }
317
+ if (transformApplies) {
318
+ record(
319
+ 'editor.transforms.get',
320
+ 'operation',
321
+ () => adapter.transforms?.get(id),
322
+ (value) => {
323
+ if (!value || typeof value !== 'object') return false;
324
+ const transform = value as Record<string, unknown>;
325
+ return ['position', 'rotation', 'scale'].every((key) => {
326
+ const part = transform[key];
327
+ return Array.isArray(part) && part.every(Number.isFinite);
328
+ });
329
+ },
330
+ );
331
+ if (adapter.transforms.editability) {
332
+ record(
333
+ 'editor.transforms.editability',
334
+ 'operation',
335
+ () => adapter.transforms?.editability?.(id, 'position'),
336
+ (value) =>
337
+ value !== null &&
338
+ typeof value === 'object' &&
339
+ typeof (value as { writable?: unknown }).writable === 'boolean',
340
+ );
341
+ }
342
+ }
343
+ }
344
+ if (adapter.inspector) {
345
+ const properties = record(
346
+ 'editor.inspector.properties',
347
+ 'operation',
348
+ () => adapter.inspector?.properties(id),
349
+ Array.isArray,
350
+ );
351
+ if (Array.isArray(properties)) {
352
+ for (const property of properties) {
353
+ if (property && typeof property === 'object' && typeof property.path === 'string') {
354
+ record('editor.inspector.get', 'operation', () =>
355
+ adapter.inspector?.get(id, property.path),
356
+ );
357
+ if (adapter.inspector.editability) {
358
+ record(
359
+ 'editor.inspector.editability',
360
+ 'operation',
361
+ () => adapter.inspector?.editability?.(id, property.path),
362
+ (value) =>
363
+ value !== null &&
364
+ typeof value === 'object' &&
365
+ typeof (value as { writable?: unknown }).writable === 'boolean',
366
+ );
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+ }
373
+
374
+ function probeIdentityProviders(
375
+ adapter: AuthoringAdapter,
376
+ id: string,
377
+ record: ReadRecorder,
378
+ passParent: (seam: string) => void,
379
+ ): void {
380
+ if (adapter.assetSubject) {
381
+ record('editor.assetSubject.get', 'operation', () => adapter.assetSubject?.get(id));
382
+ if (adapter.assetSubject.entries) {
383
+ record(
384
+ 'editor.assetSubject.entries',
385
+ 'operation',
386
+ () => adapter.assetSubject?.entries?.(),
387
+ Array.isArray,
388
+ );
389
+ }
390
+ passParent('editor.assetSubject');
391
+ }
392
+ if (adapter.related) {
393
+ record('editor.related.links', 'operation', () => adapter.related?.links(id), Array.isArray);
394
+ passParent('editor.related');
395
+ }
396
+ if (adapter.instances) {
397
+ record('editor.instances.describe', 'operation', () => adapter.instances?.describe(id));
398
+ }
399
+ if (adapter.truth) {
400
+ record(
401
+ 'editor.truth.resolve',
402
+ 'operation',
403
+ () => adapter.truth?.resolve(id, ''),
404
+ (value) => value !== null && typeof value === 'object',
405
+ );
406
+ passParent('editor.truth');
407
+ }
408
+ }
409
+
410
+ function probeViewportProviders(
411
+ adapter: AuthoringAdapter,
412
+ id: string,
413
+ record: ReadRecorder,
414
+ passParent: (seam: string) => void,
415
+ ): void {
416
+ if (adapter.pickable) {
417
+ record(
418
+ 'editor.pickable.pick',
419
+ 'operation',
420
+ () => adapter.pickable?.pick(0, 0),
421
+ (value) => value === null || typeof value === 'string',
422
+ );
423
+ if (adapter.pickable.candidates) {
424
+ record(
425
+ 'editor.pickable.candidates',
426
+ 'operation',
427
+ () => adapter.pickable?.candidates?.(0, 0),
428
+ Array.isArray,
429
+ );
430
+ }
431
+ passParent('editor.pickable');
432
+ }
433
+ if (adapter.rects) {
434
+ record(
435
+ 'editor.rects.rect',
436
+ 'operation',
437
+ () => adapter.rects?.rect(id),
438
+ (value) => {
439
+ if (value === null) return true;
440
+ if (!value || typeof value !== 'object') return false;
441
+ const rect = value as Record<string, unknown>;
442
+ return ['x', 'y', 'width', 'height'].every((key) => Number.isFinite(rect[key]));
443
+ },
444
+ );
445
+ if (adapter.rects.contextRects) {
446
+ record('editor.rects.contextRects', 'operation', () => adapter.rects?.contextRects?.(id));
447
+ }
448
+ if (adapter.rects.emptyContainers) {
449
+ record(
450
+ 'editor.rects.emptyContainers',
451
+ 'operation',
452
+ () => adapter.rects?.emptyContainers?.(),
453
+ Array.isArray,
454
+ );
455
+ }
456
+ passParent('editor.rects');
457
+ }
458
+ if (adapter.colorSample) {
459
+ record(
460
+ 'editor.colorSample.backgroundChainAt',
461
+ 'operation',
462
+ () => adapter.colorSample?.backgroundChainAt(0, 0),
463
+ (value) => value === null || Array.isArray(value),
464
+ );
465
+ passParent('editor.colorSample');
466
+ }
467
+ if (adapter.spatialHandles) {
468
+ record(
469
+ 'editor.spatialHandles.layers',
470
+ 'operation',
471
+ () => adapter.spatialHandles?.layers(id),
472
+ Array.isArray,
473
+ );
474
+ }
475
+ }
476
+
477
+ function probeDocumentProviders(adapter: AuthoringAdapter, id: string, record: ReadRecorder): void {
478
+ if (adapter.persistence) {
479
+ record(
480
+ 'editor.persistence.isDirty',
481
+ 'operation',
482
+ () => adapter.persistence?.isDirty(),
483
+ (value) => typeof value === 'boolean',
484
+ );
485
+ if (adapter.persistence.lastError) {
486
+ record(
487
+ 'editor.persistence.lastError',
488
+ 'operation',
489
+ () => adapter.persistence?.lastError?.(),
490
+ (value) => value === null || typeof value === 'string',
491
+ );
492
+ }
493
+ }
494
+ if (adapter.text) record('editor.text.get', 'operation', () => adapter.text?.get(id));
495
+ if (adapter.stories) {
496
+ record(
497
+ 'editor.stories.storiesFor',
498
+ 'operation',
499
+ () => adapter.stories?.storiesFor(id),
500
+ Array.isArray,
501
+ );
502
+ record(
503
+ 'editor.stories.active',
504
+ 'operation',
505
+ () => adapter.stories?.active(id),
506
+ (value) => value === null || typeof value === 'string',
507
+ );
508
+ }
509
+ if (adapter.assetDrop) {
510
+ record(
511
+ 'editor.assetDrop.accepts',
512
+ 'operation',
513
+ () => adapter.assetDrop?.accepts(id, '__vgai_read_probe__'),
514
+ (value) => typeof value === 'boolean',
515
+ );
516
+ }
517
+ }
518
+
519
+ export function probeAuthoringReads(options: {
520
+ readonly adapter: AuthoringAdapter;
521
+ readonly subject: string;
522
+ readonly epoch: string;
523
+ }): readonly SeamEvidenceReceipt[] {
524
+ const { adapter, subject, epoch } = options;
525
+ const receipts: SeamEvidenceReceipt[] = [];
526
+ const record = (
527
+ seam: string,
528
+ stage: SeamProofStage,
529
+ run: () => unknown,
530
+ valid: (value: unknown) => boolean = () => true,
531
+ ): unknown => {
532
+ try {
533
+ const value = run();
534
+ const pass = valid(value);
535
+ receipts.push({
536
+ seam,
537
+ subject,
538
+ epoch,
539
+ stage,
540
+ outcome: pass ? 'pass' : 'fail',
541
+ source: 'conformance-probe',
542
+ detail: pass
543
+ ? `${seam} returned a value accepted by its consumer-facing shape`
544
+ : `${seam} returned a value outside its consumer-facing shape`,
545
+ });
546
+ return value;
547
+ } catch (error) {
548
+ receipts.push({
549
+ seam,
550
+ subject,
551
+ epoch,
552
+ stage,
553
+ outcome: 'fail',
554
+ source: 'conformance-probe',
555
+ detail: `${seam} threw while read: ${error instanceof Error ? error.message : String(error)}`,
556
+ });
557
+ return undefined;
558
+ }
559
+ };
560
+ const passParent = (seam: string): void => {
561
+ receipts.push({
562
+ seam,
563
+ subject,
564
+ epoch,
565
+ stage: 'operation',
566
+ outcome: 'pass',
567
+ source: 'conformance-probe',
568
+ detail: `${seam} served a real read against the mounted projection`,
569
+ });
570
+ };
571
+
572
+ const roots = hierarchyReceipts({ adapter, subject, epoch, record, receipts });
573
+
574
+ const id = roots[0]?.id;
575
+ if (!id) return receipts;
576
+
577
+ probeCoreProviders(adapter, id, record);
578
+ probeIdentityProviders(adapter, id, record, passParent);
579
+ probeViewportProviders(adapter, id, record, passParent);
580
+ probeDocumentProviders(adapter, id, record);
581
+
582
+ return receipts;
583
+ }