@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,232 @@
1
+ /**
2
+ * The DATA-WRITER seam — the half of ingest persistence for a game whose
3
+ * authorable truth is not source.
4
+ *
5
+ * WHY THIS EXISTS. Creation-site write-back can only reach a value that a
6
+ * `new` expression spells in the game's own code. A level-based game's placed
7
+ * cargo does not: its enemies, pickups and objectives are binary records
8
+ * inside a level file, parsed at load, and NO source literal mentions any of
9
+ * them. An authored move of one is therefore an edit to the GAME'S OWN DATA —
10
+ * the doctrine's third destination, not a fourth kind of overlay.
11
+ *
12
+ * WHY THE PLAN IS COMPUTED IN THE BROWSER, where every source plan is computed
13
+ * on the server. Because a data write needs the RUNNING GAME, and the server
14
+ * has no game. A level writer typically cannot place an object without knowing
15
+ * which cell of the loaded level it landed in, and the only correct answer
16
+ * comes from the game's own spatial lookup over the level it has parsed; a
17
+ * server-side planner could
18
+ * only take a client's word for that number, which would make "the server plans
19
+ * against the real bytes" true in form and empty in substance. So the browser
20
+ * reads the file's bytes through `/__ingest-source/read` (which already speaks
21
+ * base64 for binary), the GAME'S OWN writer produces the next bytes, and
22
+ * `/__ingest-source/apply` — the one guarded, checksum-matched, lock-recording
23
+ * door — writes them. The server keeps every property it had: containment, the
24
+ * `ifMatchSha` guard, and the vendored-lock recorder running INSIDE the apply
25
+ * route, so a data edit to a vendored game still moves its lock in the same
26
+ * operation.
27
+ *
28
+ * WHAT THE HOST KNOWS ABOUT ANY PARTICULAR GAME: nothing. The module is named
29
+ * by the game's own manifest (`ingest.dataWriter`) and is validated against the
30
+ * two exports below. There is no per-game branch here and there must never be
31
+ * one.
32
+ *
33
+ * RESOURCE OWNERSHIP (brief-rule 4). The registration below is a module-level
34
+ * slot owned by the ingest THREE mount (`ingest-root-adapter.ts`), which sets it
35
+ * for every ingest mount — including to `null` for a game that declares no
36
+ * writer, which is what tears the previous mount's registration down. Nothing
37
+ * else writes it except {@link resetIngestDataWriterForTest}. It is scoped the
38
+ * same way ingest ownership is: to the opened project, whose adapters are
39
+ * rebuilt on a project switch.
40
+ */
41
+
42
+ import type { NodeCreationSite } from '@volter/editor-project/adapter';
43
+ import type { ChannelValue } from '@volter/editor-core/creation-site-edit';
44
+
45
+ /**
46
+ * The one `userData` key that anchors a live object to a record in its game's
47
+ * own data file.
48
+ *
49
+ * The GAME declares it — a recorded patch or a contract shim stamps it where
50
+ * the game itself builds the object from record `i`, which is the only place
51
+ * the rendered object and its record are both in hand. The host never infers
52
+ * one, never derives one from tree order, and never invents one for an object
53
+ * that carries none: that object is unanchored, with its reason, and its edits
54
+ * stay live-only (the anti-shim rule).
55
+ */
56
+ export const DATA_RECORD_KEY = 'vgaiRecordIndex';
57
+
58
+ /** The record index a live object declares, or `null` when it declares none. */
59
+ export function dataRecordIndexOf(object: unknown): number | null {
60
+ const userData = (object as { userData?: unknown } | null)?.userData;
61
+ if (userData === null || typeof userData !== 'object') return null;
62
+ const raw = (userData as Record<string, unknown>)[DATA_RECORD_KEY];
63
+ return typeof raw === 'number' && Number.isInteger(raw) && raw >= 0 ? raw : null;
64
+ }
65
+
66
+ /** One property edit, offered to a game's own data writer. */
67
+ export interface DataEditRequest {
68
+ /** The index of the record this object is anchored to, in the writer's file. */
69
+ readonly record: number;
70
+ /** An editor property path (a `CREATION_SITE_CHANNELS` key). */
71
+ readonly property: string;
72
+ readonly baseline: ChannelValue;
73
+ readonly next: ChannelValue;
74
+ }
75
+
76
+ export type DataEditPlan =
77
+ | { readonly changed: true; readonly bytes: Uint8Array }
78
+ | { readonly changed: false; readonly reason: string };
79
+
80
+ /** The contract a `ingest.dataWriter` module must satisfy, and all of it. */
81
+ export interface DataWriter {
82
+ /** Project-relative path of the ONE file this writer edits. */
83
+ readonly dataFile: string;
84
+ /**
85
+ * OPTIONAL, and the pre-edit half of {@link planDataEdit}: can this format
86
+ * hold `property` at all, and if not, why not? Synchronous and cheap, because
87
+ * it answers the sentence a surface shows BEFORE anyone drags anything.
88
+ *
89
+ * Without it, a property the writer will always refuse still reads as
90
+ * "Persisting to …" until the user tries — which is the exact disagreement
91
+ * `gate`/`describe` were unified to end, one layer down. A writer that
92
+ * implements it must answer from the SAME table `planDataEdit` reads, so the
93
+ * promise and the refusal cannot drift apart.
94
+ */
95
+ canPlace?(property: string): true | string;
96
+ planDataEdit(bytes: Uint8Array, request: DataEditRequest): Promise<DataEditPlan> | DataEditPlan;
97
+ }
98
+
99
+ /** Where the declared writer got to. `absent` is not a failure — most games
100
+ * have no data file to write and say so by declaring nothing. */
101
+ export type DataWriterState =
102
+ | { readonly state: 'absent' }
103
+ | { readonly state: 'loading' }
104
+ | { readonly state: 'ready'; readonly writer: DataWriter }
105
+ | { readonly state: 'failed'; readonly reason: string };
106
+
107
+ let current: DataWriterState = { state: 'absent' };
108
+ let token = 0;
109
+
110
+ /**
111
+ * Validate a loaded module against the contract, by NAME. A module that is
112
+ * nearly right is refused rather than half-driven: a `planDataEdit` that is not
113
+ * a function, or a `dataFile` that is not a string, cannot produce bytes anyone
114
+ * should write to somebody else's game.
115
+ */
116
+ export function readDataWriterModule(module: unknown, specifier: string): DataWriter {
117
+ const m = module as Partial<DataWriter> | null;
118
+ if (m === null || typeof m !== 'object') {
119
+ throw new Error(`the data writer "${specifier}" did not export a module object`);
120
+ }
121
+ if (typeof m.dataFile !== 'string' || m.dataFile.length === 0) {
122
+ throw new Error(
123
+ `the data writer "${specifier}" exports no \`dataFile\` string — a writer must name the ` +
124
+ 'one project-relative file it edits',
125
+ );
126
+ }
127
+ if (typeof m.planDataEdit !== 'function') {
128
+ throw new Error(
129
+ `the data writer "${specifier}" exports no \`planDataEdit\` function — that is the whole ` +
130
+ 'contract: (bytes, request) => {changed, bytes} | {changed, reason}',
131
+ );
132
+ }
133
+ return {
134
+ dataFile: m.dataFile,
135
+ ...(typeof m.canPlace === 'function' ? { canPlace: m.canPlace.bind(m) } : {}),
136
+ planDataEdit: m.planDataEdit.bind(m),
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Register (or clear) the data writer this mount's game declares.
142
+ *
143
+ * The load is started immediately and its result lands asynchronously; until it
144
+ * does, the state is `loading` and every surface says so rather than pretending
145
+ * an object is unwritable. A load that arrives after a LATER registration is
146
+ * dropped on its stale token — a fast project switch must not resurrect the
147
+ * previous game's writer.
148
+ */
149
+ export function setIngestDataWriter(
150
+ declaration: { readonly specifier: string; readonly load: () => Promise<unknown> } | null,
151
+ ): void {
152
+ const mine = ++token;
153
+ if (declaration === null) {
154
+ current = { state: 'absent' };
155
+ return;
156
+ }
157
+ current = { state: 'loading' };
158
+ void declaration
159
+ .load()
160
+ .then((module) => {
161
+ if (token !== mine) return;
162
+ current = { state: 'ready', writer: readDataWriterModule(module, declaration.specifier) };
163
+ })
164
+ .catch((error) => {
165
+ if (token !== mine) return;
166
+ current = {
167
+ state: 'failed',
168
+ reason: `this game's declared data writer (${declaration.specifier}) could not be loaded: ${
169
+ error instanceof Error ? error.message : String(error)
170
+ }`,
171
+ };
172
+ });
173
+ }
174
+
175
+ export function ingestDataWriterNow(): DataWriterState {
176
+ return current;
177
+ }
178
+
179
+ /**
180
+ * The reason this game's data format cannot hold `property` at all, or `null`
181
+ * when it can (or when the writer declines to say, which is the honest floor:
182
+ * an unanswered question is not a refusal).
183
+ */
184
+ export function dataPlacementRefusal(property: string): string | null {
185
+ const state = current;
186
+ if (state.state !== 'ready' || !state.writer.canPlace) return null;
187
+ const verdict = state.writer.canPlace(property);
188
+ return verdict === true ? null : verdict;
189
+ }
190
+
191
+ /** Test-only: put the slot back to its boot state. */
192
+ export function resetIngestDataWriterForTest(): void {
193
+ token++;
194
+ current = { state: 'absent' };
195
+ }
196
+
197
+ /**
198
+ * The anchor for an object that declares record `record`.
199
+ *
200
+ * An object that names a record but whose game has no reachable writer is
201
+ * UNANCHORED WITH A REASON — never quietly re-anchored to the source line that
202
+ * happened to construct its mesh. That fallback would be the shim this doctrine
203
+ * forbids: it would report a `file:line` an edit can never be written at, and
204
+ * the refusal the user finally got would name the wrong obstacle.
205
+ */
206
+ export function dataRecordAnchor(record: number): NodeCreationSite {
207
+ const state = current;
208
+ switch (state.state) {
209
+ case 'ready':
210
+ return {
211
+ anchored: true,
212
+ kind: 'data',
213
+ file: state.writer.dataFile,
214
+ record,
215
+ display: `${state.writer.dataFile} record ${record}`,
216
+ };
217
+ case 'loading':
218
+ return {
219
+ anchored: false,
220
+ reason: `this object is record ${record} of its game's own level data; the game's data writer is still loading`,
221
+ };
222
+ case 'failed':
223
+ return { anchored: false, reason: `this object is record ${record}, but ${state.reason}` };
224
+ default:
225
+ return {
226
+ anchored: false,
227
+ reason:
228
+ `this object is record ${record} of its game's own level data, and this game declares ` +
229
+ 'no data writer (`ingest.dataWriter`) that could write that record back',
230
+ };
231
+ }
232
+ }