@volter/editor-game 0.5.67 → 0.5.69
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/NOTICE +7 -7
- package/contributions/autoplay.service.ts +1 -1
- package/contributions/build-player.document.tsx +95 -0
- package/contributions/build-profiles.document.tsx +1 -1
- package/contributions/build-progress.status.tsx +2 -2
- package/contributions/build.header.tsx +1 -1
- package/contributions/connection.status.tsx +1 -1
- package/contributions/coverage.service.ts +2 -2
- package/contributions/game-eval.command.ts +6 -6
- package/contributions/gameplay.command.ts +2 -2
- package/contributions/generations.status.tsx +1 -1
- package/contributions/ingest.service.ts +2 -2
- package/contributions/play.command.ts +6 -6
- package/contributions/react/react-inspector.service.ts +1 -1
- package/package.json +9 -8
- package/serving/index.ts +1 -1
- package/src/asset-budget/AssetBudgetPanel.tsx +1 -1
- package/src/asset-budget/asset-budget-model.ts +14 -14
- package/src/asset-budget/optimize-apply.ts +8 -8
- package/src/bridge/call.ts +3 -3
- package/src/bridge/dispatch.ts +12 -12
- package/src/bridge/screenshot.ts +6 -6
- package/src/build/BuildProfilesPanel.tsx +19 -8
- package/src/build/build-session.ts +35 -0
- package/src/canvas/canvas-board/CanvasBoardDocument.tsx +78 -5
- package/src/canvas/canvas-board/canvas-component-board.ts +1 -1
- package/src/canvas/canvas-design-mount.ts +1 -1
- package/src/coverage/live-project-verbs.ts +3 -3
- package/src/coverage/native-system-coverage.ts +3 -3
- package/src/coverage/root-coverage.ts +2 -2
- package/src/coverage/session-coverage.ts +2 -2
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +2 -2
- package/src/game-document/DevicePresetPicker.tsx +6 -6
- package/src/game-document/GameCaptureFrameButton.tsx +1 -1
- package/src/game-document/GameDocument.tsx +2 -2
- package/src/game-document/GamePanel.tsx +9 -9
- package/src/game-document/InstanceInspectorPicker.tsx +1 -1
- package/src/game-document/device-preview.ts +4 -4
- package/src/game-document/physics-debug.ts +2 -2
- package/src/generation/GenerationActivity.tsx +22 -22
- package/src/generation/GenerationGallery.css +79 -79
- package/src/generation/generation-documents.tsx +25 -25
- package/src/generation/generation-presentation.ts +1 -1
- package/src/host/adapter-reach.ts +1 -1
- package/src/host/adapter-runtime-bindings.ts +5 -5
- package/src/host/after-paint.ts +3 -3
- package/src/host/api/configurations.ts +1 -1
- package/src/host/authoring/canvas-runtime-recognition.ts +2 -2
- package/src/host/authoring/contract-hierarchy-authoring.ts +6 -6
- package/src/host/authoring/contract-scenes-stories.ts +5 -5
- package/src/host/authoring/ingest-data-writer.ts +1 -1
- package/src/host/authoring/ingest-source-persistence.ts +2 -2
- package/src/host/authoring/mount-isolated-pixi-screen.ts +1 -1
- package/src/host/authoring/pixi-authoring-adapter.ts +206 -28
- package/src/host/authoring/pixi-creatable-kinds.ts +80 -0
- package/src/host/authoring/pixi-live-write-target.ts +6 -5
- package/src/host/authoring/pixi-source-write-target.ts +118 -20
- package/src/host/authoring/pixi-structure-history.ts +3 -3
- package/src/host/binding-resolver.ts +6 -6
- package/src/host/browser-transpile.ts +10 -10
- package/src/host/canvas-preview-frames.ts +1 -1
- package/src/host/components/CameraAuthoringOverlay.tsx +14 -14
- package/src/host/components/HeaderTelemetry.tsx +11 -11
- package/src/host/components/PixiIsolationSceneContent.tsx +3 -3
- package/src/host/components/ResolutionPicker.tsx +4 -4
- package/src/host/components/scene-document.tsx +3 -3
- package/src/host/components/world-root-stage-binding.tsx +1 -1
- package/src/host/components/world-root-stage.ts +1 -1
- package/src/host/coverage/authoring-read-probe.ts +1 -1
- package/src/host/coverage/capability-coverage.ts +22 -22
- package/src/host/coverage/game-contract-seam-evidence.ts +8 -8
- package/src/host/coverage/project-verb-coverage.ts +1 -1
- package/src/host/coverage/system-adapter-coverage.ts +19 -6
- package/src/host/game-location-guard.ts +3 -3
- package/src/host/game-module-access.ts +6 -6
- package/src/host/game-realm-page.ts +3 -3
- package/src/host/game-realm-reclaim.ts +1 -1
- package/src/host/gameplay-recording.ts +5 -5
- package/src/host/gated-globals.ts +13 -13
- package/src/host/ingest/registry.ts +14 -14
- package/src/host/play-control-hook.ts +1 -1
- package/src/host/playwright-shim.ts +2 -2
- package/src/host/projection/pixi.ts +2 -2
- package/src/host/react-mount-runtime.ts +1 -1
- package/src/host/roots/canvas-root.tsx +13 -1
- package/src/host/roots/r3f-root.tsx +1 -1
- package/src/host/scene-view-drawability.ts +1 -1
- package/src/host/sdk/tools.ts +1 -1
- package/src/host/served-bundle-runtime-modules.ts +2 -0
- package/src/host/staged-projects.ts +1 -1
- package/src/host/stories/story-pixi-preview.ts +1 -1
- package/src/host/stories/story-three-preview.ts +1 -1
- package/src/host/three-ingest-runtime.ts +1 -1
- 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 +11 -11
- package/src/ingest/capture-wait-report.ts +4 -4
- package/src/ingest/discovery-public-ingest.ts +4 -4
- package/src/ingest/dom-stub-mark.ts +1 -1
- package/src/ingest/entry-load.ts +1 -1
- package/src/ingest/game-contract-realm.ts +3 -3
- package/src/ingest/game-pointer-lock.ts +1 -1
- package/src/ingest/ingest-canvas-scene-document.tsx +3 -3
- package/src/ingest/ingest-evidence-hook.ts +6 -6
- package/src/ingest/ingest-frame-snapshot.ts +2 -2
- package/src/ingest/ingest-play-commands.ts +1 -1
- package/src/ingest/ingest-play-control.ts +11 -11
- package/src/ingest/ingest-render-debug.ts +5 -5
- package/src/ingest/ingest-siblings.ts +3 -3
- package/src/ingest/module-mode.ts +2 -2
- package/src/ingest/mount-canvas-ingest-root.ts +10 -10
- package/src/ingest/mount-coverage.ts +3 -3
- package/src/ingest/mount-dom-ingest-root.ts +7 -7
- package/src/ingest/mount-ingest-root.ts +10 -10
- package/src/ingest/mount-three-ingest-root.ts +5 -5
- package/src/ingest/resolve-three.ts +2 -2
- package/src/ingest/served-bundle.ts +6 -6
- package/src/ingest/served-html-boot.ts +8 -8
- package/src/ingest/surface-dom.ts +9 -9
- package/src/ingest/surface-three.ts +1 -1
- package/src/ingest/types.ts +1 -1
- package/src/ingest/unmount-ingest-root.ts +3 -3
- package/src/network/NetworkInspectorPanel.tsx +521 -54
- package/src/network/network-inspector-model.ts +6 -0
- package/src/play/play-boot-stall.ts +1 -1
- package/src/play/play-mode.ts +31 -30
- package/src/play/play-recording.ts +12 -12
- package/src/play-bar/PlayBar.tsx +8 -8
- package/src/play-bar/PlayerCountPicker.tsx +3 -3
- package/src/profiler/FrameDebuggerPanel.tsx +2 -2
- package/src/profiler/PerformancePanel.tsx +4 -4
- package/src/react/design-time-react-mount.ts +5 -5
- package/src/react/dom-authoring-adapter.ts +3 -3
- package/src/react/react-inspector-section.tsx +2 -2
- package/src/react/react-world-authoring-adapter.ts +20 -20
- package/src/react/story-documents/story-documents.tsx +4 -4
- package/src/react/ui-component-board.ts +1 -1
- package/src/runtime/adapter/audio-meter.ts +1 -1
- package/src/runtime/adapter/ingest/contract-debug-adapter.ts +4 -4
- package/src/runtime/adapter/ingest/contract-system-adapters.ts +9 -9
- package/src/runtime/adapter/ingest/merge-debug-adapters.ts +2 -2
- package/src/runtime/adapter/ingest/observation-debug-adapter.ts +1 -1
- package/src/runtime/adapter/native-debug-module.ts +5 -5
- package/src/runtime/audio/pose-guard.ts +1 -1
- package/src/runtime/core/game-scoped-slot.ts +1 -1
- package/src/runtime/create-runtime.ts +15 -15
- package/src/runtime/debug-bridge.ts +25 -25
- package/src/runtime/debug-registry.ts +17 -17
- package/src/runtime/dev/instruments.ts +1 -1
- package/src/runtime/dev/performance-profiler.ts +2 -2
- package/src/runtime/dev/register-render-vitals.ts +2 -2
- package/src/runtime/dev/static-batch-advisor.ts +3 -3
- package/src/runtime/dev-build.ts +3 -3
- package/src/runtime/game.ts +5 -5
- package/src/runtime/input-router.ts +24 -11
- package/src/runtime/mount-manifest.ts +9 -9
- package/src/runtime/pixi/authoring.ts +1 -1
- package/src/runtime/pixi/ingest.ts +1 -1
- package/src/runtime/presentation.ts +1 -1
- package/src/runtime/render-control.ts +21 -21
- package/src/runtime/render-seed.ts +3 -3
- package/src/runtime/run-ticks-settled.ts +1 -1
- package/src/services/game-audio.ts +3 -3
- package/src/services/game-network.ts +125 -15
- package/src/three/authoring/model-asset-inspector-section.css +15 -15
- package/src/three/authoring/model-asset-inspector-section.tsx +7 -7
- package/src/three/authoring/r3f-design-session.ts +10 -10
- package/src/three/authoring/r3f-source-authoring-adapter.ts +5 -5
- package/src/three/authoring/three-authoring-adapter.ts +5 -4
- package/src/three/authoring/typed-three-inspector.ts +1 -1
- package/src/three/story-documents/three-story-documents.tsx +3 -3
- package/src/three/three-board/board-scene.ts +9 -9
- package/src/three/three-board/three-component-board.ts +1 -1
|
@@ -172,13 +172,13 @@ function AudioTelemetry() {
|
|
|
172
172
|
: 'Audio quiet';
|
|
173
173
|
|
|
174
174
|
return (
|
|
175
|
-
<div className="
|
|
175
|
+
<div className="volter-header-audio" data-muted={muted || undefined} data-available={!!adapter}>
|
|
176
176
|
<Button
|
|
177
177
|
ref={buttonRef}
|
|
178
178
|
type="button"
|
|
179
179
|
variant="ghost"
|
|
180
180
|
size="compact"
|
|
181
|
-
className="
|
|
181
|
+
className="volter-header-telemetry-button"
|
|
182
182
|
data-testid="header-audio-telemetry"
|
|
183
183
|
aria-label={`${stateLabel}; open audio controls`}
|
|
184
184
|
aria-haspopup="menu"
|
|
@@ -187,7 +187,7 @@ function AudioTelemetry() {
|
|
|
187
187
|
onClick={() => setMenuOpen((open) => !open)}
|
|
188
188
|
>
|
|
189
189
|
<EditorIcon icon={muted ? faVolumeXmark : faVolumeHigh} size="sm" />
|
|
190
|
-
<span className="
|
|
190
|
+
<span className="volter-audio-vu" aria-hidden="true">
|
|
191
191
|
{Array.from({ length: AUDIO_METER_SEGMENTS }, (_, index) => (
|
|
192
192
|
<i key={index} data-filled={index < filled || undefined} />
|
|
193
193
|
))}
|
|
@@ -268,7 +268,7 @@ function PerformanceTelemetry() {
|
|
|
268
268
|
type="button"
|
|
269
269
|
variant="ghost"
|
|
270
270
|
size="compact"
|
|
271
|
-
className="
|
|
271
|
+
className="volter-header-telemetry-button volter-header-performance"
|
|
272
272
|
data-testid="header-performance-telemetry"
|
|
273
273
|
data-performance-tone={tone}
|
|
274
274
|
disabled={!source}
|
|
@@ -277,24 +277,24 @@ function PerformanceTelemetry() {
|
|
|
277
277
|
onClick={() => profilerView.open('profiler')}
|
|
278
278
|
>
|
|
279
279
|
<svg
|
|
280
|
-
className="
|
|
280
|
+
className="volter-performance-graph"
|
|
281
281
|
aria-hidden="true"
|
|
282
282
|
viewBox="0 0 72 18"
|
|
283
283
|
width="72"
|
|
284
284
|
height="18"
|
|
285
285
|
>
|
|
286
286
|
<line
|
|
287
|
-
className="
|
|
287
|
+
className="volter-performance-budget"
|
|
288
288
|
x1="0"
|
|
289
289
|
x2="72"
|
|
290
290
|
y1={18 - (FRAME_BUDGET_60_FPS_MS / 50) * 18}
|
|
291
291
|
y2={18 - (FRAME_BUDGET_60_FPS_MS / 50) * 18}
|
|
292
292
|
/>
|
|
293
|
-
{points && <polyline className="
|
|
293
|
+
{points && <polyline className="volter-performance-sparkline" points={points} />}
|
|
294
294
|
</svg>
|
|
295
|
-
<span className="
|
|
296
|
-
<strong className="
|
|
297
|
-
<span className="
|
|
295
|
+
<span className="volter-performance-readout">
|
|
296
|
+
<strong className="volter-header-fps">{fps ?? '—'} FPS</strong>
|
|
297
|
+
<span className="volter-header-frame-ms">{frameMs?.toFixed(1) ?? '—'} ms</span>
|
|
298
298
|
</span>
|
|
299
299
|
</Button>
|
|
300
300
|
);
|
|
@@ -322,7 +322,7 @@ export function HeaderTelemetry() {
|
|
|
322
322
|
if (activeChromeRegions().telemetry === 'hidden') return null;
|
|
323
323
|
return (
|
|
324
324
|
<Inline
|
|
325
|
-
className="
|
|
325
|
+
className="volter-header-telemetry volter-chrome-island volter-glass-island"
|
|
326
326
|
data-island-scale="compact"
|
|
327
327
|
align="center"
|
|
328
328
|
gap={1}
|
|
@@ -184,8 +184,8 @@ export function PixiIsolationSceneContent({
|
|
|
184
184
|
ref={containerRef}
|
|
185
185
|
data-testid="scene-document-pixi-isolation"
|
|
186
186
|
data-document-id={documentId}
|
|
187
|
-
data-
|
|
188
|
-
data-
|
|
187
|
+
data-volter-backdrop-color={active ? CANVAS_SCENE_BACKGROUND : undefined}
|
|
188
|
+
data-volter-backdrop-policy={active ? 'dark-frost' : undefined}
|
|
189
189
|
onPointerDown={() => setActiveScope('viewport')}
|
|
190
190
|
style={{
|
|
191
191
|
position: 'absolute',
|
|
@@ -197,7 +197,7 @@ export function PixiIsolationSceneContent({
|
|
|
197
197
|
}}
|
|
198
198
|
>
|
|
199
199
|
<CanvasSceneBackdrop view={view} documentId={documentId} />
|
|
200
|
-
{adapter ? <RootSelectionOverlay adapter={adapter} view={view} transformModeAware /> : null}
|
|
200
|
+
{adapter ? <RootSelectionOverlay adapter={adapter} view={view} transformModeAware presentationId={documentId} /> : null}
|
|
201
201
|
<CanvasSceneControls
|
|
202
202
|
active={active}
|
|
203
203
|
{...(adapter ? { adapter } : {})}
|
|
@@ -42,14 +42,14 @@ export function ResolutionPicker({
|
|
|
42
42
|
const label = isFill ? 'Fill' : `${resolution.width}×${resolution.height}`;
|
|
43
43
|
|
|
44
44
|
return (
|
|
45
|
-
<div className="
|
|
45
|
+
<div className="volter-playbar-popover-anchor">
|
|
46
46
|
<Button
|
|
47
47
|
ref={ref}
|
|
48
48
|
variant="ghost"
|
|
49
49
|
size="comfortable"
|
|
50
50
|
aria-haspopup="menu"
|
|
51
51
|
aria-expanded={open}
|
|
52
|
-
aria-controls={open ? '
|
|
52
|
+
aria-controls={open ? 'volter-resolution-picker-menu' : undefined}
|
|
53
53
|
onClick={() => setOpen((value) => !value)}
|
|
54
54
|
title={title}
|
|
55
55
|
>
|
|
@@ -63,9 +63,9 @@ export function ResolutionPicker({
|
|
|
63
63
|
</Button>
|
|
64
64
|
{open && (
|
|
65
65
|
<AnchoredMenu
|
|
66
|
-
id="
|
|
66
|
+
id="volter-resolution-picker-menu"
|
|
67
67
|
anchorRef={ref}
|
|
68
|
-
className="
|
|
68
|
+
className="volter-resolution-menu"
|
|
69
69
|
gap={4}
|
|
70
70
|
onDismiss={() => setOpen(false)}
|
|
71
71
|
>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `Scene` (the subject, not its entry file — see SCENE_DOCUMENT_TITLE),
|
|
4
4
|
* closeable and discoverable in Content, content = the world root's stage.
|
|
5
5
|
*
|
|
6
|
-
* Transcribed from the Scene half of
|
|
6
|
+
* Transcribed from the Scene half of volter's `components/CenterDocuments.tsx`.
|
|
7
7
|
* The kit keeps the other half (`@volter/editor-core/components/CenterDocuments`
|
|
8
8
|
* `useCenterDocuments`): it binds the Game document's tab, reports the
|
|
9
9
|
* live/stopped edge and closes every document on session teardown. What moved
|
|
@@ -195,7 +195,7 @@ export class ViewportErrorBoundary extends Component<
|
|
|
195
195
|
editorConsole.error(`Viewport failed to start: ${error.message}`, 'editor');
|
|
196
196
|
// Keep the component stack in the browser console for developer diagnosis.
|
|
197
197
|
// biome-ignore lint/suspicious/noConsole: renderer startup failures need their React component stack in developer tools as well as the user-facing editor Console entry
|
|
198
|
-
console.error('[
|
|
198
|
+
console.error('[volter] Viewport failed to start', error, info.componentStack);
|
|
199
199
|
|
|
200
200
|
if (isTransientRendererStartupFailure(error) && this.autoRetryCount < MAX_AUTO_RETRIES) {
|
|
201
201
|
this.clearPendingRetry();
|
|
@@ -251,7 +251,7 @@ export class ViewportErrorBoundary extends Component<
|
|
|
251
251
|
</div>
|
|
252
252
|
<Button
|
|
253
253
|
type="button"
|
|
254
|
-
className="
|
|
254
|
+
className="volter-btn"
|
|
255
255
|
data-testid="retry-viewport"
|
|
256
256
|
onClick={this.handleManualRetry}
|
|
257
257
|
>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* (`@volter/editor-threejs/kit/components/world-root-binding`): the world's surface,
|
|
4
4
|
* its stage, and the overlays the stage shows only over a world.
|
|
5
5
|
*
|
|
6
|
-
* `Overlays` is the world-root half of
|
|
6
|
+
* `Overlays` is the world-root half of volter's `stage-overlay-set.tsx` — every
|
|
7
7
|
* part it rendered only when `worldRoot` held (the root selection layer, the
|
|
8
8
|
* camera-authoring pin, the scene's
|
|
9
9
|
* surface-state card). The rest of that overlay set is the kit's
|
|
@@ -454,7 +454,7 @@ export function installWorldRootStage(options: WorldRootStageOptions): WorldRoot
|
|
|
454
454
|
// DEV/e2e hook: lets the per-game feature matrix assert the viewport camera
|
|
455
455
|
// reframes (view presets / focus / orbit) on an ingested scene.
|
|
456
456
|
if (import.meta.env.DEV) {
|
|
457
|
-
(window as unknown as Record<string, unknown>)['
|
|
457
|
+
(window as unknown as Record<string, unknown>)['__volterViewport'] = viewport;
|
|
458
458
|
}
|
|
459
459
|
|
|
460
460
|
// --- Composer rebuild ---
|
|
@@ -510,7 +510,7 @@ function probeDocumentProviders(adapter: AuthoringAdapter, id: string, record: R
|
|
|
510
510
|
record(
|
|
511
511
|
'editor.assetDrop.accepts',
|
|
512
512
|
'operation',
|
|
513
|
-
() => adapter.assetDrop?.accepts(id, '
|
|
513
|
+
() => adapter.assetDrop?.accepts(id, '__volter_read_probe__'),
|
|
514
514
|
(value) => typeof value === 'boolean',
|
|
515
515
|
);
|
|
516
516
|
}
|
|
@@ -52,7 +52,7 @@ import type {
|
|
|
52
52
|
} from '@volter/editor-project/adapter';
|
|
53
53
|
import { WRITE_ANCHOR_KINDS } from '@volter/editor-project/adapter';
|
|
54
54
|
import type { AdapterSurface } from '@volter/editor-project/adapter/adapter-surface';
|
|
55
|
-
import type {
|
|
55
|
+
import type { VolterGameContract } from '@volter/editor-project/adapter/ingest/game-contract';
|
|
56
56
|
import type { SystemAdapters } from '@volter/editor-project/adapter/system-adapter';
|
|
57
57
|
import { SYSTEM_ADAPTERS_SHAPE } from '@volter/editor-project/adapter/system-seam-contract';
|
|
58
58
|
import {
|
|
@@ -92,7 +92,7 @@ export {
|
|
|
92
92
|
* gets a row.
|
|
93
93
|
*
|
|
94
94
|
* The rest are contract facts about an ingested game — declarations only a
|
|
95
|
-
* `window.
|
|
95
|
+
* `window.volterGame` can make — and they grow with the contract; nothing
|
|
96
96
|
* switches exhaustively on this type, so no consumer breaks when it does.
|
|
97
97
|
*/
|
|
98
98
|
export type CapabilityCoverageSeam =
|
|
@@ -212,7 +212,7 @@ export interface SystemsPresence {
|
|
|
212
212
|
* and this file never calls any of them.
|
|
213
213
|
*/
|
|
214
214
|
export interface ContractPresence {
|
|
215
|
-
/** `readGameContract()` answered at all (a v1 `window.
|
|
215
|
+
/** `readGameContract()` answered at all (a v1 `window.volterGame`). */
|
|
216
216
|
readonly declared: boolean;
|
|
217
217
|
readonly root: boolean;
|
|
218
218
|
readonly start: boolean;
|
|
@@ -224,7 +224,7 @@ export interface ContractPresence {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
/** Fold a read contract (or its absence) into presence flags. */
|
|
227
|
-
export function measureGameContract(contract:
|
|
227
|
+
export function measureGameContract(contract: VolterGameContract | null): ContractPresence {
|
|
228
228
|
const systems = contract?.systems;
|
|
229
229
|
return {
|
|
230
230
|
declared: contract !== null,
|
|
@@ -276,7 +276,7 @@ export interface WriteReachFacts {
|
|
|
276
276
|
* reads healthy while every one of the 12 belongs to one lane and a whole
|
|
277
277
|
* other lane — a world's physics-placed cargo, or its level-data records —
|
|
278
278
|
* goes unwritten and unexercised. A per-kind tally is what makes an
|
|
279
|
-
* exhaustive walk possible
|
|
279
|
+
* exhaustive walk possible.
|
|
280
280
|
*
|
|
281
281
|
* Optional, because callers older than the vocabulary supply none and a
|
|
282
282
|
* fabricated zero for every kind would read as a measurement.
|
|
@@ -307,7 +307,7 @@ export interface DataWriterFacts {
|
|
|
307
307
|
* actually holds an adapter for this slot. That is the strongest available
|
|
308
308
|
* statement: it is the same object the editor's panels will call.
|
|
309
309
|
* - `empty` — the game POSITIVELY answered "I have no X" through
|
|
310
|
-
* `window.
|
|
310
|
+
* `window.volterGame.systems.systemAdapters` (`evidence` is its own words).
|
|
311
311
|
* - `malformed` — a declaration that could not be honoured; `evidence` is the
|
|
312
312
|
* projection's reason. Reported so a broken shim is loud, never silently
|
|
313
313
|
* read as either of the two terminal states.
|
|
@@ -327,7 +327,7 @@ export interface SystemAdapterMeasurement {
|
|
|
327
327
|
* shared default would state wrongly.
|
|
328
328
|
*
|
|
329
329
|
* This is what keeps the derivation provenance-NEUTRAL. The default fix text
|
|
330
|
-
* below names `window.
|
|
330
|
+
* below names `window.volterGame.systems…`, which is true of a game that
|
|
331
331
|
* declares a contract and false of a first-party one; rather than have the
|
|
332
332
|
* row builder ask WHO produced the measurement, the measurement carries the
|
|
333
333
|
* answer as a fact (`coverage/system-adapter-coverage.ts` supplies it for a
|
|
@@ -474,7 +474,7 @@ type ResolvedCoverageFacts = Required<CapabilityCoverageFacts>;
|
|
|
474
474
|
|
|
475
475
|
/**
|
|
476
476
|
* The route for a game whose bytes may not be touched — which is EVERY
|
|
477
|
-
* repo-vendored ingest, by doctrine. "Declare `window.
|
|
477
|
+
* repo-vendored ingest, by doctrine. "Declare `window.volterGame.root`" is the
|
|
478
478
|
* mechanism, but a game nobody may edit cannot declare anything, so a fix that
|
|
479
479
|
* stopped there would be telling the reader to do something they are not
|
|
480
480
|
* allowed to do. Both host-side carriers now exist, so this names them and
|
|
@@ -483,8 +483,8 @@ type ResolvedCoverageFacts = Required<CapabilityCoverageFacts>;
|
|
|
483
483
|
const UNMODIFIABLE_ROUTE =
|
|
484
484
|
'a game whose bytes must stay unmodified declares it from a host-side carrier ' +
|
|
485
485
|
'instead — a contract shim beside the game (`ingest.contractShim` in its ' +
|
|
486
|
-
'
|
|
487
|
-
'public/ingest/simcity/
|
|
486
|
+
'volter.project.json, injected ahead of the game`s entry module; see ' +
|
|
487
|
+
'public/ingest/simcity/volter.shim.js) or a recorded patch';
|
|
488
488
|
|
|
489
489
|
const CONTRACT_FIX = (endpoint: string): string =>
|
|
490
490
|
`declare ${endpoint} in the game's own entry; ${UNMODIFIABLE_ROUTE}`;
|
|
@@ -656,7 +656,7 @@ function loopRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
656
656
|
missing:
|
|
657
657
|
"nothing in this editor controls the game's frames: ▶/⏸ and Step do not stop it, and " +
|
|
658
658
|
'Edit mode cannot be quiet — it keeps simulating while you author',
|
|
659
|
-
fix: CONTRACT_FIX('window.
|
|
659
|
+
fix: CONTRACT_FIX('window.volterGame.lifecycle.pause + resume'),
|
|
660
660
|
};
|
|
661
661
|
}
|
|
662
662
|
// No verdict, and that is its own answer — never a gap. The caller supplies
|
|
@@ -681,7 +681,7 @@ function contractRootRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
681
681
|
detail: `the game declares a root, but the seam failed — ${facts.contract.proof.root.detail}`,
|
|
682
682
|
missing:
|
|
683
683
|
'the host cannot trust that the declared element owns the game DOM rather than an unrelated or detached subtree',
|
|
684
|
-
fix: CONTRACT_FIX('window.
|
|
684
|
+
fix: CONTRACT_FIX('window.volterGame.root'),
|
|
685
685
|
};
|
|
686
686
|
}
|
|
687
687
|
return {
|
|
@@ -703,7 +703,7 @@ function contractRootRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
703
703
|
'the host adopts the bare canvas, so any DOM this game owns beside it (HUD, overlays, ' +
|
|
704
704
|
'portals) is stranded at page level over the editor chrome instead of living in the ' +
|
|
705
705
|
'game pane',
|
|
706
|
-
fix: CONTRACT_FIX('window.
|
|
706
|
+
fix: CONTRACT_FIX('window.volterGame.root'),
|
|
707
707
|
};
|
|
708
708
|
}
|
|
709
709
|
|
|
@@ -716,7 +716,7 @@ function contractStartRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
716
716
|
detail: `lifecycle.start is declared but failed — ${facts.contract.proof.start.detail}`,
|
|
717
717
|
missing:
|
|
718
718
|
'the mount cannot prove it starts cold and enters a session only when Play requests one',
|
|
719
|
-
fix: CONTRACT_FIX('window.
|
|
719
|
+
fix: CONTRACT_FIX('window.volterGame.lifecycle.start'),
|
|
720
720
|
};
|
|
721
721
|
}
|
|
722
722
|
return {
|
|
@@ -738,7 +738,7 @@ function contractStartRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
738
738
|
'the mount cannot be COLD: this game runs its session side-effects (backend ' +
|
|
739
739
|
'connections, audio, narrative) as it loads, so opening it in the editor starts ' +
|
|
740
740
|
'playing it, and ▶ is a wire event the game may ignore',
|
|
741
|
-
fix: CONTRACT_FIX('window.
|
|
741
|
+
fix: CONTRACT_FIX('window.volterGame.lifecycle.start'),
|
|
742
742
|
};
|
|
743
743
|
}
|
|
744
744
|
|
|
@@ -792,7 +792,7 @@ function contractPauseRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
792
792
|
status: 'gap',
|
|
793
793
|
detail: `pause/resume is declared but failed — ${failed.detail}`,
|
|
794
794
|
missing: pauseFallbackCost(facts),
|
|
795
|
-
fix: CONTRACT_FIX('window.
|
|
795
|
+
fix: CONTRACT_FIX('window.volterGame.lifecycle.pause + resume'),
|
|
796
796
|
};
|
|
797
797
|
}
|
|
798
798
|
return {
|
|
@@ -814,7 +814,7 @@ function contractPauseRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
814
814
|
? 'a contract is declared, but it names no lifecycle.pause/resume'
|
|
815
815
|
: 'no game→host contract is declared at all',
|
|
816
816
|
missing: pauseFallbackCost(facts),
|
|
817
|
-
fix: CONTRACT_FIX('window.
|
|
817
|
+
fix: CONTRACT_FIX('window.volterGame.lifecycle.pause + resume'),
|
|
818
818
|
};
|
|
819
819
|
}
|
|
820
820
|
|
|
@@ -989,7 +989,7 @@ function systemsRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
989
989
|
detail: `the systems carrier is declared but failed — ${facts.contract.proof.systems.detail}`,
|
|
990
990
|
missing:
|
|
991
991
|
'`game.commands()` / `game.providers()` cannot safely enumerate or invoke this carrier',
|
|
992
|
-
fix: CONTRACT_FIX('window.
|
|
992
|
+
fix: CONTRACT_FIX('window.volterGame.systems (commands + state)'),
|
|
993
993
|
};
|
|
994
994
|
}
|
|
995
995
|
return {
|
|
@@ -1013,7 +1013,7 @@ function systemsRow(facts: ResolvedCoverageFacts): CapabilityCoverageRow {
|
|
|
1013
1013
|
'this game exposes no verbs and no state to drive or read it with: `game.commands()` and ' +
|
|
1014
1014
|
'`game.providers()` enumerate nothing, so there is no `game.command(...)` to play it from ' +
|
|
1015
1015
|
`${commandLine('eval')} and no \`game.state(...)\` for ${commandLine('status')} to see anything of what it is doing`,
|
|
1016
|
-
fix: CONTRACT_FIX('window.
|
|
1016
|
+
fix: CONTRACT_FIX('window.volterGame.systems (commands + state)'),
|
|
1017
1017
|
};
|
|
1018
1018
|
}
|
|
1019
1019
|
|
|
@@ -1063,9 +1063,9 @@ function systemSlotFix(slot: SystemAdapterSlot): string {
|
|
|
1063
1063
|
);
|
|
1064
1064
|
}
|
|
1065
1065
|
if (slot === 'debug') {
|
|
1066
|
-
return CONTRACT_FIX('window.
|
|
1066
|
+
return CONTRACT_FIX('window.volterGame.systems (commands + state)');
|
|
1067
1067
|
}
|
|
1068
|
-
return CONTRACT_FIX(`window.
|
|
1068
|
+
return CONTRACT_FIX(`window.volterGame.systems.systemAdapters.${slot}`);
|
|
1069
1069
|
}
|
|
1070
1070
|
|
|
1071
1071
|
/**
|
|
@@ -1571,7 +1571,7 @@ export function formatCapabilityCoverageBlocks(
|
|
|
1571
1571
|
* The console door, ONCE PER MOUNT.
|
|
1572
1572
|
*
|
|
1573
1573
|
* The report is derived on demand (the status facet re-derives it on every
|
|
1574
|
-
* read, so `
|
|
1574
|
+
* read, so `volter-game-editor status` never serves a verdict older than the question), which
|
|
1575
1575
|
* makes "print it" a thing that could happen many times — per status poll, per
|
|
1576
1576
|
* re-render, in the limit per frame. The guard is a mount token: the same
|
|
1577
1577
|
* mount's report is emitted once and then never again, and a NEW mount emits
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
type SeamEvidenceVerdict,
|
|
8
8
|
} from '@volter/editor-project/adapter';
|
|
9
9
|
import type {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
VolterGameContract,
|
|
11
|
+
VolterGameLifecycle,
|
|
12
|
+
VolterGameSystems,
|
|
13
13
|
} from '@volter/editor-project/adapter/ingest/game-contract';
|
|
14
14
|
import {
|
|
15
15
|
GAME_CONTRACT_SHAPE,
|
|
@@ -21,7 +21,7 @@ import { liveSeamEvidence } from '@volter/editor-sdk/kit/live-seam-evidence';
|
|
|
21
21
|
const EPOCHS = new WeakMap<object, string>();
|
|
22
22
|
let nextEpoch = 1;
|
|
23
23
|
|
|
24
|
-
export function gameContractEpoch(contract:
|
|
24
|
+
export function gameContractEpoch(contract: VolterGameContract): string {
|
|
25
25
|
const found = EPOCHS.get(contract);
|
|
26
26
|
if (found) return found;
|
|
27
27
|
const epoch = `contract-${nextEpoch++}`;
|
|
@@ -38,7 +38,7 @@ export interface GameContractEvidence {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export function inspectGameContractSeams(options: {
|
|
41
|
-
readonly contract:
|
|
41
|
+
readonly contract: VolterGameContract;
|
|
42
42
|
readonly subject?: string | undefined;
|
|
43
43
|
readonly epoch?: string | undefined;
|
|
44
44
|
readonly receipts?: readonly SeamEvidenceReceipt[] | undefined;
|
|
@@ -46,21 +46,21 @@ export function inspectGameContractSeams(options: {
|
|
|
46
46
|
const subject = options.subject ?? 'game';
|
|
47
47
|
const epoch = options.epoch ?? gameContractEpoch(options.contract);
|
|
48
48
|
const receipts: SeamEvidenceReceipt[] = [
|
|
49
|
-
...inspectSeamShape<
|
|
49
|
+
...inspectSeamShape<VolterGameContract>({
|
|
50
50
|
prefix: 'contract',
|
|
51
51
|
value: options.contract,
|
|
52
52
|
shape: GAME_CONTRACT_SHAPE,
|
|
53
53
|
subject,
|
|
54
54
|
epoch,
|
|
55
55
|
}),
|
|
56
|
-
...inspectSeamShape<
|
|
56
|
+
...inspectSeamShape<VolterGameLifecycle>({
|
|
57
57
|
prefix: 'contract.lifecycle',
|
|
58
58
|
value: options.contract.lifecycle,
|
|
59
59
|
shape: GAME_LIFECYCLE_SHAPE,
|
|
60
60
|
subject,
|
|
61
61
|
epoch,
|
|
62
62
|
}),
|
|
63
|
-
...inspectSeamShape<
|
|
63
|
+
...inspectSeamShape<VolterGameSystems>({
|
|
64
64
|
prefix: 'contract.systems',
|
|
65
65
|
value: options.contract.systems,
|
|
66
66
|
shape: GAME_SYSTEMS_SHAPE,
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* something the product itself already acts on:
|
|
23
23
|
*
|
|
24
24
|
* - `export` reads what the export path ACTUALLY requires of a project, taken
|
|
25
|
-
* from the door's own source: `create-
|
|
25
|
+
* from the door's own source: `create-volter-project/src/deploy.ts` runs
|
|
26
26
|
* `npm run build` in the project (`runProjectBuild`) and stages the `dist/`
|
|
27
27
|
* that emits (`stageBuildOutput`). Ingest roots pass that door because the
|
|
28
28
|
* ingest project's own build is already its standalone game; arbitrary
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* `editor.*` family — every mounted root, native or ingested, gets a row per
|
|
9
9
|
* `AuthoringAdapter` provider. It deliberately keeps ONLY that family, on the
|
|
10
10
|
* grounds that the remaining facts are "the ingested lane's own questions".
|
|
11
|
-
* That is true of `contract.*` (a native game declares no `window.
|
|
11
|
+
* That is true of `contract.*` (a native game declares no `window.volterGame`
|
|
12
12
|
* and never will) and false of `system.*`: a `SystemAdapters` slot is read off
|
|
13
13
|
* the editor's OWN live registry (`authoring/active-systems.ts`), which a
|
|
14
14
|
* native game fills exactly as an ingested one does. So the slots went
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* `networking` is the exception, and it is the whole point. Nothing engine-side
|
|
32
32
|
* builds it: a game registers a `NetworkingAdapter` itself. So its absence is
|
|
33
33
|
* ruled out only by the PROJECT'S OWN FILE — no `server` block in
|
|
34
|
-
* `
|
|
34
|
+
* `volter.project.json` means there is no room to describe. A game that DOES
|
|
35
35
|
* declare a room and binds nothing is `unanswered`, i.e. a standing warning.
|
|
36
36
|
*
|
|
37
37
|
* ## Provenance neutrality
|
|
@@ -59,7 +59,7 @@ import { inspectSystemAdapterSeam } from '@volter/editor-sdk/kit/system-seam-evi
|
|
|
59
59
|
* it. Both facts are read off things that already exist; neither is a guess. */
|
|
60
60
|
export interface NativeProjectFacts {
|
|
61
61
|
/**
|
|
62
|
-
* `
|
|
62
|
+
* `volter.project.json` declares a `server` block (a Colyseus room) — read from
|
|
63
63
|
* the active project view (`project-manager.ts`'s `ActiveProject.server`).
|
|
64
64
|
* The decisive project-level declaration for whether this game is networked.
|
|
65
65
|
*/
|
|
@@ -118,7 +118,7 @@ export interface NativeProjectFacts {
|
|
|
118
118
|
*/
|
|
119
119
|
const SLOT_LIBRARIES: Partial<Record<SystemAdapterSlot, readonly string[]>> = {
|
|
120
120
|
physics: ['@react-three/rapier', '@dimforge/rapier3d-compat', '@dimforge/rapier2d-compat'],
|
|
121
|
-
networking: ['colyseus.js'],
|
|
121
|
+
networking: ['@colyseus/sdk', 'colyseus.js'],
|
|
122
122
|
navigation: ['recast-navigation', '@recast-navigation/core'],
|
|
123
123
|
audio: ['tone', 'howler'],
|
|
124
124
|
};
|
|
@@ -213,7 +213,7 @@ function reasonAcknowledges(reason: string, library: string): boolean {
|
|
|
213
213
|
|
|
214
214
|
/** The file fact that rules `networking` out, in the file's own terms. */
|
|
215
215
|
const NO_SERVER_EVIDENCE =
|
|
216
|
-
"this project's
|
|
216
|
+
"this project's volter.project.json declares no `server` block, so there is no room for a " +
|
|
217
217
|
'NetworkingAdapter to describe';
|
|
218
218
|
|
|
219
219
|
/**
|
|
@@ -251,7 +251,7 @@ const ENGINE_OWNED_EVIDENCE: Partial<Record<SystemAdapterSlot, string>> = {
|
|
|
251
251
|
};
|
|
252
252
|
|
|
253
253
|
/** The fix a native game's author can actually act on, per slot. Never the
|
|
254
|
-
* ingested lane's `window.
|
|
254
|
+
* ingested lane's `window.volterGame` text, which would be false here. */
|
|
255
255
|
function nativeSlotFix(slot: SystemAdapterSlot, facts: NativeProjectFacts): string {
|
|
256
256
|
if (!facts.mounted) {
|
|
257
257
|
return 'nothing is mounted, so the live system registry has not been asked yet — enter Play, or open a project whose roots mount, and the slot reports its real state';
|
|
@@ -349,6 +349,19 @@ export function measureNativeSystemAdapters(
|
|
|
349
349
|
: `declared absent by root "${absence.rootId}": ${absence.reason}${lastMountNote}`,
|
|
350
350
|
};
|
|
351
351
|
}
|
|
352
|
+
// A Colyseus client is the editor's to observe: Play binds its observer of the game's room
|
|
353
|
+
// sockets (`adapter-runtime-bindings.ts`'s `withObservedNetworking`), so an unbound slot while
|
|
354
|
+
// nothing plays is that observer not yet attached, not an adapter the game owes.
|
|
355
|
+
if (slot === 'networking' && libraries.length > 0) {
|
|
356
|
+
return {
|
|
357
|
+
slot,
|
|
358
|
+
state: 'empty',
|
|
359
|
+
attestedBy: 'host',
|
|
360
|
+
evidence:
|
|
361
|
+
`this project ships ${libraries.join(' + ')}; the editor observes its room sockets from ` +
|
|
362
|
+
'Play, and nothing is connected while editing',
|
|
363
|
+
};
|
|
364
|
+
}
|
|
352
365
|
// Nothing declared, and the library IS here: the host's inference below
|
|
353
366
|
// would print a confident absence over real evidence to the contrary. That
|
|
354
367
|
// is an UNANSWERED question — the only state that is a work order — not a
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* ordinary game code and there is no cooperative way for a game to know better.
|
|
10
10
|
*
|
|
11
11
|
* So the game realm's `location` is a facade: reads of the game's own
|
|
12
|
-
* query keep working, host control-plane keys (`play`, `project`, `
|
|
12
|
+
* query keep working, host control-plane keys (`play`, `project`, `volter-*`,
|
|
13
13
|
* …) are stripped from `search`/`href` (bubbo's `getUrlParam('play')` is a
|
|
14
14
|
* debug skip-to-Game; the editor's `?play=1` autoplay is not that flag),
|
|
15
15
|
* and every write that would NAVIGATE is refused with a `console.error`
|
|
16
16
|
* naming the game, the exact assignment, and this mechanism. Refused, never
|
|
17
17
|
* swallowed — a silent no-op would make "the quit button does nothing" an
|
|
18
18
|
* unexplainable bug, and the console line is a real product door
|
|
19
|
-
* (`
|
|
19
|
+
* (`volter-game-editor status` reports page console errors).
|
|
20
20
|
*
|
|
21
21
|
* `hash` is deliberately NOT refused: it changes the URL without leaving the
|
|
22
22
|
* document, and games use it for their own state.
|
|
@@ -49,7 +49,7 @@ const HOST_SEARCH_KEYS = new Set([
|
|
|
49
49
|
]);
|
|
50
50
|
|
|
51
51
|
function isHostSearchKey(key: string): boolean {
|
|
52
|
-
return HOST_SEARCH_KEYS.has(key) || key.startsWith('
|
|
52
|
+
return HOST_SEARCH_KEYS.has(key) || key.startsWith('volter');
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/** The search string a game is allowed to read — host keys removed. */
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { commandLine } from '@volter/editor-sdk/kit/product-command';
|
|
2
2
|
/**
|
|
3
|
-
* THE MODULE LANE of `
|
|
3
|
+
* THE MODULE LANE of `volter-game-editor eval` — reach the RUNNING mount's own module
|
|
4
4
|
* instances, by served path, using URLs retained by their owning realm.
|
|
5
5
|
*
|
|
6
6
|
* WHY THE MOUNT STAMP IS LOAD-BEARING. Project modules are served per mount
|
|
7
|
-
* as `/<path>?
|
|
7
|
+
* as `/<path>?volter-mount=<id>` (`project-module-instance.ts` propagates the
|
|
8
8
|
* id through the whole project subtree), so browser module identity is
|
|
9
9
|
* per-instance BY URL. A bare `import('/src/sim/host.ts')` therefore loads a
|
|
10
10
|
* SECOND, never-ticked copy — the phantom-module trap the old string-command
|
|
@@ -25,15 +25,15 @@ import { commandLine } from '@volter/editor-sdk/kit/product-command';
|
|
|
25
25
|
// with a guard test on the one true owner.
|
|
26
26
|
/** Mirrors `project-module-url.ts`'s PROJECT_MOUNT_QUERY (guarded by test —
|
|
27
27
|
* the two must never drift). */
|
|
28
|
-
const PROJECT_MOUNT_QUERY = '
|
|
28
|
+
const PROJECT_MOUNT_QUERY = 'volter-mount';
|
|
29
29
|
|
|
30
30
|
let focusedInstanceProvider: (() => string) | null = null;
|
|
31
31
|
/** The packaged shell and source-served contributions share the host realm owner. */
|
|
32
32
|
function moduleUrls(instanceId: string): readonly string[] {
|
|
33
33
|
const host = globalThis as typeof globalThis & {
|
|
34
|
-
|
|
34
|
+
__volterGameModuleUrls?: (id: string) => readonly string[];
|
|
35
35
|
};
|
|
36
|
-
return host.
|
|
36
|
+
return host.__volterGameModuleUrls?.(instanceId) ?? [];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** Wired once by the editor shell (`command-listener.ts`) at boot. */
|
|
@@ -76,7 +76,7 @@ export function loadedGameModulePaths(instanceId: string): string[] {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
* The `modules` binding of `
|
|
79
|
+
* The `modules` binding of `volter-game-editor eval`'s `game.run` scope and of the
|
|
80
80
|
* `game-eval` op — a RESOLVER, `modules('src/sim/host.ts')`, that also
|
|
81
81
|
* ENUMERATES what it can resolve.
|
|
82
82
|
*
|
|
@@ -70,7 +70,7 @@ function captureOf(options?: boolean | AddEventListenerOptions | EventListenerOp
|
|
|
70
70
|
|
|
71
71
|
/** Mark carrying the text this observer last wrote, so its own write is not
|
|
72
72
|
* re-scoped and a game's next rewrite is. */
|
|
73
|
-
export const SCOPED_STYLE_MARK = '
|
|
73
|
+
export const SCOPED_STYLE_MARK = 'volterScopedCss';
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* The `@scope` rewriter, loaded on FIRST USE — the one page-side door to the
|
|
@@ -128,7 +128,7 @@ export function loadGameCssScoper(): Promise<(css: string) => string> {
|
|
|
128
128
|
* `url.fileURLToPath/pathToFileURL`,
|
|
129
129
|
* `source-map-js.SourceMapConsumer/SourceMapGenerator` — straight into the
|
|
130
130
|
* game realm's console, which made the console-silence contract unreachable in
|
|
131
|
-
* a fresh scaffold and every `
|
|
131
|
+
* a fresh scaffold and every `volter` verb exit non-zero (measured three times:
|
|
132
132
|
* the donut, cold-barrel and lit-lanes probes). The chain was
|
|
133
133
|
* `play-mode.enterPlayModeInner` -> `gated-globals.setGameSurface` ->
|
|
134
134
|
* `GameRealmPage.surface` -> `observeInjectedStyles` -> `import('../server/
|
|
@@ -356,7 +356,7 @@ export class GameRealmPage {
|
|
|
356
356
|
if (!surface) return null;
|
|
357
357
|
if (!this.headElement || this.headElement.parentElement !== surface) {
|
|
358
358
|
const head = document.createElement('div');
|
|
359
|
-
head.dataset['
|
|
359
|
+
head.dataset['volterGameHead'] = '';
|
|
360
360
|
head.hidden = true;
|
|
361
361
|
surface.prepend(head);
|
|
362
362
|
this.headElement = head;
|
|
@@ -26,7 +26,7 @@ import { disposeGameRealm } from './gated-globals';
|
|
|
26
26
|
*
|
|
27
27
|
* The report is the point: a game that released everything it registered is
|
|
28
28
|
* silent, and a game that did not is named, with the count, on the editor
|
|
29
|
-
* console (and therefore in `
|
|
29
|
+
* console (and therefore in `volter-game-editor console`). The editor reclaims either way.
|
|
30
30
|
*/
|
|
31
31
|
export function reclaimGameRealm(id: string, name: string): void {
|
|
32
32
|
const released = disposeGameRealm(id);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Live gameplay recording over the SAME clean game compositor used by
|
|
3
|
-
* `
|
|
3
|
+
* `volter-game-editor screenshot`: world canvases + DOM HUD, no editor chrome.
|
|
4
4
|
*
|
|
5
5
|
* This is intentionally a browser primitive, not a CLI workflow. The page
|
|
6
6
|
* owns `MediaRecorder` and the live pixels/audio, streams bounded chunks to
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* A counter for it was written, shipped and REMOVED in the same afternoon: it
|
|
22
22
|
* keyed on `domOverlays === 0` and therefore reported a clean zero over clips
|
|
23
23
|
* visibly missing their HUD. A measurement that can only ever say "fine" is
|
|
24
|
-
* worse than none, so the limit is stated — here, and in the ack `
|
|
24
|
+
* worse than none, so the limit is stated — here, and in the ack `volter-game-editor stop`
|
|
25
25
|
* prints — rather than counted. Read the world layer as complete evidence and
|
|
26
26
|
* the HUD as present-when-present.
|
|
27
27
|
*/
|
|
@@ -62,7 +62,7 @@ export interface GameplayRecordingStartOptions {
|
|
|
62
62
|
readonly canvasFrame?: CaptureOptions['canvasFrame'];
|
|
63
63
|
readonly audio?: AudioRecordingHandle | null | undefined;
|
|
64
64
|
/**
|
|
65
|
-
* The file this recording lands in, under the project's `.
|
|
65
|
+
* The file this recording lands in, under the project's `.volter/recordings/`.
|
|
66
66
|
* During Play, absent is named after the Gameplay Session; outside one it
|
|
67
67
|
* falls back to `play-latest.webm`. A name is the caller's own keepsake. The server owns the literals — see
|
|
68
68
|
* `editor-server.ts`'s `/__editor/recording/start`.
|
|
@@ -211,7 +211,7 @@ interface FrameClock {
|
|
|
211
211
|
* `setInterval` on the page is the obvious pacer and it is the wrong one for
|
|
212
212
|
* the case this recorder exists to serve. Chrome clamps a background tab's
|
|
213
213
|
* timers to 1 Hz, and to roughly 1/min once the tab has been hidden for five
|
|
214
|
-
* minutes — and an agent's `
|
|
214
|
+
* minutes — and an agent's `volter-game-editor edit` tab is hidden BY DESIGN (worktree
|
|
215
215
|
* sessions open behind the human's window; see `after-paint.ts` for the same
|
|
216
216
|
* fact stated for rAF). A 30 fps request would encode as a handful of frames.
|
|
217
217
|
*
|
|
@@ -370,7 +370,7 @@ function samplePreviewFrame(recording: ActiveRecording, mediaTimeMs: number): vo
|
|
|
370
370
|
*
|
|
371
371
|
* The refusal this replaced ("show the editor tab, then start recording")
|
|
372
372
|
* exempted exactly the sessions the recorder exists for: an agent's
|
|
373
|
-
* `
|
|
373
|
+
* `volter-game-editor edit` tab opens behind the human's window and stays hidden for the
|
|
374
374
|
* whole run, so every unattended playthrough — the ones with no human to
|
|
375
375
|
* describe what happened — would have produced no evidence at all. What a
|
|
376
376
|
* hidden tab actually costs is two things, and both are handled rather than
|