@volter/editor-game 0.5.66 → 0.5.67
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/contributions/audio-unlock.service.ts +2 -2
- package/contributions/autoplay.service.ts +1 -1
- package/contributions/bridge.command.ts +3 -3
- package/contributions/canvas/component-board.service.ts +16 -0
- package/contributions/canvas/design-time-mount.service.ts +45 -0
- package/contributions/canvas-story-capture.service.ts +12 -0
- package/contributions/edit-mode-audio.service.ts +2 -2
- package/contributions/edit-mode-networking.service.ts +1 -1
- package/contributions/gameplay.command.ts +4 -4
- package/contributions/generation.service.ts +1 -1
- package/contributions/godot.style.ts +26 -6
- package/contributions/godot.view.ts +6 -0
- package/contributions/ingest.service.ts +2 -2
- package/contributions/instances.command.ts +1 -1
- package/contributions/navmesh.menu.ts +1 -1
- package/contributions/network-observer.service.ts +14 -0
- package/contributions/play.command.ts +10 -4
- package/contributions/react/component-board.service.ts +1 -1
- package/contributions/react/design-time-mount.service.ts +1 -1
- package/contributions/scene-document.service.ts +3 -3
- package/contributions/state-watch.menu.ts +1 -1
- package/contributions/state-watch.utility.tsx +1 -1
- package/contributions/team-playtest.service.ts +4 -4
- package/contributions/three/component-board.service.ts +1 -1
- package/contributions/three/component-verbs.command.ts +6 -6
- package/contributions/three/three-authoring.service.ts +8 -5
- package/contributions/three-story-capture.service.ts +12 -0
- package/contributions/unity.style.ts +17 -7
- package/contributions/unity.view.ts +6 -0
- package/contributions/unreal.style.ts +10 -2
- package/contributions/unreal.view.ts +5 -0
- package/package.json +20 -10
- package/src/asset-budget/AssetBudgetPanel.tsx +1 -1
- package/src/asset-budget/asset-budget-model.ts +1 -1
- package/src/audio/AudioDebuggerPanel.tsx +1 -1
- package/src/bridge/dispatch.ts +14 -14
- package/src/bridge/live-frames.ts +1 -1
- package/src/bridge/screenshot.ts +3 -3
- package/src/build/BuildProfilesPanel.tsx +3 -3
- package/src/canvas/canvas-board/CanvasBoardDocument.tsx +676 -0
- package/src/canvas/canvas-board/canvas-board-model.ts +407 -0
- package/src/canvas/canvas-board/canvas-component-board.ts +56 -0
- package/src/canvas/canvas-design-mount.ts +524 -0
- package/src/canvas/design-time-canvas-mount.ts +79 -0
- package/src/coverage/live-authoring-surface.ts +5 -5
- package/src/coverage/live-project-verbs.ts +1 -1
- package/src/coverage/native-system-coverage.ts +5 -5
- package/src/coverage/root-coverage.ts +1 -1
- package/src/coverage/session-coverage.ts +3 -3
- package/src/design-system-stories/ApplicationChrome.stories.tsx +5 -5
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +7 -7
- package/src/edit-mode/edit-mode-audio.ts +4 -4
- package/src/edit-mode/edit-mode-networking.ts +2 -2
- package/src/game-document/GameCaptureFrameButton.tsx +1 -1
- package/src/game-document/GameDocument.tsx +2 -2
- package/src/game-document/GamePanel.tsx +4 -4
- package/src/game-document/InstanceInspectorPicker.tsx +2 -2
- package/src/game-document/crowd-debug.ts +1 -1
- package/src/game-document/device-preview.ts +10 -11
- package/src/game-document/physics-debug.ts +1 -1
- package/src/generation/GenerationActivity.tsx +3 -3
- package/src/generation/generation-documents.tsx +5 -5
- package/src/generation/generation-jobs.ts +1 -1
- package/src/host/adapter-runtime-bindings.ts +96 -5
- package/src/host/authoring/babylon-authoring-adapter.ts +6 -6
- package/src/host/authoring/gesture-persist.ts +1 -1
- package/src/host/authoring/ingest-data-writer.ts +1 -1
- package/src/host/authoring/ingest-source-persistence.ts +4 -4
- package/src/host/authoring/mounted-authoring.ts +4 -4
- package/src/host/authoring/phaser-live-authoring-adapter.ts +4 -4
- package/src/host/authoring/pixi-authoring-adapter.ts +40 -15
- package/src/host/authoring/pixi-creation-site-write-target.ts +2 -2
- package/src/host/authoring/pixi-live-write-target.ts +4 -4
- package/src/host/authoring/pixi-source-identity.ts +3 -3
- package/src/host/authoring/pixi-source-write-target.ts +1614 -0
- package/src/host/authoring/pixi-still-presentation.ts +1 -1
- package/src/host/authoring/pixi-structure-history.ts +2 -2
- package/src/host/authoring/pixi-transform-channels.ts +16 -14
- package/src/host/authoring/source-persistence-backend.ts +3 -3
- package/src/host/authoring/struct-write-pipe.ts +1 -1
- package/src/host/binding-resolver.ts +8 -9
- package/src/host/browser-transpile.ts +1 -1
- package/src/host/canvas-entry-runtime.ts +58 -47
- package/src/host/canvas-preview-frames.ts +482 -0
- package/src/host/components/CameraAuthoringOverlay.tsx +1 -1
- package/src/host/components/HeaderTelemetry.tsx +4 -4
- package/src/host/components/PixiIsolationSceneContent.tsx +10 -10
- package/src/host/components/ThreeIsolationSceneContent.tsx +3 -3
- package/src/host/components/frame-debugger-model.ts +2 -2
- package/src/host/components/header-telemetry-model.ts +2 -2
- package/src/host/components/scene-document.tsx +14 -14
- package/src/host/components/utility-view-state.ts +1 -1
- package/src/host/components/world-root-stage-binding.tsx +12 -12
- package/src/host/components/world-root-stage.ts +70 -49
- package/src/host/coverage/system-adapter-coverage.ts +3 -4
- package/src/host/design-system-stories/fixtures/editor-runtime.tsx +9 -11
- package/src/host/document-preview-three.ts +1 -1
- package/src/host/entry-adjudication.ts +6 -6
- package/src/host/game-css-scope-transform.ts +4 -0
- package/src/host/game-realm-page.ts +1 -1
- package/src/host/gameplay-export.ts +25 -14
- package/src/host/gameplay-recording.ts +5 -5
- package/src/host/gated-globals.ts +2 -2
- package/src/host/history/json-history-resource.ts +1 -1
- package/src/host/projection/pixi.ts +24 -2
- package/src/host/r3f-entry-runtime.ts +65 -34
- package/src/host/react-mount-runtime.ts +7 -48
- package/src/host/realm-services.ts +1 -1
- package/src/host/roots/canvas-root.tsx +361 -0
- package/src/host/roots/r3f-root.tsx +473 -0
- package/src/host/roots/react-root.ts +9 -43
- package/src/host/served-bundle-runtime-modules.ts +1 -19
- package/src/host/server-log-bridge.ts +2 -2
- package/src/host/stories/mounted-story-viewport-source.ts +1 -1
- package/src/host/stories/pixi-story-model.ts +30 -0
- package/src/host/stories/story-media-captures.ts +46 -0
- package/src/host/stories/story-media-presence.ts +3 -3
- package/src/host/stories/story-pixi-preview.ts +408 -0
- package/src/host/stories/story-three-preview.ts +806 -0
- package/src/host/stories/three-story-captures.ts +35 -0
- package/src/host/story-three-preview-runtime.ts +56 -0
- package/src/host/use-active-performance-source.ts +2 -2
- package/src/host/viewport-pose-memory.ts +1 -1
- package/src/host/viewport-root-presentation.ts +6 -5
- package/src/ingest/active-ingest.ts +1 -1
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +4 -4
- package/src/ingest/authoring/ingest-root-adapter.ts +8 -8
- package/src/ingest/deferred-ingest-play.ts +6 -6
- package/src/ingest/discovery-public-ingest.ts +2 -2
- package/src/ingest/ingest-boot-viewport.ts +2 -2
- package/src/ingest/ingest-canvas-scene-document.tsx +8 -8
- package/src/ingest/ingest-canvas-scene.ts +3 -3
- package/src/ingest/ingest-evidence-hook.ts +1 -1
- package/src/ingest/ingest-frame-snapshot.ts +1 -1
- package/src/ingest/ingest-play-control.ts +1 -1
- package/src/ingest/ingest-render-debug.ts +10 -10
- package/src/ingest/ingest-siblings.ts +13 -25
- package/src/ingest/module-mode.ts +14 -14
- package/src/ingest/mount-canvas-ingest-root.ts +21 -21
- package/src/ingest/mount-coverage.ts +2 -2
- package/src/ingest/mount-dom-ingest-root.ts +11 -11
- package/src/ingest/mount-ingest-root.ts +8 -8
- package/src/ingest/mount-three-ingest-root.ts +8 -8
- package/src/ingest/resolve-canvas.ts +1 -1
- package/src/ingest/served-html-boot.ts +1 -1
- package/src/ingest/surface-canvas.ts +1 -1
- package/src/ingest/unmount-ingest-root.ts +5 -5
- package/src/navmesh/navmesh-handler.ts +24 -16
- package/src/network/NetworkInspectorPanel.tsx +449 -14
- package/src/network/network-inspector-model.ts +14 -2
- package/src/play/play-log-events.ts +1 -1
- package/src/play/play-mode.ts +76 -133
- package/src/play/play-recording.ts +1 -1
- package/src/play/react-play-live-authoring.ts +3 -3
- package/src/play/run-selection.ts +93 -0
- package/src/play-bar/PlayBar.tsx +20 -39
- package/src/profiler/FrameDebuggerPanel.tsx +1 -1
- package/src/profiler/PerformancePanel.tsx +3 -3
- package/src/react/design-time-react-mount.ts +23 -65
- package/src/react/dom-authoring-adapter.ts +9 -9
- package/src/react/react-inspector-section.tsx +5 -5
- package/src/react/react-world-authoring-adapter.ts +11 -11
- package/src/react/story-documents/story-documents.tsx +9 -9
- package/src/react/ui-board-document.tsx +7 -7
- package/src/react/ui-component-board.ts +2 -2
- package/src/runtime/adapter/audio-meter.ts +21 -0
- package/src/runtime/adapter/first-party-audio-system.ts +230 -0
- package/src/runtime/adapter/ingest/contract-debug-adapter.ts +114 -0
- package/src/runtime/adapter/ingest/contract-system-adapters.ts +256 -0
- package/src/runtime/adapter/ingest/merge-debug-adapters.ts +197 -0
- package/src/runtime/adapter/ingest/observation-debug-adapter.ts +162 -0
- package/src/runtime/adapter/ingest/upstream-pin.ts +51 -0
- package/src/runtime/adapter/native-debug-module.ts +498 -0
- package/src/runtime/audio/bus-mixer.ts +161 -0
- package/src/runtime/audio/pose-guard.ts +80 -0
- package/src/runtime/core/frame-pacing.ts +126 -0
- package/src/runtime/core/game-loop.ts +225 -0
- package/src/runtime/core/game-scoped-slot.ts +28 -0
- package/src/runtime/core/seeded-random.ts +162 -0
- package/src/runtime/core/sim-clock.ts +391 -0
- package/src/runtime/core/system-runner.ts +269 -0
- package/src/runtime/core/types.ts +104 -0
- package/src/runtime/create-runtime.ts +1128 -0
- package/src/runtime/debug-bridge.ts +570 -0
- package/src/runtime/debug-registry.ts +899 -0
- package/src/runtime/dev/chrome-trace.ts +153 -0
- package/src/runtime/dev/instruments.ts +403 -0
- package/src/runtime/dev/logger.ts +119 -0
- package/src/runtime/dev/performance-profiler.ts +367 -0
- package/src/runtime/dev/register-render-vitals.ts +276 -0
- package/src/runtime/dev/render-census.ts +354 -0
- package/src/runtime/dev/render-debug-adapter.ts +218 -0
- package/src/runtime/dev/render-memory.ts +226 -0
- package/src/runtime/dev/render-vitals.ts +338 -0
- package/src/runtime/dev/static-batch-advisor.ts +188 -0
- package/src/runtime/dev/webgl-frame-capture.ts +366 -0
- package/src/runtime/dev/webgl-gpu-timer.ts +53 -0
- package/src/runtime/dev-build.ts +47 -0
- package/src/runtime/game.ts +1636 -0
- package/src/runtime/gameplay-rng-trap.ts +135 -0
- package/src/runtime/host-context.ts +64 -0
- package/src/runtime/input-router.ts +169 -0
- package/src/runtime/mount-manifest.ts +480 -0
- package/src/runtime/pixi/authoring.ts +706 -0
- package/src/runtime/pixi/ingest.ts +116 -0
- package/src/runtime/pixi/physics-registry.ts +49 -0
- package/src/runtime/pixi/render-pass-bracket.ts +117 -0
- package/src/runtime/pixi/scene-capture.ts +179 -0
- package/src/runtime/pixi/system-adapters.ts +69 -0
- package/src/runtime/playtest.ts +22 -0
- package/src/runtime/presentation.ts +141 -0
- package/src/runtime/render-control.ts +642 -0
- package/src/runtime/render-seed.ts +77 -0
- package/src/runtime/run-ticks-settled.ts +73 -0
- package/src/runtime/setup/setup-audio.ts +72 -0
- package/src/services/audio-pose-guard.ts +2 -2
- package/src/services/game-audio.ts +152 -0
- package/src/services/game-network.ts +657 -0
- package/src/services/game-physics.ts +334 -0
- package/src/state-watch/StateWatchPanel.tsx +1 -1
- package/src/three/authoring/camera-runtime-inspector-section.tsx +3 -2
- package/src/three/authoring/constraint-inspector-section.tsx +6 -5
- package/src/three/authoring/model-asset-inspector-section.tsx +7 -6
- package/src/three/authoring/oid-source-persistence.ts +7 -7
- package/src/three/authoring/r3f-design-session.ts +58 -54
- package/src/three/authoring/r3f-source-authoring-adapter.ts +95 -91
- package/src/three/authoring/reflection-probe-inspector-section.tsx +3 -2
- package/src/three/authoring/three-authoring-adapter.ts +29 -29
- package/src/three/component-verbs/extract-menu.ts +7 -6
- package/src/three/component-verbs/fork-menu.ts +7 -6
- package/src/three/component-verbs/internals-menu.ts +2 -2
- package/src/three/story-documents/three-story-documents.tsx +13 -13
- package/src/three/three-board/ThreeBoardDocument.tsx +13 -12
- package/src/three/three-board/board-scene.ts +6 -6
- package/src/three/three-board/three-component-board.ts +2 -2
- package/src/services/game-audio-unlock.ts +0 -48
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONE `DebugAdapter` out of several projections of the same mount.
|
|
3
|
+
*
|
|
4
|
+
* Two feeders exist today and they are peers, not layers: the game's own
|
|
5
|
+
* declared system surface (`contract-debug-adapter.ts`, from
|
|
6
|
+
* `window.vgaiGame.systems`) and its adapter module's observation table
|
|
7
|
+
* (`observation-debug-adapter.ts`). Both project onto the ONE runtime door the
|
|
8
|
+
* editor and `vgai eval` already read (`SystemAdapters.debug`), so the merge
|
|
9
|
+
* belongs here rather than in a second slot — a consumer must not be able to
|
|
10
|
+
* tell a name's provenance, which is the whole point of both projections.
|
|
11
|
+
*
|
|
12
|
+
* ## A collision REFUSES the name; it never shadows it
|
|
13
|
+
*
|
|
14
|
+
* When two feeders declare the same provider or command name, one of them would
|
|
15
|
+
* otherwise win silently and an agent would read the wrong thing with every log
|
|
16
|
+
* line still saying success. So the merged adapter:
|
|
17
|
+
*
|
|
18
|
+
* - LISTS the name once (it is declared, and hiding it would be its own lie);
|
|
19
|
+
* - REFUSES to answer it with a coded `DEBUG_NAME_COLLISION` naming BOTH
|
|
20
|
+
* sources, at `state()` / `invoke()` and per-key inside `stateAll()`;
|
|
21
|
+
* - reports the collisions to the caller ({@link MergedDebugAdapters}) so the
|
|
22
|
+
* mount can say so out loud where a person will see it, instead of the
|
|
23
|
+
* refusal waiting to be discovered by whoever calls the name first.
|
|
24
|
+
*
|
|
25
|
+
* The refusal is per-NAME, not per-mount, deliberately: killing the mount would
|
|
26
|
+
* cost a user their whole game to protect against one ambiguous read, and the
|
|
27
|
+
* per-name refusal already gives the same guarantee — nothing is silently
|
|
28
|
+
* shadowed — with none of the collateral.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type {
|
|
32
|
+
DebugAdapter,
|
|
33
|
+
DebugCommandInfo,
|
|
34
|
+
TickStampedEvent,
|
|
35
|
+
} from '@volter/editor-project/adapter/system-adapter';
|
|
36
|
+
import { DebugError } from '../../debug-registry';
|
|
37
|
+
|
|
38
|
+
/** One feeder, with the words a collision message names it by. */
|
|
39
|
+
export interface DebugAdapterSource {
|
|
40
|
+
/** How this projection is named to a reader — its provenance, in prose. */
|
|
41
|
+
readonly label: string;
|
|
42
|
+
/** `null` when this feeder projected nothing (the honest floor both return). */
|
|
43
|
+
readonly adapter: DebugAdapter | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface MergedDebugAdapters {
|
|
47
|
+
/** The one door, or `null` when no feeder projected anything. */
|
|
48
|
+
readonly adapter: DebugAdapter | null;
|
|
49
|
+
/** One sentence per colliding name, naming both sources. Empty is the norm. */
|
|
50
|
+
readonly collisions: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function collisionError(name: string, labels: readonly string[]): DebugError {
|
|
54
|
+
return new DebugError(
|
|
55
|
+
'DEBUG_NAME_COLLISION',
|
|
56
|
+
`"${name}" is declared by more than one source (${labels.join(' and ')}) — refusing to ` +
|
|
57
|
+
'answer rather than picking one silently. Rename it in whichever source you own.',
|
|
58
|
+
{ name, sources: [...labels] },
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Names each feeder claims, in listing order, de-duplicated within a feeder. */
|
|
63
|
+
function claimedNames(
|
|
64
|
+
sources: readonly DebugAdapterSource[],
|
|
65
|
+
face: 'state' | 'command',
|
|
66
|
+
): {
|
|
67
|
+
order: string[];
|
|
68
|
+
labelsByName: Map<string, string[]>;
|
|
69
|
+
} {
|
|
70
|
+
const order: string[] = [];
|
|
71
|
+
const labelsByName = new Map<string, string[]>();
|
|
72
|
+
for (const source of sources) {
|
|
73
|
+
if (!source.adapter) continue;
|
|
74
|
+
const names =
|
|
75
|
+
face === 'state'
|
|
76
|
+
? source.adapter.providers().map((provider) => provider.name)
|
|
77
|
+
: source.adapter.commands().map((command) => command.name);
|
|
78
|
+
for (const name of new Set(names)) {
|
|
79
|
+
if (!labelsByName.has(name)) {
|
|
80
|
+
labelsByName.set(name, []);
|
|
81
|
+
order.push(name);
|
|
82
|
+
}
|
|
83
|
+
labelsByName.get(name)?.push(source.label);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { order, labelsByName };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Fold every projection of one mount onto a single {@link DebugAdapter}.
|
|
91
|
+
*
|
|
92
|
+
* `events()` concatenates, because an event ring is append-only and two rings
|
|
93
|
+
* genuinely both happened — there is no name to collide on. Everything else is
|
|
94
|
+
* name-keyed and therefore goes through the collision rule above.
|
|
95
|
+
*/
|
|
96
|
+
export function mergeDebugAdapters(sources: readonly DebugAdapterSource[]): MergedDebugAdapters {
|
|
97
|
+
const live = sources.filter((source) => source.adapter !== null);
|
|
98
|
+
if (live.length === 0) return { adapter: null, collisions: [] };
|
|
99
|
+
if (live.length === 1) return { adapter: live[0]!.adapter, collisions: [] };
|
|
100
|
+
|
|
101
|
+
const providers = claimedNames(live, 'state');
|
|
102
|
+
const commands = claimedNames(live, 'command');
|
|
103
|
+
|
|
104
|
+
const collisions: string[] = [];
|
|
105
|
+
for (const [face, claimed] of [
|
|
106
|
+
['state provider', providers],
|
|
107
|
+
['command', commands],
|
|
108
|
+
] as const) {
|
|
109
|
+
for (const name of claimed.order) {
|
|
110
|
+
const labels = claimed.labelsByName.get(name) ?? [];
|
|
111
|
+
if (labels.length > 1) {
|
|
112
|
+
collisions.push(`${face} "${name}" is declared by ${labels.join(' and ')}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The feeder that owns `name`, or a thrown collision error when several do. */
|
|
118
|
+
const ownerOf = (
|
|
119
|
+
claimed: ReturnType<typeof claimedNames>,
|
|
120
|
+
name: string,
|
|
121
|
+
face: 'state' | 'command',
|
|
122
|
+
): DebugAdapter | null => {
|
|
123
|
+
const labels = claimed.labelsByName.get(name);
|
|
124
|
+
if (!labels) return null;
|
|
125
|
+
if (labels.length > 1) throw collisionError(name, labels);
|
|
126
|
+
return (
|
|
127
|
+
live.find((source) => {
|
|
128
|
+
const owned =
|
|
129
|
+
face === 'state'
|
|
130
|
+
? source.adapter?.providers().some((provider) => provider.name === name)
|
|
131
|
+
: source.adapter?.commands().some((command) => command.name === name);
|
|
132
|
+
return owned === true;
|
|
133
|
+
})?.adapter ?? null
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
adapter: {
|
|
139
|
+
providers: () =>
|
|
140
|
+
live.flatMap((source) => source.adapter?.providers() ?? []).filter(uniqueByName()),
|
|
141
|
+
|
|
142
|
+
state: (name: string): unknown => {
|
|
143
|
+
const owner = ownerOf(providers, name, 'state');
|
|
144
|
+
if (!owner) {
|
|
145
|
+
throw new DebugError(
|
|
146
|
+
'STATE_PROVIDER_NOT_FOUND',
|
|
147
|
+
`No state provider named "${name}" is declared by this game.`,
|
|
148
|
+
{ registered: providers.order },
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return owner.state(name);
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
stateAll: (): Record<string, unknown> => {
|
|
155
|
+
const out: Record<string, unknown> = {};
|
|
156
|
+
for (const name of providers.order) {
|
|
157
|
+
try {
|
|
158
|
+
out[name] = ownerOf(providers, name, 'state')?.state(name);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
out[name] = { __error: String(err) };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
commands: (): DebugCommandInfo[] =>
|
|
167
|
+
live.flatMap((source) => source.adapter?.commands() ?? []).filter(uniqueByName()),
|
|
168
|
+
|
|
169
|
+
invoke: async (name: string, args: unknown[]): Promise<unknown> => {
|
|
170
|
+
const owner = ownerOf(commands, name, 'command');
|
|
171
|
+
if (!owner) {
|
|
172
|
+
throw new DebugError(
|
|
173
|
+
'DEBUG_COMMAND_NOT_REGISTERED',
|
|
174
|
+
`No command named "${name}" is declared by this game.`,
|
|
175
|
+
{ registered: commands.order },
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return owner.invoke(name, args);
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
events: (sinceSeq?: number): TickStampedEvent[] =>
|
|
182
|
+
live.flatMap((source) => source.adapter?.events(sinceSeq) ?? []),
|
|
183
|
+
},
|
|
184
|
+
collisions,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Keep the FIRST listing of each name — the merged listing advertises a name
|
|
189
|
+
* once, whatever `state()`/`invoke()` then decide to do with it. */
|
|
190
|
+
function uniqueByName<T extends { name: string }>(): (value: T) => boolean {
|
|
191
|
+
const seen = new Set<string>();
|
|
192
|
+
return (value) => {
|
|
193
|
+
if (seen.has(value.name)) return false;
|
|
194
|
+
seen.add(value.name);
|
|
195
|
+
return true;
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projects an ADAPTER's declared observation slots
|
|
3
|
+
* (`AdapterDefinition.observation`, `adapter/adapter-module.ts`) onto the host's
|
|
4
|
+
* existing {@link DebugAdapter} — the one seam `game.providers()` /
|
|
5
|
+
* `game.state()` / `game.commands()` already read through
|
|
6
|
+
* (`command-listener.ts`'s `dispatchBridgeMethod` → `getActiveSystems().debug`).
|
|
7
|
+
*
|
|
8
|
+
* This is the SIBLING of `contract-debug-adapter.ts` and deliberately the same
|
|
9
|
+
* shape: a projection rather than a second door. The two differ only in WHO
|
|
10
|
+
* declared the surface — the game itself, on `window.vgaiGame.systems`, or the
|
|
11
|
+
* game's adapter module beside it — and a consumer downstream must not be able
|
|
12
|
+
* to tell them apart. `merge-debug-adapters.ts` is what folds both onto the one
|
|
13
|
+
* slot.
|
|
14
|
+
*
|
|
15
|
+
* ## What `game` is
|
|
16
|
+
*
|
|
17
|
+
* The host's to hand over, and for an INGEST mount it is the REALM the game's
|
|
18
|
+
* own modules ran in. That is not a convenience: an ingest mount runs the game
|
|
19
|
+
* in the editor's own realm (`game-contract.ts`'s `readGameContract` says so —
|
|
20
|
+
* "the realm every ingest mount runs the game's modules in, where the game and
|
|
21
|
+
* the host share one global"), a vendored bundle's adapter is HOST-realm by the
|
|
22
|
+
* placement rule, and the only handles such a game publishes are the ones on
|
|
23
|
+
* that realm — its runtime's own public registry, or a host-build seam beside
|
|
24
|
+
* it. Handing the realm is therefore handing the game everything it actually
|
|
25
|
+
* exposes; handing anything narrower would make the adapter reach for `window`
|
|
26
|
+
* itself, which is the same read with the provenance hidden.
|
|
27
|
+
*
|
|
28
|
+
* ## The two-state rule, kept
|
|
29
|
+
*
|
|
30
|
+
* Declaring a slot and answering it are separate facts. A declaration with NO
|
|
31
|
+
* `answer` is still listed and refuses BY NAME when read — never omitted,
|
|
32
|
+
* because omitting it would report "the game has no such state" when the truth
|
|
33
|
+
* is "the table declared it and nothing binds it yet". Same for a throwing
|
|
34
|
+
* `answer`: it propagates (exactly as a first-party provider's does), and only
|
|
35
|
+
* `stateAll()` isolates it per key.
|
|
36
|
+
*
|
|
37
|
+
* ## What this deliberately does NOT do
|
|
38
|
+
*
|
|
39
|
+
* - **Validate arguments.** A declared command's `answer` receives the invoked
|
|
40
|
+
* argument list after the game handle; there is no Zod tuple beside a foreign
|
|
41
|
+
* game, so a verb validates itself and throws its own error, surfaced wrapped
|
|
42
|
+
* as `DEBUG_COMMAND_FAILED`. Identical to the contract projection's stance.
|
|
43
|
+
* - **Fabricate an event ring.** Nothing emits behind a declaration, so
|
|
44
|
+
* `events()` is empty — accurate, not a stub.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import type { ObservationDeclaration } from '@volter/editor-project/adapter/adapter-module';
|
|
48
|
+
import type {
|
|
49
|
+
DebugAdapter,
|
|
50
|
+
DebugCommandInfo,
|
|
51
|
+
TickStampedEvent,
|
|
52
|
+
} from '@volter/editor-project/adapter/system-adapter';
|
|
53
|
+
import { DebugError } from '../../debug-registry';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* An adapter-declared read is DERIVED by the host-side table from the game's own
|
|
57
|
+
* handles — which is precisely what `assisted` means in the tier vocabulary
|
|
58
|
+
* (`game-contract.ts`: "`observable` is a value the game already computes,
|
|
59
|
+
* `assisted` is one the shim derives"). Reporting `observable` would claim the
|
|
60
|
+
* game itself publishes the value.
|
|
61
|
+
*/
|
|
62
|
+
const DECLARED_TIER = 'assisted' as const;
|
|
63
|
+
|
|
64
|
+
function unanswered(id: string, kind: string): DebugError {
|
|
65
|
+
return new DebugError(
|
|
66
|
+
'OBSERVATION_UNANSWERED',
|
|
67
|
+
`Observation "${id}" (${kind}) is declared by this game's adapter with no \`answer\` — ` +
|
|
68
|
+
'the slot exists and nothing binds it yet, which is not the same as the game having none.',
|
|
69
|
+
{ id, kind },
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Build a {@link DebugAdapter} over an adapter's observation declarations, or
|
|
75
|
+
* `null` when it declares none. `null` is the honest floor — the same one
|
|
76
|
+
* `createContractDebugAdapter` returns — so a game that declared nothing keeps
|
|
77
|
+
* reporting `DEBUG_ADAPTER_UNAVAILABLE` rather than serving an empty adapter
|
|
78
|
+
* that reads as "this game has no state".
|
|
79
|
+
*/
|
|
80
|
+
export function createObservationDebugAdapter(
|
|
81
|
+
declarations: readonly ObservationDeclaration[] | undefined | null,
|
|
82
|
+
game: unknown,
|
|
83
|
+
): DebugAdapter | null {
|
|
84
|
+
const all = declarations ?? [];
|
|
85
|
+
if (all.length === 0) return null;
|
|
86
|
+
|
|
87
|
+
// Snapshot into id-keyed maps once, exactly as the contract projection does:
|
|
88
|
+
// a later duplicate shadows an earlier one and the listing de-duplicates with
|
|
89
|
+
// it, so a listed id can never resolve differently than it reads.
|
|
90
|
+
const byProvider = new Map(all.filter((d) => d.kind === 'state').map((d) => [d.id, d]));
|
|
91
|
+
const byCommand = new Map(all.filter((d) => d.kind === 'command').map((d) => [d.id, d]));
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
providers: () => [...byProvider.keys()].map((name) => ({ name, tier: DECLARED_TIER })),
|
|
95
|
+
|
|
96
|
+
state: (name: string): unknown => {
|
|
97
|
+
const declaration = byProvider.get(name);
|
|
98
|
+
if (!declaration) {
|
|
99
|
+
throw new DebugError(
|
|
100
|
+
'STATE_PROVIDER_NOT_FOUND',
|
|
101
|
+
`No state provider named "${name}" is declared by this game's adapter.`,
|
|
102
|
+
{ registered: [...byProvider.keys()] },
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (!declaration.answer) throw unanswered(name, 'state');
|
|
106
|
+
return declaration.answer(game);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
stateAll: (): Record<string, unknown> => {
|
|
110
|
+
const out: Record<string, unknown> = {};
|
|
111
|
+
for (const [name, declaration] of byProvider) {
|
|
112
|
+
// One throwing declaration must not cost the caller every other
|
|
113
|
+
// reading — the same per-key isolation the first-party registry gives.
|
|
114
|
+
try {
|
|
115
|
+
out[name] = declaration.answer
|
|
116
|
+
? declaration.answer(game)
|
|
117
|
+
: (() => {
|
|
118
|
+
throw unanswered(name, 'state');
|
|
119
|
+
})();
|
|
120
|
+
} catch (err) {
|
|
121
|
+
out[name] = { __error: String(err) };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
commands: (): DebugCommandInfo[] =>
|
|
128
|
+
[...byCommand.keys()].map((name) => ({
|
|
129
|
+
name,
|
|
130
|
+
// An ingested game runs entirely in the browser realm the host mounted
|
|
131
|
+
// it in; there is no server leg for its verbs to route to.
|
|
132
|
+
locus: byCommand.get(name)?.locus ?? ('client' as const),
|
|
133
|
+
})),
|
|
134
|
+
|
|
135
|
+
invoke: async (name: string, args: unknown[]): Promise<unknown> => {
|
|
136
|
+
const declaration = byCommand.get(name);
|
|
137
|
+
if (!declaration) {
|
|
138
|
+
throw new DebugError(
|
|
139
|
+
'DEBUG_COMMAND_NOT_REGISTERED',
|
|
140
|
+
`No command named "${name}" is declared by this game's adapter.`,
|
|
141
|
+
{ registered: [...byCommand.keys()] },
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (!declaration.answer) throw unanswered(name, 'command');
|
|
145
|
+
try {
|
|
146
|
+
// The game handle first, then the invoked argument list — a declared
|
|
147
|
+
// verb is a function OF the mounted game, which is the whole reason
|
|
148
|
+
// `answer` takes one.
|
|
149
|
+
return await (declaration.answer as (game: unknown, ...rest: unknown[]) => unknown)(
|
|
150
|
+
game,
|
|
151
|
+
...args,
|
|
152
|
+
);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
throw new DebugError('DEBUG_COMMAND_FAILED', `Command "${name}" threw: ${String(err)}`, {
|
|
155
|
+
name,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
events: (): TickStampedEvent[] => [],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upstream-pin extraction — R5 ((c)/ §7.1 item 9): the fix for the overlay identity
|
|
3
|
+
* gap the structural-id + `authoredAgainst.gameVersion` scheme (T7.7/D9) does not
|
|
4
|
+
* close. A re-vendor of a vendored upstream game that keeps a slot's shape (index +
|
|
5
|
+
* three.js type + name unchanged) reapplies a saved overlay onto a semantically
|
|
6
|
+
* different object with `applied: 1, orphanedIds: []` — silent wrong-meaning success
|
|
7
|
+
* — because `authoredAgainst.gameVersion` is the MANIFEST's own `version` (hand-set
|
|
8
|
+
* by whoever wrote the ingest manifest, e.g. every `public/ingest/*` vendored game
|
|
9
|
+
* ships `"version": "0.1.0"` regardless of which upstream commit it vendors), never
|
|
10
|
+
* tied to the thing that actually changes on a re-vendor: the `UPSTREAM.md` pin.
|
|
11
|
+
*
|
|
12
|
+
* This module is the pure, dependency-free half (parse text -> pin, or
|
|
13
|
+
* `null`): NO file I/O here, so it is usable from any context (editor
|
|
14
|
+
* save/apply-time hooks over a fetched `/UPSTREAM.md`, a future Node-side
|
|
15
|
+
* CLI check, tests). READ-ONLY by construction — it has no writer; this
|
|
16
|
+
* codebase never generates or edits an `UPSTREAM.md` (the anti-shim rule:
|
|
17
|
+
* adapters never fabricate first-party data, and a game's own vendoring
|
|
18
|
+
* provenance is exactly that).
|
|
19
|
+
*
|
|
20
|
+
* Format (established convention, not invented here — see every
|
|
21
|
+
* `public/ingest/*\/UPSTREAM.md`'s `## Upstream` section, e.g.
|
|
22
|
+
* `public/ingest/tanks/UPSTREAM.md`):
|
|
23
|
+
*
|
|
24
|
+
* ## Upstream
|
|
25
|
+
*
|
|
26
|
+
* - Repo: https://github.com/colyseus/realtime-tanks-demo
|
|
27
|
+
* - Commit: **`6339493130b4b1d4d28f0f52d17b2fba738c7d47`**
|
|
28
|
+
*
|
|
29
|
+
* i.e. a Markdown bullet whose label is `Commit:` and whose value is a bold,
|
|
30
|
+
* backtick-code full (or abbreviated) git SHA. Every BUNDLE-vendored game in
|
|
31
|
+
* this repo (`tanks`, `simcity`) follows this exact shape. Source-tree
|
|
32
|
+
* vendored games carry their pin in `vendor/games/<id>.UPSTREAM.lock`'s
|
|
33
|
+
* machine-readable `commit` instead and never reach this parser.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** Matches `- Commit: **\`<sha>\`**` (any amount of internal whitespace, case-insensitive hex). */
|
|
37
|
+
const COMMIT_BULLET_RE = /-\s*Commit:\s*\*\*`([0-9a-fA-F]{7,40})`\*\*/;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extract the pinned upstream commit hash from a vendored game's
|
|
41
|
+
* `UPSTREAM.md` prose, or `null` if the file doesn't follow the convention
|
|
42
|
+
* (or wasn't found — the caller decides what a missing file means; this
|
|
43
|
+
* function only parses text it's handed). Never throws — an unparseable
|
|
44
|
+
* document is an honest `null`, not an error (anti-shim: no guessing, no
|
|
45
|
+
* fabricating a pin from a game folder that doesn't declare one, e.g. an
|
|
46
|
+
* externally-authored non-vendored project opened via the CLI-on-folder
|
|
47
|
+
* route).
|
|
48
|
+
*/
|
|
49
|
+
export function extractUpstreamPin(markdown: string): string | null {
|
|
50
|
+
return COMMIT_BULLET_RE.exec(markdown)?.[1] ?? null;
|
|
51
|
+
}
|