@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,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE COVERAGE UNIT — one derivation for every printer.
|
|
3
|
+
*
|
|
4
|
+
* Two numbers have been published for the same template walk: "editor 11 of 42"
|
|
5
|
+
* and "editor 7 of 21". 42 is root-instances (21 editor seams × 2 graded
|
|
6
|
+
* roots). 21 is distinct capabilities. Neither printer said which, so a
|
|
7
|
+
* legitimate applicability change (#2238) was indistinguishable from a
|
|
8
|
+
* silently more-forgiving instrument.
|
|
9
|
+
*
|
|
10
|
+
* This module is the one owner of both numbers. A capability is a unique
|
|
11
|
+
* seam; a root-instance is one row (one seam on one root). A capability is a
|
|
12
|
+
* gap if ANY applicable root is short of it — gap-wins, so a per-root miss
|
|
13
|
+
* cannot vanish into a sibling's ok. An all-N-A seam is not a capability of
|
|
14
|
+
* this session (that is the applicability rule, not a silent drop).
|
|
15
|
+
*
|
|
16
|
+
* Pure: no editor session, no filesystem. Callers that already hold rows
|
|
17
|
+
* (console, CLI banner, doctor, portfolio) format them here.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export interface CoverageAccountingRow {
|
|
21
|
+
readonly seam: string;
|
|
22
|
+
readonly status: string;
|
|
23
|
+
/** Root this row belongs to, when the same seam appears once per mount. */
|
|
24
|
+
readonly subject?: string | null;
|
|
25
|
+
/** Fallback for subject when the union stamped `[label] ` onto `detail`. */
|
|
26
|
+
readonly detail?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CoverageFamilyAccounting {
|
|
30
|
+
readonly family: string;
|
|
31
|
+
/** Distinct applicable seams (the capabilities unit). */
|
|
32
|
+
readonly capabilities: number;
|
|
33
|
+
/** Distinct seams with a gap on any applicable root (gap-wins). */
|
|
34
|
+
readonly capabilityGaps: number;
|
|
35
|
+
/** Rows in this family — the root-instances unit, including N-A. */
|
|
36
|
+
readonly rootInstances: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CoverageAccounting {
|
|
40
|
+
readonly capabilities: number;
|
|
41
|
+
readonly capabilityGaps: number;
|
|
42
|
+
readonly rootsGraded: number;
|
|
43
|
+
readonly rootInstances: number;
|
|
44
|
+
readonly families: readonly CoverageFamilyAccounting[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class CoverageReconcileError extends Error {
|
|
48
|
+
constructor(message: string) {
|
|
49
|
+
super(`coverage-accounting: ${message}`);
|
|
50
|
+
this.name = 'CoverageReconcileError';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The family a seam belongs to: the segment before its first `.`, or the whole
|
|
55
|
+
* seam for the single-word ones (`loop`, `persistence`, `systems`). */
|
|
56
|
+
export function coverageSeamFamily(seam: string): string {
|
|
57
|
+
const dot = seam.indexOf('.');
|
|
58
|
+
return dot === -1 ? seam : seam.slice(0, dot);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Subject stamped by `unionCoverageReport` as `[label] ` on the detail. */
|
|
62
|
+
export function subjectFromDetail(detail: string | undefined): string | null {
|
|
63
|
+
if (!detail) return null;
|
|
64
|
+
const match = /^\[([^\]]+)\] /.exec(detail);
|
|
65
|
+
return match?.[1] ?? null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function rowSubject(row: CoverageAccountingRow): string | null {
|
|
69
|
+
if (row.subject !== undefined && row.subject !== null && row.subject !== '') {
|
|
70
|
+
return row.subject;
|
|
71
|
+
}
|
|
72
|
+
return subjectFromDetail(row.detail);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface SeamFold {
|
|
76
|
+
gap: boolean;
|
|
77
|
+
applicable: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function foldSeam(existing: SeamFold | undefined, status: string): SeamFold {
|
|
81
|
+
const fold = existing ?? { gap: false, applicable: false };
|
|
82
|
+
if (status === 'na') return fold;
|
|
83
|
+
return { gap: fold.gap || status === 'gap', applicable: true };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function familyAccounting(
|
|
87
|
+
family: string,
|
|
88
|
+
rows: readonly CoverageAccountingRow[],
|
|
89
|
+
): CoverageFamilyAccounting {
|
|
90
|
+
const folds = new Map<string, SeamFold>();
|
|
91
|
+
for (const row of rows) {
|
|
92
|
+
folds.set(row.seam, foldSeam(folds.get(row.seam), row.status));
|
|
93
|
+
}
|
|
94
|
+
let capabilities = 0;
|
|
95
|
+
let capabilityGaps = 0;
|
|
96
|
+
for (const fold of folds.values()) {
|
|
97
|
+
if (!fold.applicable) continue;
|
|
98
|
+
capabilities += 1;
|
|
99
|
+
if (fold.gap) capabilityGaps += 1;
|
|
100
|
+
}
|
|
101
|
+
return { family, capabilities, capabilityGaps, rootInstances: rows.length };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Both units, from the same rows. Gap-wins on the capabilities unit so a
|
|
106
|
+
* miss on one root cannot be hidden by another root's ok.
|
|
107
|
+
*/
|
|
108
|
+
export function deriveCoverageAccounting(
|
|
109
|
+
rows: readonly CoverageAccountingRow[],
|
|
110
|
+
): CoverageAccounting {
|
|
111
|
+
const subjects = new Set<string>();
|
|
112
|
+
const familyRows = new Map<string, CoverageAccountingRow[]>();
|
|
113
|
+
for (const row of rows) {
|
|
114
|
+
const subject = rowSubject(row);
|
|
115
|
+
if (subject !== null) subjects.add(subject);
|
|
116
|
+
const family = coverageSeamFamily(row.seam);
|
|
117
|
+
const list = familyRows.get(family);
|
|
118
|
+
if (list) list.push(row);
|
|
119
|
+
else familyRows.set(family, [row]);
|
|
120
|
+
}
|
|
121
|
+
const families = [...familyRows].map(([family, familyList]) =>
|
|
122
|
+
familyAccounting(family, familyList),
|
|
123
|
+
);
|
|
124
|
+
const folds = new Map<string, SeamFold>();
|
|
125
|
+
for (const row of rows) {
|
|
126
|
+
folds.set(row.seam, foldSeam(folds.get(row.seam), row.status));
|
|
127
|
+
}
|
|
128
|
+
let capabilities = 0;
|
|
129
|
+
let capabilityGaps = 0;
|
|
130
|
+
for (const fold of folds.values()) {
|
|
131
|
+
if (!fold.applicable) continue;
|
|
132
|
+
capabilities += 1;
|
|
133
|
+
if (fold.gap) capabilityGaps += 1;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
capabilities,
|
|
137
|
+
capabilityGaps,
|
|
138
|
+
rootsGraded: subjects.size,
|
|
139
|
+
rootInstances: rows.length,
|
|
140
|
+
families,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** `8 of 30 capabilities are MISSING` */
|
|
145
|
+
export function formatCoverageMissingLead(accounting: CoverageAccounting): string {
|
|
146
|
+
return `${accounting.capabilityGaps} of ${accounting.capabilities} capabilities are MISSING`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** `2 roots graded; 51 root-instances` — omitted roots clause when none were labelled. */
|
|
150
|
+
export function formatCoverageDerivation(accounting: CoverageAccounting): string {
|
|
151
|
+
const roots = accounting.rootsGraded > 0 ? `${accounting.rootsGraded} roots graded; ` : '';
|
|
152
|
+
return `${roots}${accounting.rootInstances} root-instances`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* `editor 7 of 21 capabilities (2 roots graded; 42 root-instances)` when the
|
|
157
|
+
* family is counted per-root; `system 0 of 7 capabilities` when instances
|
|
158
|
+
* already equal capabilities.
|
|
159
|
+
*/
|
|
160
|
+
export function formatCoverageFamilyBreakdown(
|
|
161
|
+
family: CoverageFamilyAccounting,
|
|
162
|
+
opts: { readonly rootsGraded?: number } = {},
|
|
163
|
+
): string {
|
|
164
|
+
const roots = opts.rootsGraded ?? 0;
|
|
165
|
+
const showInstances = family.rootInstances !== family.capabilities || roots > 1;
|
|
166
|
+
const head = `${family.family} ${family.capabilityGaps} of ${family.capabilities} capabilities`;
|
|
167
|
+
if (!showInstances) return head;
|
|
168
|
+
const rootsBit = roots > 1 ? `${roots} roots graded; ` : '';
|
|
169
|
+
return `${head} (${rootsBit}${family.rootInstances} root-instances)`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function familyOpts(
|
|
173
|
+
family: CoverageFamilyAccounting,
|
|
174
|
+
accounting: CoverageAccounting,
|
|
175
|
+
): { readonly rootsGraded?: number } {
|
|
176
|
+
return family.family === 'editor' ? { rootsGraded: accounting.rootsGraded } : {};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** THE sentence. Head names the subject; the rest is this module's unit. */
|
|
180
|
+
export function formatCoverageGapHeadline(head: string, accounting: CoverageAccounting): string {
|
|
181
|
+
const breakdown = accounting.families
|
|
182
|
+
.map((family) => formatCoverageFamilyBreakdown(family, familyOpts(family, accounting)))
|
|
183
|
+
.join(', ');
|
|
184
|
+
return `${head} — ${formatCoverageMissingLead(accounting)} (${formatCoverageDerivation(accounting)}: ${breakdown}).`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface ParsedCoverageHeadline {
|
|
188
|
+
readonly capabilityGaps: number;
|
|
189
|
+
readonly capabilities: number;
|
|
190
|
+
readonly rootsGraded: number;
|
|
191
|
+
readonly rootInstances: number;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Pull the unit numbers back out of a headline so a report can fail closed. */
|
|
195
|
+
export function parseCoverageHeadline(headline: string): ParsedCoverageHeadline {
|
|
196
|
+
const lead = /(\d+) of (\d+) capabilities are MISSING/.exec(headline);
|
|
197
|
+
if (!lead) {
|
|
198
|
+
throw new CoverageReconcileError(
|
|
199
|
+
'headline does not state the capabilities unit (missing "N of M capabilities are MISSING")',
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const roots = /(\d+) roots graded/.exec(headline);
|
|
203
|
+
const instances = /(\d+) root-instances/.exec(headline);
|
|
204
|
+
if (!instances) {
|
|
205
|
+
throw new CoverageReconcileError('headline does not state root-instances');
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
capabilityGaps: Number(lead[1]),
|
|
209
|
+
capabilities: Number(lead[2]),
|
|
210
|
+
rootsGraded: roots ? Number(roots[1]) : 0,
|
|
211
|
+
rootInstances: Number(instances[1]),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* A report whose members cannot reconcile with its headline is a broken
|
|
217
|
+
* report. Detail rows are the source; the headline must be derivable from
|
|
218
|
+
* them, and the headline's numbers must be those derived values.
|
|
219
|
+
*/
|
|
220
|
+
export function assertCoverageReconciles(
|
|
221
|
+
rows: readonly CoverageAccountingRow[],
|
|
222
|
+
headline: string,
|
|
223
|
+
): void {
|
|
224
|
+
const derived = deriveCoverageAccounting(rows);
|
|
225
|
+
const parsed = parseCoverageHeadline(headline);
|
|
226
|
+
if (parsed.capabilityGaps !== derived.capabilityGaps) {
|
|
227
|
+
throw new CoverageReconcileError(
|
|
228
|
+
`headline capability-gaps ${parsed.capabilityGaps} !== row-derived ${derived.capabilityGaps}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
if (parsed.capabilities !== derived.capabilities) {
|
|
232
|
+
throw new CoverageReconcileError(
|
|
233
|
+
`headline capabilities ${parsed.capabilities} !== row-derived ${derived.capabilities}`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
if (parsed.rootInstances !== derived.rootInstances) {
|
|
237
|
+
throw new CoverageReconcileError(
|
|
238
|
+
`headline root-instances ${parsed.rootInstances} !== row-derived ${derived.rootInstances}`,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
if (parsed.rootsGraded !== derived.rootsGraded) {
|
|
242
|
+
throw new CoverageReconcileError(
|
|
243
|
+
`headline roots-graded ${parsed.rootsGraded} !== row-derived ${derived.rootsGraded}`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const familyInstanceSum = derived.families.reduce((sum, family) => sum + family.rootInstances, 0);
|
|
247
|
+
if (familyInstanceSum !== derived.rootInstances) {
|
|
248
|
+
throw new CoverageReconcileError(
|
|
249
|
+
`family root-instances ${familyInstanceSum} !== ${derived.rootInstances}`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
const familyCapabilitySum = derived.families.reduce(
|
|
253
|
+
(sum, family) => sum + family.capabilities,
|
|
254
|
+
0,
|
|
255
|
+
);
|
|
256
|
+
if (familyCapabilitySum !== derived.capabilities) {
|
|
257
|
+
throw new CoverageReconcileError(
|
|
258
|
+
`family capabilities ${familyCapabilitySum} !== ${derived.capabilities}`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Current-epoch evidence for the game→host contract. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
gradeSeamEvidence,
|
|
5
|
+
inspectSeamShape,
|
|
6
|
+
type SeamEvidenceReceipt,
|
|
7
|
+
type SeamEvidenceVerdict,
|
|
8
|
+
} from '@volter/editor-project/adapter';
|
|
9
|
+
import type {
|
|
10
|
+
VgaiGameContract,
|
|
11
|
+
VgaiGameLifecycle,
|
|
12
|
+
VgaiGameSystems,
|
|
13
|
+
} from '@volter/editor-project/adapter/ingest/game-contract';
|
|
14
|
+
import {
|
|
15
|
+
GAME_CONTRACT_SHAPE,
|
|
16
|
+
GAME_LIFECYCLE_SHAPE,
|
|
17
|
+
GAME_SYSTEMS_SHAPE,
|
|
18
|
+
} from '@volter/editor-project/adapter/ingest/game-contract-seams';
|
|
19
|
+
import { liveSeamEvidence } from '@volter/editor-core/coverage/live-seam-evidence';
|
|
20
|
+
|
|
21
|
+
const EPOCHS = new WeakMap<object, string>();
|
|
22
|
+
let nextEpoch = 1;
|
|
23
|
+
|
|
24
|
+
export function gameContractEpoch(contract: VgaiGameContract): string {
|
|
25
|
+
const found = EPOCHS.get(contract);
|
|
26
|
+
if (found) return found;
|
|
27
|
+
const epoch = `contract-${nextEpoch++}`;
|
|
28
|
+
EPOCHS.set(contract, epoch);
|
|
29
|
+
return epoch;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface GameContractEvidence {
|
|
33
|
+
readonly root: SeamEvidenceVerdict;
|
|
34
|
+
readonly start: SeamEvidenceVerdict;
|
|
35
|
+
readonly pause: SeamEvidenceVerdict;
|
|
36
|
+
readonly resume: SeamEvidenceVerdict;
|
|
37
|
+
readonly systems: SeamEvidenceVerdict;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function inspectGameContractSeams(options: {
|
|
41
|
+
readonly contract: VgaiGameContract;
|
|
42
|
+
readonly subject?: string | undefined;
|
|
43
|
+
readonly epoch?: string | undefined;
|
|
44
|
+
readonly receipts?: readonly SeamEvidenceReceipt[] | undefined;
|
|
45
|
+
}): GameContractEvidence {
|
|
46
|
+
const subject = options.subject ?? 'game';
|
|
47
|
+
const epoch = options.epoch ?? gameContractEpoch(options.contract);
|
|
48
|
+
const receipts: SeamEvidenceReceipt[] = [
|
|
49
|
+
...inspectSeamShape<VgaiGameContract>({
|
|
50
|
+
prefix: 'contract',
|
|
51
|
+
value: options.contract,
|
|
52
|
+
shape: GAME_CONTRACT_SHAPE,
|
|
53
|
+
subject,
|
|
54
|
+
epoch,
|
|
55
|
+
}),
|
|
56
|
+
...inspectSeamShape<VgaiGameLifecycle>({
|
|
57
|
+
prefix: 'contract.lifecycle',
|
|
58
|
+
value: options.contract.lifecycle,
|
|
59
|
+
shape: GAME_LIFECYCLE_SHAPE,
|
|
60
|
+
subject,
|
|
61
|
+
epoch,
|
|
62
|
+
}),
|
|
63
|
+
...inspectSeamShape<VgaiGameSystems>({
|
|
64
|
+
prefix: 'contract.systems',
|
|
65
|
+
value: options.contract.systems,
|
|
66
|
+
shape: GAME_SYSTEMS_SHAPE,
|
|
67
|
+
subject,
|
|
68
|
+
epoch,
|
|
69
|
+
}),
|
|
70
|
+
...liveSeamEvidence.receipts({ subject, epoch }),
|
|
71
|
+
...(options.receipts ?? []),
|
|
72
|
+
];
|
|
73
|
+
const verdict = (seam: string, required: 'operation' | 'effect'): SeamEvidenceVerdict =>
|
|
74
|
+
gradeSeamEvidence({ seam, subject, epoch, required, receipts });
|
|
75
|
+
return {
|
|
76
|
+
root: verdict('contract.root', 'effect'),
|
|
77
|
+
start: verdict('contract.lifecycle.start', 'effect'),
|
|
78
|
+
pause: verdict('contract.lifecycle.pause', 'effect'),
|
|
79
|
+
resume: verdict('contract.lifecycle.resume', 'effect'),
|
|
80
|
+
systems: verdict('contract.systems', 'operation'),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE `project.*` RULES — the pure half of `@volter/editor-game/coverage/live-project-verbs.ts`.
|
|
3
|
+
*
|
|
4
|
+
* ## The defect this closes
|
|
5
|
+
*
|
|
6
|
+
* Derived coverage had exactly two families, and both are pinned to an
|
|
7
|
+
* INTERFACE: `editor.*` to `AuthoringAdapter`'s provider vocabulary
|
|
8
|
+
* (`AUTHORING_PROVIDER_PRESENCE`) and `system.*` to `SystemAdapters`' slots.
|
|
9
|
+
* That is what makes them exhaustive — of what an ADAPTER can be asked. Native
|
|
10
|
+
* verbs like the export/build path are not an adapter's to answer at all,
|
|
11
|
+
* because their subject is the PROJECT rather than a mounted root: whether
|
|
12
|
+
* this project can produce the standalone build that is the ONLY way it runs
|
|
13
|
+
* outside the editor.
|
|
14
|
+
*
|
|
15
|
+
* So a project could be green in both families and silently lack it, which
|
|
16
|
+
* violates the standing rule that an unreached capability is an OPEN warning
|
|
17
|
+
* and never silence.
|
|
18
|
+
*
|
|
19
|
+
* ## Every row is a real fact with a real reader
|
|
20
|
+
*
|
|
21
|
+
* Nothing here is a schema field invented for the report. Each rule reads
|
|
22
|
+
* something the product itself already acts on:
|
|
23
|
+
*
|
|
24
|
+
* - `export` reads what the export path ACTUALLY requires of a project, taken
|
|
25
|
+
* from the door's own source: `create-vgai-project/src/deploy.ts` runs
|
|
26
|
+
* `npm run build` in the project (`runProjectBuild`) and stages the `dist/`
|
|
27
|
+
* that emits (`stageBuildOutput`). Ingest roots pass that door because the
|
|
28
|
+
* ingest project's own build is already its standalone game; arbitrary
|
|
29
|
+
* module roots and unsupported built-ins remain a named refusal. These are
|
|
30
|
+
* static, manifest/package-readable preconditions; neither is a guess.
|
|
31
|
+
*
|
|
32
|
+
* Pure over its inputs, so the whole table is unit-testable with no browser and
|
|
33
|
+
* no live session — same shape and same reason as
|
|
34
|
+
* `coverage/system-adapter-coverage.ts`.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { commandLine } from '@volter/editor-core/product-command';
|
|
38
|
+
import {
|
|
39
|
+
PROJECT_VERB_SLOTS,
|
|
40
|
+
type ProjectVerbMeasurement,
|
|
41
|
+
type ProjectVerbSlot,
|
|
42
|
+
} from './capability-coverage';
|
|
43
|
+
|
|
44
|
+
/** One manifest root, in the terms the export door's wall reads. */
|
|
45
|
+
export interface ProjectRootFact {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
/** `ResolvedAdapter['type']` — `builtin` | `module` | `ingest`. */
|
|
48
|
+
readonly type: string;
|
|
49
|
+
/** `ResolvedAdapter['identity']` — `three`/`dom`/`ingest-three`/… */
|
|
50
|
+
readonly identity: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Everything the rules read. Every field arrives as data, and every `null`
|
|
54
|
+
* means NOBODY LOOKED — never "there is none". */
|
|
55
|
+
export interface ProjectVerbFacts {
|
|
56
|
+
/**
|
|
57
|
+
* The script names the project's `package.json` declares, `[]` when it has a
|
|
58
|
+
* `package.json` with no scripts, and `null` when the question could not be
|
|
59
|
+
* asked (a fetch that has not landed, no server).
|
|
60
|
+
*/
|
|
61
|
+
readonly scripts: readonly string[] | null;
|
|
62
|
+
/** Does the project have a `package.json` at all? `false` with `scripts: []`
|
|
63
|
+
* is a measured absence and reads differently from an empty scripts block. */
|
|
64
|
+
readonly hasPackageJson: boolean;
|
|
65
|
+
/** The manifest's roots as the export wall reads them; `null` ⇒ unasked. */
|
|
66
|
+
readonly roots: readonly ProjectRootFact[] | null;
|
|
67
|
+
/** Why the file-read facts above are `null`, in the fetcher's own words.
|
|
68
|
+
* Shown verbatim, so an unanswered row can never read as a clean one. */
|
|
69
|
+
readonly unavailable: string | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The sentence an unanswered row prints. One place, so all three agree. */
|
|
73
|
+
function unanswered(
|
|
74
|
+
slot: ProjectVerbMeasurement['slot'],
|
|
75
|
+
why: string | null,
|
|
76
|
+
): ProjectVerbMeasurement {
|
|
77
|
+
return {
|
|
78
|
+
slot,
|
|
79
|
+
state: 'unanswered',
|
|
80
|
+
evidence:
|
|
81
|
+
`nobody has read this project's own files yet, so ${slot} is unmeasured — ` +
|
|
82
|
+
(why ?? 'the editor has not asked its dev server for them'),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Is every root this project declares an ingest root? An ingest-only
|
|
87
|
+
* project's own build is already its standalone game, which changes what the
|
|
88
|
+
* export wall says about it. */
|
|
89
|
+
function ingestOnly(roots: readonly ProjectRootFact[]): boolean {
|
|
90
|
+
return roots.length > 0 && roots.every((root) => root.type === 'ingest');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function exportVerb(facts: ProjectVerbFacts): ProjectVerbMeasurement {
|
|
94
|
+
const { scripts, roots } = facts;
|
|
95
|
+
if (scripts === null || roots === null) return unanswered('export', facts.unavailable);
|
|
96
|
+
if (!scripts.includes('build')) {
|
|
97
|
+
return {
|
|
98
|
+
slot: 'export',
|
|
99
|
+
state: 'absent',
|
|
100
|
+
evidence: facts.hasPackageJson
|
|
101
|
+
? "this project's package.json declares no `build` script, and the export path runs " +
|
|
102
|
+
'`npm run build` in the project and stages the `dist/` it emits'
|
|
103
|
+
: 'this project has no package.json, so there is no `build` script for the export path to run',
|
|
104
|
+
missing:
|
|
105
|
+
'this project cannot produce a standalone build, so it runs NOWHERE but the editor — the ' +
|
|
106
|
+
'dev-served standalone page refuses an unexported game outright',
|
|
107
|
+
fix: "add the scaffold's `build` script (`build: tsc && vite build`), which is what every deploy path runs",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const refused = roots.filter(
|
|
111
|
+
(root) => root.type !== 'ingest' && (root.type !== 'builtin' || root.identity !== 'three'),
|
|
112
|
+
);
|
|
113
|
+
const wallLeg =
|
|
114
|
+
refused.length > 0
|
|
115
|
+
? `; note that the legacy ${commandLine('deploy')} verb additionally refuses ${refused
|
|
116
|
+
.map((root) => `root "${root.id}" (${root.identity})`)
|
|
117
|
+
.join(
|
|
118
|
+
', ',
|
|
119
|
+
)} — use the project-owned deploy scripts (${commandLine('add deploy-cloudflare')}/\`deploy-vercel\`, then \`npm run deploy\`)`
|
|
120
|
+
: '';
|
|
121
|
+
const deployLeg = scripts.includes('deploy')
|
|
122
|
+
? ', and a `deploy` script to ship it'
|
|
123
|
+
: ` (no \`deploy\` script yet — ${commandLine('add deploy-cloudflare')}/\`deploy-vercel\` adds one)`;
|
|
124
|
+
const ingestLeg = ingestOnly(roots)
|
|
125
|
+
? '; this ingest project ships the standalone bundle produced by its own build unchanged'
|
|
126
|
+
: '';
|
|
127
|
+
return {
|
|
128
|
+
slot: 'export',
|
|
129
|
+
state: 'present',
|
|
130
|
+
evidence: `this project declares a \`build\` script, which is what the export path runs to emit the \`dist/\` it stages${deployLeg}${ingestLeg}${wallLeg}`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The terminal state of every native verb for the OPEN PROJECT.
|
|
136
|
+
*
|
|
137
|
+
* Order is `PROJECT_VERB_SLOTS`, so two reports of the same project stay
|
|
138
|
+
* diffable line for line — the same property the other two families have.
|
|
139
|
+
*/
|
|
140
|
+
export function measureProjectVerbs(facts: ProjectVerbFacts): readonly ProjectVerbMeasurement[] {
|
|
141
|
+
// A `Record` keyed by the slot union, so a verb added to the vocabulary fails
|
|
142
|
+
// to compile until it has a rule — the same compile pin the vocabulary itself
|
|
143
|
+
// carries in `coverage/capability-coverage.ts`.
|
|
144
|
+
const rules: Record<ProjectVerbSlot, (f: ProjectVerbFacts) => ProjectVerbMeasurement> = {
|
|
145
|
+
export: exportVerb,
|
|
146
|
+
};
|
|
147
|
+
return PROJECT_VERB_SLOTS.map((slot) => rules[slot](facts));
|
|
148
|
+
}
|