@volter/project 0.0.0-stage → 0.5.203

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.
Files changed (53) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +18 -3
  4. package/package.json +64 -4
  5. package/schemas/engine-api.json +57 -0
  6. package/schemas/engine-api.md +22 -0
  7. package/schemas/volter-project.schema.json +468 -0
  8. package/schemas/volter-settings.schema.json +149 -0
  9. package/src/adapter/adapter-module.ts +997 -0
  10. package/src/adapter/adapter-surface.ts +34 -0
  11. package/src/adapter/authoring-seam-contract.ts +224 -0
  12. package/src/adapter/authoring.ts +1336 -0
  13. package/src/adapter/binding.ts +445 -0
  14. package/src/adapter/debug-registry-ref.ts +19 -0
  15. package/src/adapter/editor-looks.ts +519 -0
  16. package/src/adapter/finders/finder-result.ts +29 -0
  17. package/src/adapter/finders/registry.ts +116 -0
  18. package/src/adapter/frame-capture.ts +80 -0
  19. package/src/adapter/host-context.ts +114 -0
  20. package/src/adapter/index.ts +209 -0
  21. package/src/adapter/ingest/contract-system-slots.ts +28 -0
  22. package/src/adapter/ingest/game-contract-seams.ts +86 -0
  23. package/src/adapter/ingest/game-contract.ts +506 -0
  24. package/src/adapter/ingest/mount-readiness.ts +140 -0
  25. package/src/adapter/loop-gate-report.ts +53 -0
  26. package/src/adapter/manifest-interpreter.ts +64 -0
  27. package/src/adapter/native-entry-surface.ts +100 -0
  28. package/src/adapter/physics-adapter-2d.ts +30 -0
  29. package/src/adapter/render-audio.ts +20 -0
  30. package/src/adapter/render-memory.ts +34 -0
  31. package/src/adapter/renderer-config.ts +84 -0
  32. package/src/adapter/root-adapter.ts +243 -0
  33. package/src/adapter/root-seam-contract.ts +61 -0
  34. package/src/adapter/root-stacking.ts +67 -0
  35. package/src/adapter/seam-evidence.ts +336 -0
  36. package/src/adapter/system-adapter.ts +865 -0
  37. package/src/adapter/system-seam-contract.ts +138 -0
  38. package/src/adapter/system-slot.ts +301 -0
  39. package/src/adapter/transform.ts +35 -0
  40. package/src/adapter/workspace-arrangement.ts +92 -0
  41. package/src/core/system-phase.ts +52 -0
  42. package/src/inspection.ts +281 -0
  43. package/src/manifest/configuration-kinds.ts +241 -0
  44. package/src/manifest/editor-port.ts +69 -0
  45. package/src/manifest/filename.ts +45 -0
  46. package/src/manifest/kind-modules.ts +58 -0
  47. package/src/manifest/load-file.ts +33 -0
  48. package/src/manifest/load.ts +380 -0
  49. package/src/manifest/locate.ts +55 -0
  50. package/src/manifest/runtime-environment.ts +48 -0
  51. package/src/manifest/schema.ts +690 -0
  52. package/src/settings/keys.ts +152 -0
  53. package/src/settings/schema.ts +151 -0
@@ -0,0 +1,61 @@
1
+ /** Exhaustive proof requirements for substrate mount/lifecycle seams. */
2
+
3
+ import type { AdapterSurface } from './adapter-surface';
4
+ import type {
5
+ MountedCanvasRoot,
6
+ MountedReactRoot,
7
+ MountedRootBase,
8
+ MountedThreeRoot,
9
+ RootAdapter,
10
+ RootStateObserver,
11
+ } from './root-adapter';
12
+ import { defineSeamShape } from './seam-evidence';
13
+
14
+ export const ROOT_ADAPTER_SHAPE = defineSeamShape<RootAdapter<AdapterSurface>>()({
15
+ id: { optional: false, kind: 'value', required: 'shape' },
16
+ mount: { optional: false, kind: 'function', required: 'effect' },
17
+ });
18
+
19
+ export const MOUNTED_ROOT_BASE_SHAPE = defineSeamShape<MountedRootBase>()({
20
+ drivesOwnLoop: { optional: false, kind: 'value', required: 'shape' },
21
+ updateCadence: { optional: true, kind: 'value', required: 'shape' },
22
+ update: { optional: true, kind: 'function', required: 'effect' },
23
+ fixedUpdate: { optional: true, kind: 'function', required: 'effect' },
24
+ setPaused: { optional: true, kind: 'function', required: 'effect' },
25
+ step: { optional: true, kind: 'function', required: 'effect' },
26
+ resize: { optional: true, kind: 'function', required: 'effect' },
27
+ hitTest: { optional: true, kind: 'function', required: 'operation' },
28
+ dispose: { optional: false, kind: 'function', required: 'effect' },
29
+ disposeComplete: { optional: true, kind: 'value', required: 'effect' },
30
+ authoring: { optional: true, kind: 'value', required: 'operation' },
31
+ systems: { optional: true, kind: 'value', required: 'operation' },
32
+ systemScope: { optional: true, kind: 'value', required: 'operation' },
33
+ observe: { optional: true, kind: 'value', required: 'effect' },
34
+ });
35
+
36
+ export const ROOT_STATE_OBSERVER_SHAPE = defineSeamShape<RootStateObserver>()({
37
+ subscribe: { optional: false, kind: 'function', required: 'effect' },
38
+ snapshot: { optional: false, kind: 'function', required: 'operation' },
39
+ });
40
+
41
+ type ThreeSurface = Omit<MountedThreeRoot<object, object>, keyof MountedRootBase>;
42
+ type CanvasSurface = Omit<MountedCanvasRoot, keyof MountedRootBase>;
43
+ type DomSurface = Omit<MountedReactRoot, keyof MountedRootBase>;
44
+
45
+ export const MOUNTED_THREE_SURFACE_SHAPE = defineSeamShape<ThreeSurface>()({
46
+ kind: { optional: false, kind: 'value', required: 'shape' },
47
+ scene: { optional: false, kind: 'value', required: 'effect' },
48
+ camera: { optional: false, kind: 'value', required: 'effect' },
49
+ rendererConfig: { optional: true, kind: 'value', required: 'shape' },
50
+ });
51
+
52
+ export const MOUNTED_CANVAS_SURFACE_SHAPE = defineSeamShape<CanvasSurface>()({
53
+ kind: { optional: false, kind: 'value', required: 'shape' },
54
+ canvas: { optional: false, kind: 'value', required: 'effect' },
55
+ substrate: { optional: false, kind: 'value', required: 'effect' },
56
+ });
57
+
58
+ export const MOUNTED_DOM_SURFACE_SHAPE = defineSeamShape<DomSurface>()({
59
+ kind: { optional: false, kind: 'value', required: 'shape' },
60
+ container: { optional: false, kind: 'value', required: 'effect' },
61
+ });
@@ -0,0 +1,67 @@
1
+ /**
2
+ * WHICH ROOT OWNS A POINT — the pure half of D5 §2a, a rule about how several
3
+ * adapter roots share one surface, so it belongs beside the other seam
4
+ * contracts rather than inside the runtime that first needed it.
5
+ *
6
+ * Two callers with nothing else in common depend on it: the engine's
7
+ * `runtime/input-router.ts`, which wires it to real canvases and forwards
8
+ * claimed events; and the EDITOR, which asks the same question at design time
9
+ * with no game running (`authoring/layered-pick.ts` picks through stacked
10
+ * authoring layers, `authoring/design-time-layers.ts` orders the layers it
11
+ * mounts). That second caller is why the file moved here 2026-09-18
12
+ * (WORK.md §The open-source launch, phase 1 unit 7): the host may import the
13
+ * adapter CONTRACT and must not import the game runtime, and this was the one
14
+ * `runtime/` module left in its closure. No DOM, no canvas, no mount.
15
+ */
16
+
17
+ /** The pure decision inputs — no DOM. */
18
+ export interface ClaimEntry {
19
+ readonly id: string;
20
+ readonly zOrder: number;
21
+ /**
22
+ * Optional claim predicate over a point RELATIVE TO THE CONTAINER. Absent
23
+ * means: "this world claims only if it is the bottom (lowest zOrder)
24
+ * world" — D5 §2a's stated default ("transparent upper roots do not
25
+ * claim, the bottom world claims everything").
26
+ */
27
+ readonly hitTest?: ((x: number, y: number) => boolean) | undefined;
28
+ }
29
+
30
+ /**
31
+ * Bottom-to-top paint order: ascending `zOrder`, ties -> original array
32
+ * order — the SAME rule `manifest/load.ts`'s `loadGameManifest` sorts
33
+ * roots by, so a world's DOM stacking position always matches its
34
+ * manifest-resolved zOrder. The last element is the topmost (visually on
35
+ * top / highest z-index).
36
+ */
37
+ export function stackOrder<T extends ClaimEntry>(entries: readonly T[]): T[] {
38
+ return entries
39
+ .map((entry, index) => ({ entry, index }))
40
+ .sort((a, b) => a.entry.zOrder - b.entry.zOrder || a.index - b.index)
41
+ .map(({ entry }) => entry);
42
+ }
43
+
44
+ /**
45
+ * Resolve which world's id claims a container-relative point `(x, y)` — D5
46
+ * §2a: walk top-down (topmost first); the first world whose `hitTest`
47
+ * returns true wins ("topmost-claim wins"). A world with no `hitTest` never
48
+ * claims UNLESS it is the bottom-most world, which claims unconditionally
49
+ * (or via its own `hitTest`, if it declares one) — "no-claim falls to
50
+ * bottom". Returns `null` only when `entries` is empty.
51
+ */
52
+ export function resolveClaimingRoot(
53
+ entries: readonly ClaimEntry[],
54
+ x: number,
55
+ y: number,
56
+ ): string | null {
57
+ if (entries.length === 0) return null;
58
+ const order = stackOrder(entries); // bottom -> top
59
+ for (let i = order.length - 1; i >= 1; i--) {
60
+ const entry = order[i]!;
61
+ if (entry.hitTest?.(x, y)) return entry.id;
62
+ }
63
+ // No upper world claimed (or there is only one world) -> the bottom world
64
+ // claims — honoring its own hitTest if it declares one (rare: the bottom
65
+ // world usually claims everything unconditionally per D5 §2a's default).
66
+ return order[0]!.id;
67
+ }
@@ -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 Volter 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
+ }