@vgai/engine 0.5.15 → 0.5.17
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/dist/adapter/adapter-module.d.ts +229 -33
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +222 -50
- package/dist/adapter/authoring-seam-contract.d.ts +530 -0
- package/dist/adapter/authoring-seam-contract.d.ts.map +1 -0
- package/dist/adapter/authoring-seam-contract.js +162 -0
- package/dist/adapter/authoring.d.ts +237 -50
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/authoring.js +2 -1
- package/dist/adapter/entrypoint-selection-source.d.ts +35 -0
- package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -0
- package/dist/adapter/entrypoint-selection-source.js +151 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +7 -3
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +145 -27
- package/dist/adapter/first-party-systems.d.ts +13 -3
- package/dist/adapter/first-party-systems.d.ts.map +1 -1
- package/dist/adapter/first-party-systems.js +10 -3
- package/dist/adapter/index.d.ts +7 -2
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +4 -0
- package/dist/adapter/ingest/contract-system-adapters.d.ts +1 -1
- package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
- package/dist/adapter/ingest/contract-system-adapters.js +13 -25
- package/dist/adapter/ingest/game-contract-seams.d.ts +214 -0
- package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -0
- package/dist/adapter/ingest/game-contract-seams.js +62 -0
- package/dist/adapter/ingest/game-contract.d.ts +5 -3
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +4 -1
- package/dist/adapter/ingest/merge-debug-adapters.d.ts +52 -0
- package/dist/adapter/ingest/merge-debug-adapters.d.ts.map +1 -0
- package/dist/adapter/ingest/merge-debug-adapters.js +141 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts +56 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts.map +1 -0
- package/dist/adapter/ingest/observation-debug-adapter.js +131 -0
- package/dist/adapter/ingest/scene-capture.d.ts +38 -13
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +101 -24
- package/dist/adapter/ingest/visible-capture-window.d.ts +22 -5
- package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
- package/dist/adapter/ingest/visible-capture-window.js +46 -7
- package/dist/adapter/native-debug-module.d.ts +43 -0
- package/dist/adapter/native-debug-module.d.ts.map +1 -0
- package/dist/adapter/native-debug-module.js +230 -0
- package/dist/adapter/object3d-authoring-subject.d.ts +32 -0
- package/dist/adapter/object3d-authoring-subject.d.ts.map +1 -0
- package/dist/adapter/object3d-authoring-subject.js +25 -0
- package/dist/adapter/root-adapter.d.ts +25 -56
- package/dist/adapter/root-adapter.d.ts.map +1 -1
- package/dist/adapter/root-adapter.js +4 -20
- package/dist/adapter/root-seam-contract.d.ts +129 -0
- package/dist/adapter/root-seam-contract.d.ts.map +1 -0
- package/dist/adapter/root-seam-contract.js +37 -0
- package/dist/adapter/seam-evidence.d.ts +118 -0
- package/dist/adapter/seam-evidence.d.ts.map +1 -0
- package/dist/adapter/seam-evidence.js +231 -0
- package/dist/adapter/setup-three-root-adapter.d.ts +2 -3
- package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.js +11 -12
- package/dist/adapter/system-adapter.d.ts +6 -1
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/adapter/system-seam-contract.d.ts +354 -0
- package/dist/adapter/system-seam-contract.d.ts.map +1 -0
- package/dist/adapter/system-seam-contract.js +95 -0
- package/dist/animation/xstate-animation-binding.d.ts +6 -0
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
- package/dist/animation/xstate-animation-binding.js +142 -4
- package/dist/asset-formats/collider.d.ts +1 -1
- package/dist/asset-formats/mesh.d.ts +2 -2
- package/dist/asset-formats/particles.d.ts +1 -1
- package/dist/canvas-react/engine-bridge.d.ts +6 -6
- package/dist/canvas-react/engine-bridge.d.ts.map +1 -1
- package/dist/canvas-react/engine-bridge.js +2 -2
- package/dist/canvas-react/index.d.ts +2 -2
- package/dist/canvas-react/index.d.ts.map +1 -1
- package/dist/canvas-react/index.js +1 -1
- package/dist/canvas-react/pixi-react-adapter.d.ts +15 -9
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-adapter.js +129 -64
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +4 -0
- package/dist/canvas-react/world-context.d.ts +22 -12
- package/dist/canvas-react/world-context.d.ts.map +1 -1
- package/dist/canvas-react/world-context.js +12 -51
- package/dist/data/curve.d.ts +1 -1
- package/dist/data/curve.js +2 -2
- package/dist/dev/instruments.d.ts +4 -4
- package/dist/dev/instruments.js +4 -4
- package/dist/dev/register-render-vitals.d.ts +2 -2
- package/dist/dev/register-render-vitals.js +2 -2
- package/dist/dev/static-batch-advisor.d.ts +1 -1
- package/dist/dev/static-batch-advisor.js +2 -2
- package/dist/ecs/user-data.d.ts +5 -0
- package/dist/ecs/user-data.d.ts.map +1 -1
- package/dist/ecs/user-data.js +3 -0
- package/dist/input/input-manager.d.ts +64 -0
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +149 -0
- package/dist/manifest/load.d.ts +0 -3
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +0 -4
- package/dist/manifest/schema.d.ts +9 -11
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +2 -23
- package/dist/pixi/authoring.d.ts +33 -0
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +301 -2
- package/dist/pixi/ingest.d.ts +2 -1
- package/dist/pixi/ingest.d.ts.map +1 -1
- package/dist/pixi/ingest.js +4 -2
- package/dist/pixi/scene-capture.d.ts +9 -1
- package/dist/pixi/scene-capture.d.ts.map +1 -1
- package/dist/pixi/scene-capture.js +9 -6
- package/dist/react/world-state.d.ts +12 -0
- package/dist/react/world-state.d.ts.map +1 -1
- package/dist/react/world-state.js +14 -0
- package/dist/render/particles-factory.d.ts +13 -6
- package/dist/render/particles-factory.d.ts.map +1 -1
- package/dist/render/particles-factory.js +6 -3
- package/dist/render/soft-particle-depth.d.ts +3 -3
- package/dist/render/soft-particle-depth.d.ts.map +1 -1
- package/dist/render/soft-particle-depth.js +8 -13
- package/dist/runtime/create-runtime.d.ts +25 -62
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +58 -105
- package/dist/runtime/dev-build.d.ts +37 -0
- package/dist/runtime/dev-build.d.ts.map +1 -0
- package/dist/runtime/dev-build.js +49 -0
- package/dist/runtime/game.d.ts +38 -72
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +40 -55
- package/dist/runtime/mount-game.d.ts.map +1 -1
- package/dist/runtime/mount-game.js +12 -1
- package/dist/runtime/mount-manifest.d.ts +0 -13
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +5 -25
- package/dist/runtime/types.d.ts +18 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/setup/setup-renderer.d.ts +3 -4
- package/dist/setup/setup-renderer.d.ts.map +1 -1
- package/dist/setup/setup-renderer.js +3 -4
- package/dist/world3d-react/engine-bridge.d.ts +1 -1
- package/dist/world3d-react/engine-bridge.d.ts.map +1 -1
- package/dist/world3d-react/engine-bridge.js +29 -4
- package/dist/world3d-react/index.d.ts +1 -1
- package/dist/world3d-react/index.js +1 -1
- package/dist/world3d-react/r3f-adapter.d.ts +7 -0
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +132 -28
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +14 -0
- package/dist/world3d-react/rapier-physics-bridge.d.ts +4 -4
- package/dist/world3d-react/rapier-physics-bridge.js +3 -3
- package/dist/world3d-react/world-context.d.ts +52 -3
- package/dist/world3d-react/world-context.d.ts.map +1 -1
- package/dist/world3d-react/world-context.js +94 -64
- package/dist-config/config.js.map +1 -1
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +8 -8
- package/schemas/vgai-project.schema.json +1 -6
- package/src/adapter/adapter-module.ts +405 -72
- package/src/adapter/authoring-seam-contract.ts +211 -0
- package/src/adapter/authoring.ts +242 -50
- package/src/adapter/entrypoint-selection-source.ts +159 -0
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +162 -36
- package/src/adapter/first-party-systems.ts +17 -3
- package/src/adapter/index.ts +51 -1
- package/src/adapter/ingest/contract-system-adapters.ts +26 -30
- package/src/adapter/ingest/game-contract-seams.ts +85 -0
- package/src/adapter/ingest/game-contract.ts +9 -4
- package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
- package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
- package/src/adapter/ingest/scene-capture.ts +126 -23
- package/src/adapter/ingest/visible-capture-window.ts +63 -9
- package/src/adapter/native-debug-module.ts +307 -0
- package/src/adapter/object3d-authoring-subject.ts +50 -0
- package/src/adapter/root-adapter.ts +25 -56
- package/src/adapter/root-seam-contract.ts +57 -0
- package/src/adapter/seam-evidence.ts +336 -0
- package/src/adapter/setup-three-root-adapter.ts +13 -15
- package/src/adapter/system-adapter.ts +6 -1
- package/src/adapter/system-seam-contract.ts +121 -0
- package/src/animation/xstate-animation-binding.ts +151 -4
- package/src/canvas-react/engine-bridge.ts +6 -6
- package/src/canvas-react/index.ts +2 -1
- package/src/canvas-react/pixi-react-adapter.tsx +143 -77
- package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
- package/src/canvas-react/world-context.ts +26 -101
- package/src/data/curve.ts +2 -2
- package/src/dev/instruments.ts +4 -4
- package/src/dev/register-render-vitals.ts +2 -2
- package/src/dev/static-batch-advisor.ts +2 -2
- package/src/ecs/user-data.ts +5 -0
- package/src/input/input-manager.ts +145 -0
- package/src/manifest/load.ts +0 -7
- package/src/manifest/schema.ts +2 -25
- package/src/pixi/authoring.ts +329 -2
- package/src/pixi/ingest.ts +9 -2
- package/src/pixi/scene-capture.ts +20 -5
- package/src/react/world-state.tsx +15 -0
- package/src/render/particles-factory.ts +16 -6
- package/src/render/soft-particle-depth.ts +8 -13
- package/src/runtime/create-runtime.ts +79 -158
- package/src/runtime/dev-build.ts +49 -0
- package/src/runtime/game.ts +72 -113
- package/src/runtime/mount-game.ts +19 -1
- package/src/runtime/mount-manifest.ts +5 -37
- package/src/runtime/types.ts +22 -0
- package/src/setup/setup-renderer.ts +3 -4
- package/src/world3d-react/engine-bridge.ts +29 -4
- package/src/world3d-react/index.ts +1 -1
- package/src/world3d-react/r3f-adapter.tsx +140 -31
- package/src/world3d-react/r3f-root-factory.tsx +14 -0
- package/src/world3d-react/rapier-physics-bridge.tsx +4 -4
- package/src/world3d-react/world-context.ts +111 -87
- package/dist/runtime/dev-layers.d.ts +0 -34
- package/dist/runtime/dev-layers.d.ts.map +0 -1
- package/dist/runtime/dev-layers.js +0 -46
- package/src/runtime/dev-layers.ts +0 -46
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence that a protocol seam tells the truth.
|
|
3
|
+
*
|
|
4
|
+
* A property existing on an object proves only SHAPE. It does not prove that
|
|
5
|
+
* calling it works, that its effect reaches the consumer, or that an authored
|
|
6
|
+
* effect survives a cold remount. Those are deliberately separate stages so a
|
|
7
|
+
* declaration can never be promoted to a green capability merely because it
|
|
8
|
+
* has the right spelling.
|
|
9
|
+
*
|
|
10
|
+
* Receipts are scoped to a SUBJECT and an EPOCH. A provider from a prior mount
|
|
11
|
+
* is not evidence about the current mount, even when both mounts use the same
|
|
12
|
+
* project and node ids. This is the generic answer to stale adapter registries,
|
|
13
|
+
* late provider registration, and project switching: old receipts simply do
|
|
14
|
+
* not participate in the new verdict.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const SEAM_PROOF_STAGES = ['claim', 'shape', 'operation', 'effect', 'round-trip'] as const;
|
|
18
|
+
|
|
19
|
+
export type SeamProofStage = (typeof SEAM_PROOF_STAGES)[number];
|
|
20
|
+
export type SeamEvidenceOutcome = 'pass' | 'fail' | 'absent';
|
|
21
|
+
export type SeamEvidenceSource =
|
|
22
|
+
| 'declaration'
|
|
23
|
+
| 'shape-check'
|
|
24
|
+
| 'consumer'
|
|
25
|
+
| 'conformance-probe'
|
|
26
|
+
| 'round-trip';
|
|
27
|
+
|
|
28
|
+
/** One directly observed fact. `detail` names the observation, never a claim
|
|
29
|
+
* such as "supported" with no described bar behind it. */
|
|
30
|
+
export interface SeamEvidenceReceipt {
|
|
31
|
+
readonly seam: string;
|
|
32
|
+
readonly subject: string;
|
|
33
|
+
readonly epoch: string;
|
|
34
|
+
readonly stage: SeamProofStage;
|
|
35
|
+
readonly outcome: SeamEvidenceOutcome;
|
|
36
|
+
readonly source: SeamEvidenceSource;
|
|
37
|
+
readonly detail: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type SeamEvidenceState = 'verified' | 'failed' | 'absent' | 'unverified' | 'not-applicable';
|
|
41
|
+
|
|
42
|
+
export interface SeamEvidenceVerdict {
|
|
43
|
+
readonly seam: string;
|
|
44
|
+
readonly subject: string;
|
|
45
|
+
readonly epoch: string;
|
|
46
|
+
readonly required: SeamProofStage;
|
|
47
|
+
readonly state: SeamEvidenceState;
|
|
48
|
+
/** The strongest current-epoch receipt, or null when nobody has measured the
|
|
49
|
+
* seam. A not-applicable verdict carries its reason here instead. */
|
|
50
|
+
readonly receipt: SeamEvidenceReceipt | null;
|
|
51
|
+
readonly detail: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const STAGE_RANK: Readonly<Record<SeamProofStage, number>> = {
|
|
55
|
+
claim: 0,
|
|
56
|
+
shape: 1,
|
|
57
|
+
operation: 2,
|
|
58
|
+
effect: 3,
|
|
59
|
+
'round-trip': 4,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export function seamStageAtLeast(observed: SeamProofStage, required: SeamProofStage): boolean {
|
|
63
|
+
return STAGE_RANK[observed] >= STAGE_RANK[required];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Grade one seam from receipts for ONE subject epoch.
|
|
68
|
+
*
|
|
69
|
+
* The strongest stage wins. At the same stage, the last receipt wins so a
|
|
70
|
+
* successful retry can close a failure and a later failure can reopen it.
|
|
71
|
+
* Lower-stage evidence can never overrule a stronger observation: a completed
|
|
72
|
+
* cold-remount round trip is not invalidated by a later shape census.
|
|
73
|
+
*/
|
|
74
|
+
export function gradeSeamEvidence(options: {
|
|
75
|
+
readonly seam: string;
|
|
76
|
+
readonly subject: string;
|
|
77
|
+
readonly epoch: string;
|
|
78
|
+
readonly required: SeamProofStage;
|
|
79
|
+
readonly receipts: readonly SeamEvidenceReceipt[];
|
|
80
|
+
readonly notApplicable?: string | undefined;
|
|
81
|
+
}): SeamEvidenceVerdict {
|
|
82
|
+
const { seam, subject, epoch, required } = options;
|
|
83
|
+
if (options.notApplicable !== undefined) {
|
|
84
|
+
return {
|
|
85
|
+
seam,
|
|
86
|
+
subject,
|
|
87
|
+
epoch,
|
|
88
|
+
required,
|
|
89
|
+
state: 'not-applicable',
|
|
90
|
+
receipt: null,
|
|
91
|
+
detail: options.notApplicable,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let strongest: SeamEvidenceReceipt | null = null;
|
|
96
|
+
for (const receipt of options.receipts) {
|
|
97
|
+
if (receipt.seam !== seam || receipt.subject !== subject || receipt.epoch !== epoch) continue;
|
|
98
|
+
if (!strongest || STAGE_RANK[receipt.stage] >= STAGE_RANK[strongest.stage]) {
|
|
99
|
+
strongest = receipt;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!strongest) {
|
|
104
|
+
return {
|
|
105
|
+
seam,
|
|
106
|
+
subject,
|
|
107
|
+
epoch,
|
|
108
|
+
required,
|
|
109
|
+
state: 'unverified',
|
|
110
|
+
receipt: null,
|
|
111
|
+
detail: `no current-epoch evidence has reached the required ${required} stage`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (strongest.outcome === 'fail') {
|
|
115
|
+
return {
|
|
116
|
+
seam,
|
|
117
|
+
subject,
|
|
118
|
+
epoch,
|
|
119
|
+
required,
|
|
120
|
+
state: 'failed',
|
|
121
|
+
receipt: strongest,
|
|
122
|
+
detail: strongest.detail,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (strongest.outcome === 'absent') {
|
|
126
|
+
return {
|
|
127
|
+
seam,
|
|
128
|
+
subject,
|
|
129
|
+
epoch,
|
|
130
|
+
required,
|
|
131
|
+
state: 'absent',
|
|
132
|
+
receipt: strongest,
|
|
133
|
+
detail: strongest.detail,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (!seamStageAtLeast(strongest.stage, required)) {
|
|
137
|
+
return {
|
|
138
|
+
seam,
|
|
139
|
+
subject,
|
|
140
|
+
epoch,
|
|
141
|
+
required,
|
|
142
|
+
state: 'unverified',
|
|
143
|
+
receipt: strongest,
|
|
144
|
+
detail: `${strongest.detail}; this proves ${strongest.stage}, but this seam requires ${required}`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
seam,
|
|
149
|
+
subject,
|
|
150
|
+
epoch,
|
|
151
|
+
required,
|
|
152
|
+
state: 'verified',
|
|
153
|
+
receipt: strongest,
|
|
154
|
+
detail: strongest.detail,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** In-memory current-session ledger. It is evidence, not a project file and
|
|
159
|
+
* therefore introduces no VGAI data format. */
|
|
160
|
+
export class SeamEvidenceLedger {
|
|
161
|
+
readonly #receipts: SeamEvidenceReceipt[] = [];
|
|
162
|
+
|
|
163
|
+
record(receipt: SeamEvidenceReceipt): void {
|
|
164
|
+
if (!receipt.seam || !receipt.subject || !receipt.epoch || !receipt.detail.trim()) {
|
|
165
|
+
throw new Error('seam evidence requires non-empty seam, subject, epoch, and detail');
|
|
166
|
+
}
|
|
167
|
+
this.#receipts.push(receipt);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
receipts(options?: {
|
|
171
|
+
readonly subject?: string | undefined;
|
|
172
|
+
readonly epoch?: string | undefined;
|
|
173
|
+
}): readonly SeamEvidenceReceipt[] {
|
|
174
|
+
return this.#receipts.filter(
|
|
175
|
+
(receipt) =>
|
|
176
|
+
(options?.subject === undefined || receipt.subject === options.subject) &&
|
|
177
|
+
(options?.epoch === undefined || receipt.epoch === options.epoch),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
verdict(options: {
|
|
182
|
+
readonly seam: string;
|
|
183
|
+
readonly subject: string;
|
|
184
|
+
readonly epoch: string;
|
|
185
|
+
readonly required: SeamProofStage;
|
|
186
|
+
readonly notApplicable?: string | undefined;
|
|
187
|
+
}): SeamEvidenceVerdict {
|
|
188
|
+
return gradeSeamEvidence({ ...options, receipts: this.#receipts });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
clearSubject(subject: string): void {
|
|
192
|
+
for (let index = this.#receipts.length - 1; index >= 0; index -= 1) {
|
|
193
|
+
if (this.#receipts[index]?.subject === subject) this.#receipts.splice(index, 1);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
clear(): void {
|
|
198
|
+
this.#receipts.length = 0;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
type NonUndefined<T> = Exclude<T, undefined>;
|
|
203
|
+
type MemberKind<T> = NonUndefined<T> extends (...args: never[]) => unknown ? 'function' : 'value';
|
|
204
|
+
|
|
205
|
+
/** A compiler-pinned description of every member of `T`. Adding, removing, or
|
|
206
|
+
* changing the optionality/kind of a contract member breaks its descriptor. */
|
|
207
|
+
export type SeamShape<T> = {
|
|
208
|
+
readonly [K in keyof T]-?: {
|
|
209
|
+
readonly optional: undefined extends T[K] ? true : false;
|
|
210
|
+
readonly kind: MemberKind<T[K]>;
|
|
211
|
+
readonly required: SeamProofStage;
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
export function defineSeamShape<T>() {
|
|
216
|
+
return <const TShape extends SeamShape<T>>(shape: TShape): TShape => shape;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function memberShapeReceipt(options: {
|
|
220
|
+
readonly seam: string;
|
|
221
|
+
readonly value: unknown;
|
|
222
|
+
readonly optional: boolean;
|
|
223
|
+
readonly kind: 'function' | 'value';
|
|
224
|
+
readonly subject: string;
|
|
225
|
+
readonly epoch: string;
|
|
226
|
+
}): SeamEvidenceReceipt {
|
|
227
|
+
if (options.value === undefined) {
|
|
228
|
+
return {
|
|
229
|
+
seam: options.seam,
|
|
230
|
+
subject: options.subject,
|
|
231
|
+
epoch: options.epoch,
|
|
232
|
+
stage: 'shape',
|
|
233
|
+
outcome: options.optional ? 'absent' : 'fail',
|
|
234
|
+
source: 'shape-check',
|
|
235
|
+
detail: options.optional
|
|
236
|
+
? `${options.seam} is not exposed by this subject`
|
|
237
|
+
: `${options.seam} is required by the contract but is absent`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const actual = typeof options.value;
|
|
241
|
+
const valid = options.kind === 'function' ? actual === 'function' : actual !== 'function';
|
|
242
|
+
return {
|
|
243
|
+
seam: options.seam,
|
|
244
|
+
subject: options.subject,
|
|
245
|
+
epoch: options.epoch,
|
|
246
|
+
stage: 'shape',
|
|
247
|
+
outcome: valid ? 'pass' : 'fail',
|
|
248
|
+
source: 'shape-check',
|
|
249
|
+
detail: valid
|
|
250
|
+
? `${options.seam} has the contract's ${options.kind} shape`
|
|
251
|
+
: `${options.seam} is ${actual}; the contract requires a ${options.kind}`,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Shape is intentionally only shape. Successful members produce `shape`
|
|
256
|
+
* receipts; callers must add operation/effect/round-trip receipts from actual
|
|
257
|
+
* consumers or conformance probes before those seams can verify. */
|
|
258
|
+
export function inspectSeamShape<T extends object>(options: {
|
|
259
|
+
readonly prefix: string;
|
|
260
|
+
readonly value: unknown;
|
|
261
|
+
readonly shape: SeamShape<T>;
|
|
262
|
+
readonly subject: string;
|
|
263
|
+
readonly epoch: string;
|
|
264
|
+
}): readonly SeamEvidenceReceipt[] {
|
|
265
|
+
const receipts: SeamEvidenceReceipt[] = [];
|
|
266
|
+
const object =
|
|
267
|
+
options.value !== null &&
|
|
268
|
+
(typeof options.value === 'object' || typeof options.value === 'function')
|
|
269
|
+
? (options.value as Record<string, unknown>)
|
|
270
|
+
: null;
|
|
271
|
+
const members = Object.entries(options.shape) as Array<
|
|
272
|
+
[string, { readonly optional: boolean; readonly kind: 'function' | 'value' }]
|
|
273
|
+
>;
|
|
274
|
+
for (const [member, spec] of members) {
|
|
275
|
+
const seam = `${options.prefix}.${member}`;
|
|
276
|
+
const value = object?.[member];
|
|
277
|
+
receipts.push(
|
|
278
|
+
memberShapeReceipt({
|
|
279
|
+
seam,
|
|
280
|
+
subject: options.subject,
|
|
281
|
+
epoch: options.epoch,
|
|
282
|
+
value,
|
|
283
|
+
optional: spec.optional,
|
|
284
|
+
kind: spec.kind,
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
return receipts;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Grade a carrier and every member it actually advertises.
|
|
293
|
+
*
|
|
294
|
+
* Optional-and-absent is honest degradation. Optional-and-present is a real
|
|
295
|
+
* promise and is held to exactly the same proof bar as a required member. This
|
|
296
|
+
* is the shared rule that prevents each coverage family from inventing a
|
|
297
|
+
* different meaning for “provider present”.
|
|
298
|
+
*/
|
|
299
|
+
export function gradeSeamCarrier(options: {
|
|
300
|
+
readonly seam: string;
|
|
301
|
+
readonly required: SeamProofStage;
|
|
302
|
+
readonly shape?: SeamShape<object> | undefined;
|
|
303
|
+
readonly subject: string;
|
|
304
|
+
readonly epoch: string;
|
|
305
|
+
readonly receipts: readonly SeamEvidenceReceipt[];
|
|
306
|
+
}): SeamEvidenceVerdict {
|
|
307
|
+
const base = gradeSeamEvidence(options);
|
|
308
|
+
if (base.state === 'absent' || base.state === 'failed' || !options.shape) return base;
|
|
309
|
+
|
|
310
|
+
for (const [member, spec] of Object.entries(options.shape)) {
|
|
311
|
+
const memberVerdict = gradeSeamEvidence({
|
|
312
|
+
seam: `${options.seam}.${member}`,
|
|
313
|
+
subject: options.subject,
|
|
314
|
+
epoch: options.epoch,
|
|
315
|
+
required: spec.required,
|
|
316
|
+
receipts: options.receipts,
|
|
317
|
+
});
|
|
318
|
+
if (spec.optional && memberVerdict.state === 'absent') continue;
|
|
319
|
+
if (memberVerdict.state === 'failed' || memberVerdict.state === 'absent') {
|
|
320
|
+
return {
|
|
321
|
+
...base,
|
|
322
|
+
state: 'failed',
|
|
323
|
+
receipt: memberVerdict.receipt,
|
|
324
|
+
detail: `${options.seam} is malformed: ${memberVerdict.detail}`,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (memberVerdict.state !== 'verified') {
|
|
328
|
+
return {
|
|
329
|
+
...base,
|
|
330
|
+
state: 'unverified',
|
|
331
|
+
detail: `${options.seam} has the required shape, but ${memberVerdict.seam} is unverified: ${memberVerdict.detail}`,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return base;
|
|
336
|
+
}
|
|
@@ -64,7 +64,7 @@ import {
|
|
|
64
64
|
type DebugRegistry,
|
|
65
65
|
getDebugRegistry,
|
|
66
66
|
} from '../runtime/debug-registry';
|
|
67
|
-
import {
|
|
67
|
+
import { devBuildEnabled } from '../runtime/dev-build';
|
|
68
68
|
import { disposeDebrisSubtree, type RootFrameHooks } from '../runtime/game';
|
|
69
69
|
import type { EditorPreview, GameCleanup, GameContext, GameSetupFn } from '../runtime/types';
|
|
70
70
|
import { type AudioContext as GameAudio, setupAudio } from '../setup/setup-audio';
|
|
@@ -119,9 +119,8 @@ export interface MountedSetupThreeRoot extends MountedThreeRoot {
|
|
|
119
119
|
* `Game.runFrame` is the CANONICAL driver going forward: it calls
|
|
120
120
|
* `frame.runPhase` once per (phase, substep) for every world, then
|
|
121
121
|
* `frame.endFrame` once per substep after all roots finish all phases.
|
|
122
|
-
* `update`
|
|
123
|
-
*
|
|
124
|
-
* keep using it unchanged.
|
|
122
|
+
* `update` is the direct entry point to the same `SystemRunner` for hosts
|
|
123
|
+
* that do not partition phases themselves.
|
|
125
124
|
*/
|
|
126
125
|
readonly frame: RootFrameHooks;
|
|
127
126
|
}
|
|
@@ -168,9 +167,8 @@ export class SetupThreeRootAdapter implements RootAdapter {
|
|
|
168
167
|
|
|
169
168
|
setAssetPrefix(assetPrefix);
|
|
170
169
|
|
|
171
|
-
// --- Scene view (scene/camera/composer). Browser:
|
|
172
|
-
//
|
|
173
|
-
// scene/camera + a no-op composer (no GPU). ---
|
|
170
|
+
// --- Scene view (scene/camera/composer). Browser: canonical builder.
|
|
171
|
+
// Headless: plain scene/camera + a no-op composer (no GPU). ---
|
|
174
172
|
let scene: THREE.Scene;
|
|
175
173
|
let camera: THREE.PerspectiveCamera;
|
|
176
174
|
let composer: EffectComposer;
|
|
@@ -593,8 +591,8 @@ export class SetupThreeRootAdapter implements RootAdapter {
|
|
|
593
591
|
// registry, with no capability to install and nothing for a game to write.
|
|
594
592
|
//
|
|
595
593
|
// THE GATE, in one place. Three conditions, and the reason for each:
|
|
596
|
-
// 1. `
|
|
597
|
-
// context" (`runtime/dev-
|
|
594
|
+
// 1. `devBuildEnabled()` — the ONE owner of "is this a dev/editor
|
|
595
|
+
// context" (`runtime/dev-build.ts`). A ship build registers nothing,
|
|
598
596
|
// subscribes to nothing, and never enables the profiler.
|
|
599
597
|
// 2. a `Game` shell exists — the readings are folded out of that game's
|
|
600
598
|
// profiler frames, and a bare mount has no profiler to fold.
|
|
@@ -622,12 +620,12 @@ export class SetupThreeRootAdapter implements RootAdapter {
|
|
|
622
620
|
// The packaged runtime bakes `import.meta.env.DEV = false` into its
|
|
623
621
|
// prebuilt bundle, which is a fact about how the EDITOR was built, not
|
|
624
622
|
// about whether this mount is a dev session — found live: a
|
|
625
|
-
// package-native project's vitals never seeded while its
|
|
626
|
-
//
|
|
627
|
-
//
|
|
628
|
-
//
|
|
629
|
-
//
|
|
630
|
-
if (!host.game || headless || !
|
|
623
|
+
// package-native project's vitals never seeded while its capabilities
|
|
624
|
+
// and every other project-graph module ran fine. The override argument
|
|
625
|
+
// is `devBuildEnabled`'s own documented seam for exactly this
|
|
626
|
+
// caller-knows-better case; a standalone/ship mount (no editorPreview)
|
|
627
|
+
// still resolves purely from the build env.
|
|
628
|
+
if (!host.game || headless || !devBuildEnabled(editorPreview ? true : undefined)) return;
|
|
631
629
|
renderVitals?.dispose();
|
|
632
630
|
host.game.profiler.enabled = true;
|
|
633
631
|
renderVitals = registerRenderVitals({
|
|
@@ -340,7 +340,9 @@ export interface NavCrowdAgentState {
|
|
|
340
340
|
* editor's Navigation panel (re)build and persist a navmesh through the seam —
|
|
341
341
|
* the first-party recast implementation (`createNavigationAdapter`) is the
|
|
342
342
|
* blessed implementer — while an external game that owns its build simply
|
|
343
|
-
* omits them. `
|
|
343
|
+
* omits them. `clear` lets that same owner release its navmesh; the editor never
|
|
344
|
+
* disposes implementation handles itself. `crowdAgents` feeds the play-mode
|
|
345
|
+
* crowd debug draw.
|
|
344
346
|
*/
|
|
345
347
|
export interface NavigationAdapter {
|
|
346
348
|
hasNavMesh(): boolean;
|
|
@@ -352,6 +354,9 @@ export interface NavigationAdapter {
|
|
|
352
354
|
/** Serialize the current navmesh to a binary blob (the editor persists it
|
|
353
355
|
* as the scene's `.navmesh` sidecar). Throws when nothing is built. */
|
|
354
356
|
exportData?(): Uint8Array;
|
|
357
|
+
/** Release the current navmesh and any debug object it attached to `scene`.
|
|
358
|
+
* Optional because an external game's navigation may be inspect-only. */
|
|
359
|
+
clear?(scene: THREE.Scene): void;
|
|
355
360
|
/** Live crowd-agent snapshots for debug draw. Empty when no crowd. */
|
|
356
361
|
crowdAgents?(): NavCrowdAgentState[];
|
|
357
362
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** Exhaustive proof requirements for mounted SystemAdapters. A bound slot is
|
|
2
|
+
* availability, not success: each nested operation has its own required proof
|
|
3
|
+
* stage and therefore cannot hide behind `active[slot] != null`. */
|
|
4
|
+
|
|
5
|
+
import type { PhysicsAdapter2D } from '../pixi/system-adapters';
|
|
6
|
+
import { defineSeamShape, type SeamShape } from './seam-evidence';
|
|
7
|
+
import type {
|
|
8
|
+
AudioAdapter,
|
|
9
|
+
CameraAdapter,
|
|
10
|
+
DebugAdapter,
|
|
11
|
+
NavigationAdapter,
|
|
12
|
+
NetworkingAdapter,
|
|
13
|
+
PhysicsAdapter,
|
|
14
|
+
RenderDebugAdapter,
|
|
15
|
+
SystemAdapters,
|
|
16
|
+
} from './system-adapter';
|
|
17
|
+
|
|
18
|
+
export const SYSTEM_ADAPTERS_SHAPE = defineSeamShape<SystemAdapters>()({
|
|
19
|
+
physics: { optional: true, kind: 'value', required: 'effect' },
|
|
20
|
+
networking: { optional: true, kind: 'value', required: 'operation' },
|
|
21
|
+
navigation: { optional: true, kind: 'value', required: 'operation' },
|
|
22
|
+
audio: { optional: true, kind: 'value', required: 'effect' },
|
|
23
|
+
camera: { optional: true, kind: 'value', required: 'effect' },
|
|
24
|
+
debug: { optional: true, kind: 'value', required: 'operation' },
|
|
25
|
+
renderDebug: { optional: true, kind: 'value', required: 'effect' },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const PHYSICS_ADAPTER_SHAPE = defineSeamShape<PhysicsAdapter>()({
|
|
29
|
+
keyedBy: { optional: true, kind: 'value', required: 'shape' },
|
|
30
|
+
ownerOf: { optional: false, kind: 'function', required: 'operation' },
|
|
31
|
+
freeze: { optional: false, kind: 'function', required: 'effect' },
|
|
32
|
+
commit: { optional: false, kind: 'function', required: 'effect' },
|
|
33
|
+
unfreeze: { optional: false, kind: 'function', required: 'effect' },
|
|
34
|
+
debugDraw: { optional: true, kind: 'function', required: 'operation' },
|
|
35
|
+
setDebugDrawEnabled: { optional: true, kind: 'function', required: 'effect' },
|
|
36
|
+
contactPoints: { optional: true, kind: 'function', required: 'operation' },
|
|
37
|
+
colliders: { optional: true, kind: 'function', required: 'operation' },
|
|
38
|
+
previewCollider: { optional: true, kind: 'function', required: 'effect' },
|
|
39
|
+
joints: { optional: true, kind: 'function', required: 'operation' },
|
|
40
|
+
previewJointAnchor: { optional: true, kind: 'function', required: 'effect' },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const PHYSICS_2D_ADAPTER_SHAPE = defineSeamShape<PhysicsAdapter2D>()({
|
|
44
|
+
keyedBy: { optional: false, kind: 'value', required: 'shape' },
|
|
45
|
+
ownerOf: { optional: false, kind: 'function', required: 'operation' },
|
|
46
|
+
freeze: { optional: false, kind: 'function', required: 'effect' },
|
|
47
|
+
commit: { optional: false, kind: 'function', required: 'effect' },
|
|
48
|
+
unfreeze: { optional: false, kind: 'function', required: 'effect' },
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const NETWORKING_ADAPTER_SHAPE = defineSeamShape<NetworkingAdapter>()({
|
|
52
|
+
peers: { optional: false, kind: 'function', required: 'operation' },
|
|
53
|
+
networkId: { optional: false, kind: 'function', required: 'operation' },
|
|
54
|
+
authority: { optional: false, kind: 'function', required: 'operation' },
|
|
55
|
+
editable: { optional: false, kind: 'function', required: 'operation' },
|
|
56
|
+
getConnectionState: { optional: false, kind: 'function', required: 'operation' },
|
|
57
|
+
getRoomInfo: { optional: false, kind: 'function', required: 'operation' },
|
|
58
|
+
getReplicationStats: { optional: false, kind: 'function', required: 'operation' },
|
|
59
|
+
subscribe: { optional: false, kind: 'function', required: 'effect' },
|
|
60
|
+
getStateSnapshot: { optional: true, kind: 'function', required: 'operation' },
|
|
61
|
+
messageEvents: { optional: true, kind: 'function', required: 'operation' },
|
|
62
|
+
getRates: { optional: true, kind: 'function', required: 'operation' },
|
|
63
|
+
getConditioning: { optional: true, kind: 'function', required: 'operation' },
|
|
64
|
+
setConditioning: { optional: true, kind: 'function', required: 'effect' },
|
|
65
|
+
getServerConfig: { optional: true, kind: 'function', required: 'operation' },
|
|
66
|
+
getPlayerIdentity: { optional: true, kind: 'function', required: 'operation' },
|
|
67
|
+
setPlayerIdentity: { optional: true, kind: 'function', required: 'effect' },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export const NAVIGATION_ADAPTER_SHAPE = defineSeamShape<NavigationAdapter>()({
|
|
71
|
+
hasNavMesh: { optional: false, kind: 'function', required: 'operation' },
|
|
72
|
+
findPath: { optional: false, kind: 'function', required: 'effect' },
|
|
73
|
+
debugMesh: { optional: false, kind: 'function', required: 'operation' },
|
|
74
|
+
bake: { optional: true, kind: 'function', required: 'effect' },
|
|
75
|
+
exportData: { optional: true, kind: 'function', required: 'operation' },
|
|
76
|
+
clear: { optional: true, kind: 'function', required: 'effect' },
|
|
77
|
+
crowdAgents: { optional: true, kind: 'function', required: 'operation' },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const AUDIO_ADAPTER_SHAPE = defineSeamShape<AudioAdapter>()({
|
|
81
|
+
resume: { optional: true, kind: 'function', required: 'effect' },
|
|
82
|
+
setMuted: { optional: false, kind: 'function', required: 'effect' },
|
|
83
|
+
isMuted: { optional: false, kind: 'function', required: 'operation' },
|
|
84
|
+
graphSnapshot: { optional: true, kind: 'function', required: 'operation' },
|
|
85
|
+
transportState: { optional: true, kind: 'function', required: 'operation' },
|
|
86
|
+
acquireMeters: { optional: true, kind: 'function', required: 'effect' },
|
|
87
|
+
acquireRecordingStream: { optional: true, kind: 'function', required: 'effect' },
|
|
88
|
+
audioEvents: { optional: true, kind: 'function', required: 'operation' },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const CAMERA_ADAPTER_SHAPE = defineSeamShape<CameraAdapter>()({
|
|
92
|
+
snapshot: { optional: false, kind: 'function', required: 'operation' },
|
|
93
|
+
subscribe: { optional: false, kind: 'function', required: 'effect' },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const DEBUG_ADAPTER_SHAPE = defineSeamShape<DebugAdapter>()({
|
|
97
|
+
providers: { optional: false, kind: 'function', required: 'operation' },
|
|
98
|
+
state: { optional: false, kind: 'function', required: 'operation' },
|
|
99
|
+
stateAll: { optional: false, kind: 'function', required: 'operation' },
|
|
100
|
+
commands: { optional: false, kind: 'function', required: 'operation' },
|
|
101
|
+
invoke: { optional: false, kind: 'function', required: 'effect' },
|
|
102
|
+
events: { optional: false, kind: 'function', required: 'operation' },
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const RENDER_DEBUG_ADAPTER_SHAPE = defineSeamShape<RenderDebugAdapter>()({
|
|
106
|
+
captureFrame: { optional: false, kind: 'function', required: 'effect' },
|
|
107
|
+
memorySnapshot: { optional: true, kind: 'function', required: 'operation' },
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
type NonPhysicsSystemSlot = Exclude<keyof SystemAdapters, 'physics'>;
|
|
111
|
+
|
|
112
|
+
export const SYSTEM_PROVIDER_SHAPES: {
|
|
113
|
+
readonly [K in NonPhysicsSystemSlot]: SeamShape<NonNullable<SystemAdapters[K]>>;
|
|
114
|
+
} = {
|
|
115
|
+
networking: NETWORKING_ADAPTER_SHAPE,
|
|
116
|
+
navigation: NAVIGATION_ADAPTER_SHAPE,
|
|
117
|
+
audio: AUDIO_ADAPTER_SHAPE,
|
|
118
|
+
camera: CAMERA_ADAPTER_SHAPE,
|
|
119
|
+
debug: DEBUG_ADAPTER_SHAPE,
|
|
120
|
+
renderDebug: RENDER_DEBUG_ADAPTER_SHAPE,
|
|
121
|
+
};
|