@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.
- package/LICENSE +661 -0
- package/NOTICE +23 -0
- package/contributions/asset-budget-asset.menu.ts +26 -0
- package/contributions/asset-budget.action.ts +18 -0
- package/contributions/asset-budget.document.tsx +21 -0
- package/contributions/asset-budget.menu.ts +22 -0
- package/contributions/audio-unlock.service.ts +17 -0
- package/contributions/audio.utility.tsx +15 -0
- package/contributions/autoplay.service.ts +48 -0
- package/contributions/bridge.command.ts +172 -0
- package/contributions/build-profiles.document.tsx +21 -0
- package/contributions/build-progress.status.tsx +45 -0
- package/contributions/build.action.ts +26 -0
- package/contributions/build.command.ts +27 -0
- package/contributions/build.header.tsx +37 -0
- package/contributions/build.menu.ts +31 -0
- package/contributions/build.service.ts +31 -0
- package/contributions/connection.status.tsx +43 -0
- package/contributions/coverage.service.ts +118 -0
- package/contributions/edit-mode-audio.service.ts +23 -0
- package/contributions/edit-mode-networking.service.ts +30 -0
- package/contributions/game-document.service.ts +25 -0
- package/contributions/game-eval.command.ts +210 -0
- package/contributions/game.layout.ts +19 -0
- package/contributions/gameplay.command.ts +255 -0
- package/contributions/generation.service.ts +96 -0
- package/contributions/generations.status.tsx +53 -0
- package/contributions/ingest.service.ts +80 -0
- package/contributions/instances.command.ts +75 -0
- package/contributions/navmesh.menu.ts +39 -0
- package/contributions/navmesh.service.ts +19 -0
- package/contributions/network.utility.tsx +17 -0
- package/contributions/play.command.ts +355 -0
- package/contributions/profiler.action.ts +16 -0
- package/contributions/profiler.menu.ts +22 -0
- package/contributions/profiler.utility.tsx +17 -0
- package/contributions/react/component-board.service.ts +21 -0
- package/contributions/react/design-time-mount.service.ts +60 -0
- package/contributions/react/pasteboard.action.ts +41 -0
- package/contributions/react/react-inspector.service.ts +85 -0
- package/contributions/react/story-documents.service.ts +44 -0
- package/contributions/scene-document.service.ts +32 -0
- package/contributions/state-watch.action.ts +17 -0
- package/contributions/state-watch.menu.ts +23 -0
- package/contributions/state-watch.utility.tsx +20 -0
- package/contributions/team-playtest.service.ts +124 -0
- package/contributions/three/camera-runtime.inspector.tsx +34 -0
- package/contributions/three/component-board.service.ts +24 -0
- package/contributions/three/component-verbs.command.ts +110 -0
- package/contributions/three/component-verbs.service.ts +92 -0
- package/contributions/three/constraints.inspector.tsx +34 -0
- package/contributions/three/model-asset-sections.service.ts +26 -0
- package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
- package/contributions/three/story-documents.service.ts +31 -0
- package/contributions/three/three-authoring.service.ts +66 -0
- package/contributions/transport.header.tsx +19 -0
- package/contributions/xstate-behavior.action.ts +42 -0
- package/contributions/xstate-behavior.document.tsx +73 -0
- package/contributions/xstate-behavior.inspector.tsx +28 -0
- package/contributions/xstate-behavior.menu.ts +23 -0
- package/package.json +144 -0
- package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
- package/src/asset-budget/asset-budget-model.ts +799 -0
- package/src/asset-budget/basis-encoder.ts +165 -0
- package/src/asset-budget/gltf-io.ts +154 -0
- package/src/asset-budget/gltf-optimize.ts +384 -0
- package/src/asset-budget/image-dims.ts +78 -0
- package/src/asset-budget/optimize-apply.ts +221 -0
- package/src/audio/AudioDebuggerPanel.tsx +457 -0
- package/src/audio/audio-debugger-model.ts +87 -0
- package/src/bridge/call.ts +60 -0
- package/src/bridge/dispatch.ts +459 -0
- package/src/bridge/live-frames.ts +25 -0
- package/src/bridge/screenshot.ts +316 -0
- package/src/build/BuildProfilesPanel.tsx +476 -0
- package/src/build/build-session.ts +247 -0
- package/src/build/format-bytes.ts +14 -0
- package/src/command-results.ts +36 -0
- package/src/coverage/live-authoring-surface.ts +30 -0
- package/src/coverage/live-project-verbs.ts +162 -0
- package/src/coverage/native-system-coverage.ts +143 -0
- package/src/coverage/root-coverage.ts +79 -0
- package/src/coverage/session-coverage.ts +193 -0
- package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
- package/src/edit-mode/edit-mode-audio.ts +56 -0
- package/src/edit-mode/edit-mode-networking.ts +112 -0
- package/src/game-document/DevicePresetPicker.tsx +84 -0
- package/src/game-document/GameCaptureFrameButton.tsx +45 -0
- package/src/game-document/GameDocument.tsx +215 -0
- package/src/game-document/GamePanel.tsx +662 -0
- package/src/game-document/InstanceInspectorPicker.tsx +183 -0
- package/src/game-document/crowd-debug.ts +183 -0
- package/src/game-document/device-preview.ts +336 -0
- package/src/game-document/game-view-store.ts +107 -0
- package/src/game-document/physics-debug.ts +187 -0
- package/src/generation/GenerationActivity.tsx +421 -0
- package/src/generation/GenerationGallery.css +231 -0
- package/src/generation/generation-documents.tsx +338 -0
- package/src/generation/generation-jobs.ts +128 -0
- package/src/generation/generation-presentation.ts +257 -0
- package/src/host/adapter-reach.ts +445 -0
- package/src/host/adapter-runtime-bindings.ts +303 -0
- package/src/host/after-paint.ts +112 -0
- package/src/host/api/configurations.ts +66 -0
- package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
- package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
- package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
- package/src/host/authoring/contract-scenes-stories.ts +204 -0
- package/src/host/authoring/creation-site-related.ts +55 -0
- package/src/host/authoring/ephemeral-persistence.ts +29 -0
- package/src/host/authoring/gesture-persist.ts +84 -0
- package/src/host/authoring/ingest-data-writer.ts +232 -0
- package/src/host/authoring/ingest-source-persistence.ts +826 -0
- package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
- package/src/host/authoring/mounted-authoring.ts +41 -0
- package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
- package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
- package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
- package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
- package/src/host/authoring/pixi-isolation-assets.ts +25 -0
- package/src/host/authoring/pixi-live-write-target.ts +979 -0
- package/src/host/authoring/pixi-source-identity.ts +141 -0
- package/src/host/authoring/pixi-still-presentation.ts +71 -0
- package/src/host/authoring/pixi-structure-history.ts +237 -0
- package/src/host/authoring/pixi-transform-channels.ts +205 -0
- package/src/host/authoring/selection-remount-handoff.ts +23 -0
- package/src/host/authoring/source-persistence-backend.ts +373 -0
- package/src/host/authoring/source-refresh-revisions.ts +81 -0
- package/src/host/authoring/struct-write-pipe.ts +143 -0
- package/src/host/auto-frame-window.ts +89 -0
- package/src/host/binding-resolver.ts +393 -0
- package/src/host/browser-transpile.ts +631 -0
- package/src/host/canvas-entry-runtime.ts +95 -0
- package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
- package/src/host/components/HeaderTelemetry.tsx +341 -0
- package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
- package/src/host/components/ResolutionPicker.tsx +89 -0
- package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
- package/src/host/components/frame-debugger-model.ts +579 -0
- package/src/host/components/header-telemetry-model.ts +74 -0
- package/src/host/components/scene-document.tsx +447 -0
- package/src/host/components/utility-view-state.ts +87 -0
- package/src/host/components/world-root-stage-binding.tsx +133 -0
- package/src/host/components/world-root-stage.ts +1046 -0
- package/src/host/coverage/authoring-read-probe.ts +583 -0
- package/src/host/coverage/capability-coverage.ts +1587 -0
- package/src/host/coverage/coverage-accounting.ts +261 -0
- package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
- package/src/host/coverage/project-verb-coverage.ts +148 -0
- package/src/host/coverage/system-adapter-coverage.ts +373 -0
- package/src/host/design-system-stories/StoryLayout.tsx +104 -0
- package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
- package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
- package/src/host/document-preview-three.ts +110 -0
- package/src/host/entry-adjudication.ts +89 -0
- package/src/host/game-location-guard.ts +150 -0
- package/src/host/game-module-access.ts +196 -0
- package/src/host/game-realm-page.ts +358 -0
- package/src/host/game-realm-reclaim.ts +55 -0
- package/src/host/game-realm-storage.ts +104 -0
- package/src/host/gameplay-export.ts +288 -0
- package/src/host/gameplay-recording.ts +717 -0
- package/src/host/gated-globals.ts +1511 -0
- package/src/host/history/json-history-resource.ts +254 -0
- package/src/host/ingest/registry.ts +261 -0
- package/src/host/instance-extract-actions.ts +120 -0
- package/src/host/instance-fork-actions.ts +120 -0
- package/src/host/play-control-hook.ts +26 -0
- package/src/host/playwright-shim.ts +479 -0
- package/src/host/projection/dom.ts +295 -0
- package/src/host/projection/pixi.ts +288 -0
- package/src/host/r3f-entry-runtime.ts +77 -0
- package/src/host/react-mount-runtime.ts +162 -0
- package/src/host/realm-services.ts +148 -0
- package/src/host/recording-preview.ts +106 -0
- package/src/host/roots/module-root.ts +203 -0
- package/src/host/roots/react-root.ts +181 -0
- package/src/host/same-realm-loop-gate.ts +544 -0
- package/src/host/scene-view-drawability.ts +69 -0
- package/src/host/sdk/tools.ts +31 -0
- package/src/host/served-bundle-runtime-modules.ts +331 -0
- package/src/host/server-log-bridge.ts +60 -0
- package/src/host/staged-projects.ts +24 -0
- package/src/host/stories/mounted-story-viewport-source.ts +64 -0
- package/src/host/stories/story-arg-descriptors.ts +50 -0
- package/src/host/stories/story-media-presence.ts +152 -0
- package/src/host/surface-content.ts +87 -0
- package/src/host/take-named-export.ts +23 -0
- package/src/host/three-ingest-runtime.ts +76 -0
- package/src/host/types-fastnoise-lite.d.ts +7 -0
- package/src/host/types-mikktspace.d.ts +20 -0
- package/src/host/types-troika-three-text.d.ts +7 -0
- package/src/host/use-active-performance-source.ts +53 -0
- package/src/host/viewport-pose-memory.ts +48 -0
- package/src/host/viewport-root-presentation.ts +40 -0
- package/src/ingest/active-ingest.ts +251 -0
- package/src/ingest/active-scene-navigation.ts +43 -0
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
- package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
- package/src/ingest/capture-wait-report.ts +122 -0
- package/src/ingest/deferred-ingest-play.ts +216 -0
- package/src/ingest/deferred-ingest-session.ts +23 -0
- package/src/ingest/discovery-public-ingest.ts +242 -0
- package/src/ingest/dom-stub-mark.ts +7 -0
- package/src/ingest/entry-load.ts +56 -0
- package/src/ingest/game-contract-realm.ts +34 -0
- package/src/ingest/game-pointer-lock.ts +89 -0
- package/src/ingest/held-scene-repaint.ts +73 -0
- package/src/ingest/host-surface-box.ts +174 -0
- package/src/ingest/ingest-boot-viewport.ts +54 -0
- package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
- package/src/ingest/ingest-canvas-scene.ts +50 -0
- package/src/ingest/ingest-evidence-hook.ts +131 -0
- package/src/ingest/ingest-frame-snapshot.ts +183 -0
- package/src/ingest/ingest-play-commands.ts +134 -0
- package/src/ingest/ingest-play-control.ts +299 -0
- package/src/ingest/ingest-render-debug.ts +320 -0
- package/src/ingest/ingest-siblings.ts +487 -0
- package/src/ingest/ingest-status.ts +62 -0
- package/src/ingest/live-ingest-facet.ts +46 -0
- package/src/ingest/module-mode.ts +229 -0
- package/src/ingest/mount-canvas-ingest-root.ts +899 -0
- package/src/ingest/mount-coverage.ts +296 -0
- package/src/ingest/mount-dom-ingest-root.ts +282 -0
- package/src/ingest/mount-ingest-root.ts +744 -0
- package/src/ingest/mount-three-ingest-root.ts +366 -0
- package/src/ingest/resolve-canvas.ts +47 -0
- package/src/ingest/resolve-three.ts +123 -0
- package/src/ingest/served-bundle.ts +109 -0
- package/src/ingest/served-html-boot.ts +292 -0
- package/src/ingest/surface-canvas.ts +84 -0
- package/src/ingest/surface-dom.ts +84 -0
- package/src/ingest/surface-three.ts +128 -0
- package/src/ingest/types.ts +76 -0
- package/src/ingest/unmount-ingest-root.ts +224 -0
- package/src/navmesh/navmesh-actions.ts +27 -0
- package/src/navmesh/navmesh-handler.ts +237 -0
- package/src/navmesh/navmesh-workflow-store.ts +78 -0
- package/src/network/NetworkInspectorPanel.tsx +644 -0
- package/src/network/network-inspector-model.ts +225 -0
- package/src/play/play-boot-stall.ts +118 -0
- package/src/play/play-log-events.ts +26 -0
- package/src/play/play-mode.ts +2553 -0
- package/src/play/play-recording.ts +335 -0
- package/src/play/react-play-live-authoring.ts +165 -0
- package/src/play-bar/PlayBar.tsx +488 -0
- package/src/play-bar/PlayerCountPicker.tsx +100 -0
- package/src/profiler/FrameDebuggerPanel.tsx +458 -0
- package/src/profiler/PerformancePanel.tsx +1068 -0
- package/src/profiler/ProfilerPanel.tsx +53 -0
- package/src/profiler/frame-debugger-store.ts +97 -0
- package/src/profiler/main-thread-busy.ts +90 -0
- package/src/react/design-time-react-mount.ts +782 -0
- package/src/react/dom-authoring-adapter.ts +764 -0
- package/src/react/pasteboard-materialize.ts +154 -0
- package/src/react/react-inspector-section.tsx +2496 -0
- package/src/react/react-world-authoring-adapter.ts +3795 -0
- package/src/react/story-documents/story-args-section.ts +19 -0
- package/src/react/story-documents/story-documents.tsx +1380 -0
- package/src/react/story-paint-bounds.ts +94 -0
- package/src/react/ui-board-document.tsx +101 -0
- package/src/react/ui-board-title.ts +9 -0
- package/src/react/ui-component-board.ts +72 -0
- package/src/services/audio-pose-guard.ts +81 -0
- package/src/services/game-audio-unlock.ts +48 -0
- package/src/state-watch/StateWatchPanel.tsx +535 -0
- package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
- package/src/three/authoring/constraint-inspector-section.tsx +189 -0
- package/src/three/authoring/design-time-renderer.ts +189 -0
- package/src/three/authoring/model-asset-inspector-section.css +41 -0
- package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
- package/src/three/authoring/oid-source-persistence.ts +557 -0
- package/src/three/authoring/r3f-design-session.ts +1174 -0
- package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
- package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
- package/src/three/authoring/spatial-audio-handles.ts +215 -0
- package/src/three/authoring/spatial-collider-handles.ts +229 -0
- package/src/three/authoring/spatial-joint-handles.ts +114 -0
- package/src/three/authoring/spatial-light-handles.ts +178 -0
- package/src/three/authoring/spatial-lod-handles.ts +93 -0
- package/src/three/authoring/spatial-particle-handles.ts +368 -0
- package/src/three/authoring/three-authoring-adapter.ts +1953 -0
- package/src/three/authoring/three-scene-identity.ts +19 -0
- package/src/three/authoring/three-spatial-handles.ts +161 -0
- package/src/three/authoring/typed-three-inspector.ts +528 -0
- package/src/three/component-verbs/extract-menu.ts +71 -0
- package/src/three/component-verbs/fork-menu.ts +78 -0
- package/src/three/component-verbs/internals-menu.ts +91 -0
- package/src/three/story-documents/three-story-documents.tsx +607 -0
- package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
- package/src/three/three-board/board-framing.ts +412 -0
- package/src/three/three-board/board-layout.ts +401 -0
- package/src/three/three-board/board-scene.ts +901 -0
- package/src/three/three-board/three-component-board.ts +62 -0
- package/src/xstate/XStateBehaviorSection.tsx +130 -0
- package/src/xstate/XStateMachineInspector.tsx +566 -0
- package/src/xstate/character-animation-machine.fixture.ts +74 -0
- package/src/xstate/live-behaviors.ts +106 -0
- package/src/xstate/use-live-actor-state.ts +44 -0
- package/src/xstate/xstate-graph.ts +235 -0
- 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
|
+
}
|