@vgai/engine 0.5.41 → 0.5.44
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 +28 -29
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +11 -34
- package/dist/adapter/authoring-seam-contract.d.ts +10 -0
- package/dist/adapter/authoring-seam-contract.d.ts.map +1 -1
- package/dist/adapter/authoring-seam-contract.js +2 -0
- package/dist/adapter/authoring.d.ts +31 -0
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/finders/finder-result.d.ts +2 -2
- package/dist/adapter/finders/finder-result.d.ts.map +1 -1
- package/dist/adapter/finders/index.d.ts +15 -2
- package/dist/adapter/finders/index.d.ts.map +1 -1
- package/dist/adapter/finders/index.js +38 -10
- package/dist/adapter/finders/prefabs-from-stories.d.ts.map +1 -1
- package/dist/adapter/finders/registry.d.ts +45 -0
- package/dist/adapter/finders/registry.d.ts.map +1 -0
- package/dist/adapter/finders/registry.js +56 -0
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +50 -20
- package/dist/adapter/system-adapter.d.ts +3 -1
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +21 -11
- package/dist/data/vite-plugin-data.d.ts +1 -1
- package/dist/data/vite-plugin-data.d.ts.map +1 -1
- package/dist/data/vite-plugin-data.js +2 -2
- package/dist/ecs/user-data.d.ts +6 -0
- package/dist/ecs/user-data.d.ts.map +1 -1
- package/dist/ecs/user-data.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/input/host-pointer.d.ts +18 -0
- package/dist/input/host-pointer.d.ts.map +1 -1
- package/dist/input/host-pointer.js +54 -5
- package/dist/input/input-manager.d.ts +83 -1
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +357 -8
- package/dist/manifest/configuration-kinds.d.ts +97 -0
- package/dist/manifest/configuration-kinds.d.ts.map +1 -0
- package/dist/manifest/configuration-kinds.js +148 -0
- package/dist/manifest/index.d.ts +2 -1
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest/index.js +4 -3
- package/dist/manifest/kind-modules.d.ts +18 -0
- package/dist/manifest/kind-modules.d.ts.map +1 -0
- package/dist/manifest/kind-modules.js +58 -0
- package/dist/manifest/load-file.d.ts +3 -3
- package/dist/manifest/load-file.d.ts.map +1 -1
- package/dist/manifest/load-file.js +4 -4
- package/dist/manifest/load.d.ts +17 -12
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +29 -19
- package/dist/manifest/runtime-environment.d.ts +11 -0
- package/dist/manifest/runtime-environment.d.ts.map +1 -0
- package/dist/manifest/runtime-environment.js +44 -0
- package/dist/manifest/schema.d.ts +10 -9
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +32 -27
- package/dist/react/use-data.d.ts +1 -1
- package/dist/react/use-data.js +1 -1
- package/dist/render/environment-capture.d.ts +10 -1
- package/dist/render/environment-capture.d.ts.map +1 -1
- package/dist/render/environment-capture.js +25 -0
- package/dist/render/ibl-override-material.d.ts +11 -2
- package/dist/render/ibl-override-material.d.ts.map +1 -1
- package/dist/render/ibl-override-material.js +16 -2
- package/dist/render/matcap-texture.d.ts +5 -0
- package/dist/render/matcap-texture.d.ts.map +1 -0
- package/dist/render/matcap-texture.js +82 -0
- package/dist/render/soft-particle-depth.d.ts +14 -8
- package/dist/render/soft-particle-depth.d.ts.map +1 -1
- package/dist/render/soft-particle-depth.js +37 -14
- package/dist/render/viewport-shading.d.ts +11 -1
- package/dist/render/viewport-shading.d.ts.map +1 -1
- package/dist/render/viewport-shading.js +25 -0
- package/dist/runtime/create-runtime.js +6 -5
- package/dist/runtime/debug-bridge.d.ts.map +1 -1
- package/dist/runtime/debug-bridge.js +17 -3
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +11 -2
- package/dist/settings/schema.d.ts +99 -0
- package/dist/settings/schema.d.ts.map +1 -0
- package/dist/settings/schema.js +110 -0
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +34 -36
- package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
- package/dist/world3d-react/rapier-physics-bridge.js +21 -6
- package/dist-config/config.js +1 -1
- package/dist-config/config.js.map +2 -2
- package/package.json +2 -1
- package/schemas/engine-capabilities.json +4 -4
- package/schemas/vgai-project.schema.json +41 -36
- package/schemas/vgai-settings.schema.json +112 -0
- package/src/adapter/adapter-module.ts +33 -51
- package/src/adapter/authoring-seam-contract.ts +2 -0
- package/src/adapter/authoring.ts +31 -0
- package/src/adapter/finders/finder-result.ts +2 -2
- package/src/adapter/finders/index.ts +68 -12
- package/src/adapter/finders/prefabs-from-stories.ts +2 -2
- package/src/adapter/finders/registry.ts +95 -0
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +4 -4
- package/src/adapter/ingest/scene-capture.ts +48 -19
- package/src/adapter/system-adapter.ts +3 -1
- package/src/canvas-react/pixi-react-root-factory.tsx +21 -11
- package/src/data/vite-plugin-data.ts +4 -2
- package/src/ecs/user-data.ts +6 -0
- package/src/index.ts +1 -1
- package/src/input/host-pointer.ts +84 -5
- package/src/input/input-manager.ts +404 -7
- package/src/manifest/configuration-kinds.ts +241 -0
- package/src/manifest/index.ts +4 -3
- package/src/manifest/kind-modules.ts +58 -0
- package/src/manifest/load-file.ts +11 -5
- package/src/manifest/load.ts +53 -22
- package/src/manifest/runtime-environment.ts +48 -0
- package/src/manifest/schema.ts +39 -33
- package/src/react/use-data.ts +1 -1
- package/src/render/environment-capture.ts +30 -1
- package/src/render/ibl-override-material.ts +32 -2
- package/src/render/matcap-texture.ts +92 -0
- package/src/render/soft-particle-depth.ts +43 -14
- package/src/render/viewport-shading.ts +33 -1
- package/src/runtime/create-runtime.ts +6 -5
- package/src/runtime/debug-bridge.ts +21 -3
- package/src/runtime/mount-manifest.ts +13 -2
- package/src/settings/schema.ts +127 -0
- package/src/world3d-react/r3f-root-factory.tsx +45 -39
- package/src/world3d-react/rapier-physics-bridge.tsx +22 -8
|
@@ -491,42 +491,22 @@ export interface PrefabsFromStoriesParams {
|
|
|
491
491
|
readonly finder: 'prefabsFromStories';
|
|
492
492
|
}
|
|
493
493
|
|
|
494
|
-
/**
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
.describe('Region ids whose entrypoints carry the selection, or the rule form'),
|
|
507
|
-
selection: z
|
|
508
|
-
.string()
|
|
509
|
-
.min(1)
|
|
510
|
-
.optional()
|
|
511
|
-
.describe(
|
|
512
|
-
"Identifier of the entrypoint's module-level scene selection table; omit for a " +
|
|
513
|
-
'single-composition entrypoint',
|
|
514
|
-
),
|
|
515
|
-
})
|
|
516
|
-
.strict()
|
|
517
|
-
.refine((value) => value.selection === undefined || Array.isArray(value.regions), {
|
|
518
|
-
message:
|
|
519
|
-
'scenesFromEntrypointSelection: `selection` names an identifier in ONE entrypoint’s ' +
|
|
520
|
-
`source, so it cannot be paired with the \`${EXPORTED_COMPOSITION_REGIONS}\` rule — ` +
|
|
521
|
-
'name the region explicitly (`regions: ["<rootId>"]`).',
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
const PrefabsFromStoriesSchema = z.object({ finder: z.literal('prefabsFromStories') }).strict();
|
|
494
|
+
/**
|
|
495
|
+
* A finder selection: the finder's registered NAME plus its own parameters.
|
|
496
|
+
* The set of finders is OPEN (ARCHITECTURE-CORE §The editor protocol,
|
|
497
|
+
* "Documents, not scenes"): the engine's own register in
|
|
498
|
+
* `adapter/finders/registry.ts`, a contribution registers more, and this
|
|
499
|
+
* module validates only the envelope — each finder's parameters are checked
|
|
500
|
+
* against ITS schema the moment the loader runs it, loudly and by name.
|
|
501
|
+
*/
|
|
502
|
+
export type FinderSelection = {
|
|
503
|
+
readonly finder: string;
|
|
504
|
+
readonly [param: string]: unknown;
|
|
505
|
+
};
|
|
525
506
|
|
|
526
|
-
const FinderSelectionSchema = z
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
]);
|
|
507
|
+
const FinderSelectionSchema = z
|
|
508
|
+
.object({ finder: z.string().min(1).describe('Registered finder name') })
|
|
509
|
+
.passthrough();
|
|
530
510
|
|
|
531
511
|
// ---------------------------------------------------------------------------
|
|
532
512
|
// The scene table
|
|
@@ -614,11 +594,13 @@ const SceneSourceSchema = z
|
|
|
614
594
|
* verb, differing only in instance site (ARCHITECTURE-CORE §Roots: "A scene is
|
|
615
595
|
* a ROLE, not a kind … the same kind of thing as a prefab").
|
|
616
596
|
*/
|
|
617
|
-
export interface
|
|
597
|
+
export interface DocumentEntry {
|
|
618
598
|
readonly id: string;
|
|
619
599
|
readonly label: string;
|
|
620
600
|
/** `scene` (swaps at the entrypoint's slot) or `prefab` (placed by a designer). */
|
|
621
|
-
|
|
601
|
+
/** OPEN: `scene` and `prefab` are the first two kinds; a page, a component,
|
|
602
|
+
* a model, a bake are kinds the same way. */
|
|
603
|
+
readonly kind: string;
|
|
622
604
|
/** Owning region id; `null` for a project-scoped entry bound to no one root. */
|
|
623
605
|
readonly region: string | null;
|
|
624
606
|
/**
|
|
@@ -633,20 +615,20 @@ export interface SceneEntry {
|
|
|
633
615
|
* infers setup from neighboring filenames or game globals. */
|
|
634
616
|
readonly isolationSetup?: SceneSource;
|
|
635
617
|
/** Which finder produced it; absent = the adapter stated it outright. */
|
|
636
|
-
readonly finder?:
|
|
618
|
+
readonly finder?: string;
|
|
637
619
|
}
|
|
638
620
|
|
|
639
|
-
const
|
|
621
|
+
const DocumentEntrySchema = z
|
|
640
622
|
.object({
|
|
641
623
|
id: z.string().min(1),
|
|
642
624
|
label: z.string().min(1),
|
|
643
|
-
kind: z.
|
|
625
|
+
kind: z.string().min(1),
|
|
644
626
|
region: z.string().min(1).nullable(),
|
|
645
627
|
authorable: z.boolean(),
|
|
646
628
|
reach: SceneReachSchema,
|
|
647
629
|
source: SceneSourceSchema.optional(),
|
|
648
630
|
isolationSetup: SceneSourceSchema.optional(),
|
|
649
|
-
finder: z.
|
|
631
|
+
finder: z.string().min(1).optional(),
|
|
650
632
|
})
|
|
651
633
|
.strict();
|
|
652
634
|
|
|
@@ -654,8 +636,8 @@ const SceneEntrySchema = z
|
|
|
654
636
|
* The adapter's scene table: entries stated outright, finder selections the
|
|
655
637
|
* host runs to produce the rest, and the entry open by default.
|
|
656
638
|
*/
|
|
657
|
-
export interface
|
|
658
|
-
readonly entries?: readonly
|
|
639
|
+
export interface AdapterDocumentTable {
|
|
640
|
+
readonly entries?: readonly DocumentEntry[];
|
|
659
641
|
readonly find?: readonly FinderSelection[];
|
|
660
642
|
/**
|
|
661
643
|
* Id of the entry open by default. Omit to let the finder's own answer
|
|
@@ -665,9 +647,9 @@ export interface AdapterSceneTable {
|
|
|
665
647
|
readonly default?: string;
|
|
666
648
|
}
|
|
667
649
|
|
|
668
|
-
const
|
|
650
|
+
const AdapterDocumentTableSchema = z
|
|
669
651
|
.object({
|
|
670
|
-
entries: z.array(
|
|
652
|
+
entries: z.array(DocumentEntrySchema).optional(),
|
|
671
653
|
find: z.array(FinderSelectionSchema).optional(),
|
|
672
654
|
default: z.string().min(1).optional(),
|
|
673
655
|
})
|
|
@@ -815,7 +797,7 @@ export interface AdapterDefinition {
|
|
|
815
797
|
* an explicit `regions` LIST carries its own and rejects these by name.
|
|
816
798
|
*/
|
|
817
799
|
readonly regionIncludes: Readonly<Record<string, AdapterRegionOverlay>>;
|
|
818
|
-
readonly
|
|
800
|
+
readonly documents: AdapterDocumentTable;
|
|
819
801
|
readonly observation: readonly ObservationDeclaration[];
|
|
820
802
|
readonly input?: AdapterInputBinding | undefined;
|
|
821
803
|
}
|
|
@@ -826,7 +808,7 @@ export interface AdapterDefinition {
|
|
|
826
808
|
export interface AdapterDefinitionInput {
|
|
827
809
|
readonly regions?: RegionBinding;
|
|
828
810
|
readonly regionIncludes?: Readonly<Record<string, AdapterRegionOverlay>>;
|
|
829
|
-
readonly
|
|
811
|
+
readonly documents?: AdapterDocumentTable;
|
|
830
812
|
readonly observation?: readonly ObservationDeclaration[];
|
|
831
813
|
readonly input?: AdapterInputBinding | undefined;
|
|
832
814
|
}
|
|
@@ -847,7 +829,7 @@ export const AdapterDefinitionSchema = z
|
|
|
847
829
|
.object({
|
|
848
830
|
regions: RegionBindingSchema.optional(),
|
|
849
831
|
regionIncludes: RegionIncludesSchema.optional(),
|
|
850
|
-
|
|
832
|
+
documents: AdapterDocumentTableSchema.optional(),
|
|
851
833
|
observation: z.array(ObservationDeclarationSchema).optional(),
|
|
852
834
|
input: AdapterInputBindingSchema.optional(),
|
|
853
835
|
})
|
|
@@ -885,7 +867,7 @@ export function defineAdapter(input: AdapterDefinitionInput = {}): AdapterDefini
|
|
|
885
867
|
return Object.freeze({
|
|
886
868
|
regions: input.regions ?? 'manifest-roots',
|
|
887
869
|
regionIncludes: Object.freeze({ ...(input.regionIncludes ?? {}) }),
|
|
888
|
-
|
|
870
|
+
documents: Object.freeze({ ...(input.documents ?? {}) }),
|
|
889
871
|
observation: Object.freeze([...(input.observation ?? [])]),
|
|
890
872
|
...(input.input ? { input: input.input } : {}),
|
|
891
873
|
});
|
|
@@ -894,7 +876,7 @@ export function defineAdapter(input: AdapterDefinitionInput = {}): AdapterDefini
|
|
|
894
876
|
/** Extra bindings a near-native game adds without restating the native ones. */
|
|
895
877
|
export interface NativeAdapterOptions {
|
|
896
878
|
/** Scene-table entries this game states outright, beside what the finders find. */
|
|
897
|
-
readonly scenes?: readonly
|
|
879
|
+
readonly scenes?: readonly DocumentEntry[];
|
|
898
880
|
/** Per-region `include`/`mounts` parameters for the derivation — see
|
|
899
881
|
* {@link AdapterDefinition.regionIncludes}. */
|
|
900
882
|
readonly regionIncludes?: Readonly<Record<string, AdapterRegionOverlay>>;
|
|
@@ -911,7 +893,7 @@ export function nativeAdapter(options: NativeAdapterOptions = {}): AdapterDefini
|
|
|
911
893
|
return defineAdapter({
|
|
912
894
|
regions: 'manifest-roots',
|
|
913
895
|
...(options.regionIncludes ? { regionIncludes: options.regionIncludes } : {}),
|
|
914
|
-
|
|
896
|
+
documents: {
|
|
915
897
|
...(options.scenes ? { entries: options.scenes } : {}),
|
|
916
898
|
find: [
|
|
917
899
|
{ finder: 'scenesFromEntrypointSelection', regions: EXPORTED_COMPOSITION_REGIONS },
|
|
@@ -57,6 +57,7 @@ export const AUTHORING_ADAPTER_SHAPE = defineSeamShape<AuthoringAdapter>()({
|
|
|
57
57
|
export const HIERARCHY_PROVIDER_SHAPE = defineSeamShape<HierarchyProvider>()({
|
|
58
58
|
roots: { optional: false, kind: 'function', required: 'operation' },
|
|
59
59
|
node: { optional: false, kind: 'function', required: 'operation' },
|
|
60
|
+
crossSurfaceStructureSignature: { optional: true, kind: 'function', required: 'operation' },
|
|
60
61
|
object3D: { optional: true, kind: 'function', required: 'operation' },
|
|
61
62
|
idForObject3D: { optional: true, kind: 'function', required: 'effect' },
|
|
62
63
|
});
|
|
@@ -82,6 +83,7 @@ export const INSPECTOR_PROVIDER_SHAPE = defineSeamShape<InspectorProvider>()({
|
|
|
82
83
|
properties: { optional: false, kind: 'function', required: 'operation' },
|
|
83
84
|
get: { optional: false, kind: 'function', required: 'operation' },
|
|
84
85
|
set: { optional: false, kind: 'function', required: 'effect' },
|
|
86
|
+
preview: { optional: true, kind: 'function', required: 'effect' },
|
|
85
87
|
editability: { optional: true, kind: 'function', required: 'operation' },
|
|
86
88
|
remove: { optional: true, kind: 'function', required: 'round-trip' },
|
|
87
89
|
});
|
package/src/adapter/authoring.ts
CHANGED
|
@@ -116,6 +116,12 @@ export interface EditorNode {
|
|
|
116
116
|
crossSurfaceParentId?: string;
|
|
117
117
|
/** Source-owned sibling ordinal used only when native rows are joined across surfaces. */
|
|
118
118
|
crossSurfaceOrder?: number;
|
|
119
|
+
/**
|
|
120
|
+
* Live source-owned label for the projected group containing this row's adapter root.
|
|
121
|
+
* Any row may carry it: a native surface can have no semantic root of its own when all of
|
|
122
|
+
* its authored rows are children of entities owned by another surface.
|
|
123
|
+
*/
|
|
124
|
+
crossSurfaceGroupLabel?: string;
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
export interface PropertyDescriptor {
|
|
@@ -177,6 +183,15 @@ export interface PropertyDescriptor {
|
|
|
177
183
|
export interface HierarchyProvider {
|
|
178
184
|
roots(): EditorNode[];
|
|
179
185
|
node(id: string): EditorNode | null;
|
|
186
|
+
/**
|
|
187
|
+
* OPTIONAL semantic-join revision. A composite hierarchy uses explicit
|
|
188
|
+
* `crossSurface*` fields to join native roots; this signature changes if and
|
|
189
|
+
* only if those fields, their semantic ancestry, or their order changed.
|
|
190
|
+
* Ordinary native-only child churn may leave it stable. `null` explicitly
|
|
191
|
+
* means this provider has no semantic participation. Omission means the
|
|
192
|
+
* composite must conservatively re-read the provider's whole hierarchy.
|
|
193
|
+
*/
|
|
194
|
+
crossSurfaceStructureSignature?(): string | null;
|
|
180
195
|
/**
|
|
181
196
|
* OPTIONAL, THREE-SPECIFIC — the live `Object3D` for raycast/gizmo binding
|
|
182
197
|
* (null for a node this adapter has no object for). A Pixi, DOM or React
|
|
@@ -331,6 +346,22 @@ export interface InspectorProvider {
|
|
|
331
346
|
* performed by this provider, and the shell reports the live-only floor.
|
|
332
347
|
*/
|
|
333
348
|
set(id: string, path: string, value: unknown): void | WriteAck | Promise<void | WriteAck>;
|
|
349
|
+
/**
|
|
350
|
+
* Optional — SHOW a value without writing it, for a control the user drags.
|
|
351
|
+
*
|
|
352
|
+
* The gizmo already has this pair ({@link TransformProvider.writeTransform}
|
|
353
|
+
* live, {@link TransformProvider.endEdit} persisting), and a colour picker
|
|
354
|
+
* needs the same shape: it streams values continuously while the pointer
|
|
355
|
+
* moves, and persisting each one is a write burst the absorb cycle cannot
|
|
356
|
+
* follow. So the control previews here on every change and calls
|
|
357
|
+
* {@link set} ONCE when the gesture ends.
|
|
358
|
+
*
|
|
359
|
+
* A preview NEVER persists and is never the truth: whatever the adapter
|
|
360
|
+
* mutates is discarded by the next remount unless a `set` follows. An
|
|
361
|
+
* adapter that cannot show a value without writing it simply omits this,
|
|
362
|
+
* and the control's preview is a no-op — the value still lands on commit.
|
|
363
|
+
*/
|
|
364
|
+
preview?(id: string, path: string, value: unknown): void;
|
|
334
365
|
/** Optional per-subject write preflight for shell affordances outside the
|
|
335
366
|
* Inspector field grid (notably hierarchy eye/lock controls). The property
|
|
336
367
|
* descriptor remains the Inspector's source of `readonly`; this gives other
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
* different facts, and only the second is a defect.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {
|
|
11
|
+
import type { DocumentEntry } from '../adapter-module';
|
|
12
12
|
|
|
13
13
|
export interface FinderResult {
|
|
14
14
|
/** Scene-table entries this finder produced, in discovery order. */
|
|
15
|
-
readonly entries: readonly
|
|
15
|
+
readonly entries: readonly DocumentEntry[];
|
|
16
16
|
/**
|
|
17
17
|
* The entry this finder's own answer makes the default, when its answer
|
|
18
18
|
* settles it UNAMBIGUOUSLY (one candidate). A finder never picks between
|
|
@@ -19,9 +19,16 @@
|
|
|
19
19
|
* is what keeps a game's `vgai.adapter.ts` free of finder implementations.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import type
|
|
22
|
+
import { type ZodType, z } from 'zod';
|
|
23
|
+
import {
|
|
24
|
+
EXPORTED_COMPOSITION_REGIONS,
|
|
25
|
+
type FinderSelection,
|
|
26
|
+
type PrefabsFromStoriesParams,
|
|
27
|
+
type ScenesFromEntrypointSelectionParams,
|
|
28
|
+
} from '../adapter-module';
|
|
23
29
|
import type { FinderResult } from './finder-result';
|
|
24
30
|
import { type PrefabsFromStoriesInput, prefabsFromStories } from './prefabs-from-stories';
|
|
31
|
+
import { registerFinder, runRegisteredFinder } from './registry';
|
|
25
32
|
import {
|
|
26
33
|
type ScenesFromEntrypointSelectionInput,
|
|
27
34
|
scenesFromEntrypointSelection,
|
|
@@ -34,6 +41,13 @@ export type {
|
|
|
34
41
|
StoryRegistration,
|
|
35
42
|
} from './prefabs-from-stories';
|
|
36
43
|
export { prefabsFromStories } from './prefabs-from-stories';
|
|
44
|
+
export type { FinderContribution, FinderRegistration } from './registry';
|
|
45
|
+
export {
|
|
46
|
+
registerContributedFinder,
|
|
47
|
+
registeredFinderNames,
|
|
48
|
+
registerFinder,
|
|
49
|
+
runRegisteredFinder,
|
|
50
|
+
} from './registry';
|
|
37
51
|
export type {
|
|
38
52
|
EntrypointSource,
|
|
39
53
|
ScenesFromEntrypointSelectionInput,
|
|
@@ -41,7 +55,20 @@ export type {
|
|
|
41
55
|
export { scenesFromEntrypointSelection } from './scenes-from-entrypoint-selection';
|
|
42
56
|
|
|
43
57
|
/** Everything any shipped finder can be handed, gathered once by the loader. */
|
|
44
|
-
|
|
58
|
+
/** A project source the host read for a selection's `include` globs. */
|
|
59
|
+
export interface ProjectSourceFile {
|
|
60
|
+
/** Project-relative path. */
|
|
61
|
+
readonly path: string;
|
|
62
|
+
readonly source: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type FinderInput = ScenesFromEntrypointSelectionInput &
|
|
66
|
+
PrefabsFromStoriesInput & {
|
|
67
|
+
/** The sources matching every selection's `include` globs — empty when
|
|
68
|
+
* no selection declares any. A contributed finder reads the project
|
|
69
|
+
* through this and nothing else. */
|
|
70
|
+
readonly sources?: readonly ProjectSourceFile[];
|
|
71
|
+
};
|
|
45
72
|
|
|
46
73
|
/**
|
|
47
74
|
* Run one adapter-declared selection. The switch is exhaustive over
|
|
@@ -49,14 +76,43 @@ export type FinderInput = ScenesFromEntrypointSelectionInput & PrefabsFromStorie
|
|
|
49
76
|
* here rather than silently contributing nothing.
|
|
50
77
|
*/
|
|
51
78
|
export function runFinderSelection(selection: FinderSelection, input: FinderInput): FinderResult {
|
|
52
|
-
|
|
53
|
-
case 'scenesFromEntrypointSelection':
|
|
54
|
-
return scenesFromEntrypointSelection(selection, input);
|
|
55
|
-
case 'prefabsFromStories':
|
|
56
|
-
return prefabsFromStories(selection, input);
|
|
57
|
-
default: {
|
|
58
|
-
const unreachable: never = selection;
|
|
59
|
-
throw new Error(`Unknown finder selection: ${JSON.stringify(unreachable)}`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
79
|
+
return runRegisteredFinder(selection, input);
|
|
62
80
|
}
|
|
81
|
+
|
|
82
|
+
// The engine's own finders register here, schemas beside them: what an
|
|
83
|
+
// adapter may select by name is exactly what this registry holds.
|
|
84
|
+
const ScenesFromEntrypointSelectionSchema = z
|
|
85
|
+
.object({
|
|
86
|
+
finder: z.literal('scenesFromEntrypointSelection'),
|
|
87
|
+
regions: z
|
|
88
|
+
.union([z.array(z.string().min(1)).min(1), z.literal(EXPORTED_COMPOSITION_REGIONS)])
|
|
89
|
+
.describe('Region ids whose entrypoints carry the selection, or the rule form'),
|
|
90
|
+
selection: z
|
|
91
|
+
.string()
|
|
92
|
+
.min(1)
|
|
93
|
+
.optional()
|
|
94
|
+
.describe(
|
|
95
|
+
"Identifier of the entrypoint's module-level scene selection table; omit for a " +
|
|
96
|
+
'single-composition entrypoint',
|
|
97
|
+
),
|
|
98
|
+
})
|
|
99
|
+
.strict()
|
|
100
|
+
.refine((value) => value.selection === undefined || Array.isArray(value.regions), {
|
|
101
|
+
message:
|
|
102
|
+
'scenesFromEntrypointSelection: `selection` names an identifier in ONE entrypoint’s ' +
|
|
103
|
+
`source, so it cannot be paired with the \`${EXPORTED_COMPOSITION_REGIONS}\` rule — ` +
|
|
104
|
+
'name the region explicitly (`regions: ["<rootId>"]`).',
|
|
105
|
+
});
|
|
106
|
+
registerFinder<ScenesFromEntrypointSelectionParams, FinderInput>({
|
|
107
|
+
name: 'scenesFromEntrypointSelection',
|
|
108
|
+
schema:
|
|
109
|
+
ScenesFromEntrypointSelectionSchema as unknown as ZodType<ScenesFromEntrypointSelectionParams>,
|
|
110
|
+
run: scenesFromEntrypointSelection,
|
|
111
|
+
});
|
|
112
|
+
registerFinder<PrefabsFromStoriesParams, FinderInput>({
|
|
113
|
+
name: 'prefabsFromStories',
|
|
114
|
+
schema: z
|
|
115
|
+
.object({ finder: z.literal('prefabsFromStories') })
|
|
116
|
+
.strict() as unknown as ZodType<PrefabsFromStoriesParams>,
|
|
117
|
+
run: prefabsFromStories,
|
|
118
|
+
});
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* already produced and the component index it already has.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
import type {
|
|
29
|
+
import type { DocumentEntry, PrefabsFromStoriesParams } from '../adapter-module';
|
|
30
30
|
import type { FinderResult } from './finder-result';
|
|
31
31
|
|
|
32
32
|
/** One composed portable-CSF story, as the host's story registry reports it. */
|
|
@@ -112,7 +112,7 @@ export function prefabsFromStories(
|
|
|
112
112
|
_params: PrefabsFromStoriesParams,
|
|
113
113
|
input: PrefabsFromStoriesInput,
|
|
114
114
|
): FinderResult {
|
|
115
|
-
const entries:
|
|
115
|
+
const entries: DocumentEntry[] = [];
|
|
116
116
|
const notes: string[] = [];
|
|
117
117
|
for (const [componentName, stories] of storiesByComponent(input.stories)) {
|
|
118
118
|
const component = input.components?.find((candidate) => candidate.name === componentName);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE FINDER REGISTRY — the open set of finders an adapter may SELECT by
|
|
3
|
+
* name (ARCHITECTURE-CORE §The editor protocol, "Documents, not scenes").
|
|
4
|
+
*
|
|
5
|
+
* A finder is a pure function from the project's gathered source to document
|
|
6
|
+
* entries, plus the Zod schema of its own parameters. The engine registers
|
|
7
|
+
* its two here (`./index.ts`); a contribution registers more through the
|
|
8
|
+
* same door. `adapter-module.ts` validates only the selection ENVELOPE; the
|
|
9
|
+
* parameters are checked against the finder's schema when the loader runs
|
|
10
|
+
* it — an unregistered name or a bad parameter refuses loudly, naming both.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ZodType } from 'zod';
|
|
14
|
+
import type { FinderSelection } from '../adapter-module';
|
|
15
|
+
import type { FinderResult } from './finder-result';
|
|
16
|
+
|
|
17
|
+
/** What a registration's selection type must carry: the name. A finder's own
|
|
18
|
+
* params interface satisfies this without an index signature. */
|
|
19
|
+
export type FinderSelectionEnvelope = { readonly finder: string };
|
|
20
|
+
|
|
21
|
+
export interface FinderRegistration<
|
|
22
|
+
S extends FinderSelectionEnvelope = FinderSelection,
|
|
23
|
+
I = unknown,
|
|
24
|
+
> {
|
|
25
|
+
readonly name: S['finder'];
|
|
26
|
+
/** The selection's full shape, `finder` included; `.strict()` by the
|
|
27
|
+
* same rule as every other adapter table. */
|
|
28
|
+
readonly schema: ZodType<S>;
|
|
29
|
+
readonly run: (selection: S, input: I) => FinderResult;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const registry = new Map<string, FinderRegistration>();
|
|
33
|
+
|
|
34
|
+
/** Register a finder. A second registration under one name replaces the
|
|
35
|
+
* first (a module re-evaluated on save), never throws. */
|
|
36
|
+
export function registerFinder<S extends FinderSelectionEnvelope, I>(
|
|
37
|
+
registration: FinderRegistration<S, I>,
|
|
38
|
+
): () => void {
|
|
39
|
+
const entry = registration as unknown as FinderRegistration;
|
|
40
|
+
registry.set(registration.name, entry);
|
|
41
|
+
return () => {
|
|
42
|
+
if (registry.get(registration.name) === entry) registry.delete(registration.name);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* What a CONTRIBUTION exports to register a finder (ARCHITECTURE-CORE §The
|
|
48
|
+
* project model): a plain object a project module `src/contributions/<name>.finder.ts`
|
|
49
|
+
* exports as `finder`, handed by the HOST to {@link registerContributedFinder}.
|
|
50
|
+
* A selection may carry `include` globs; the host then supplies the matching
|
|
51
|
+
* project sources on `input.sources`, which is how a finder reads the
|
|
52
|
+
* project without a file system.
|
|
53
|
+
*/
|
|
54
|
+
export type FinderContribution = FinderRegistration<FinderSelection, unknown>;
|
|
55
|
+
|
|
56
|
+
/** Register a contribution's finder after checking its shape by name. */
|
|
57
|
+
export function registerContributedFinder(value: unknown, source: string): () => void {
|
|
58
|
+
const record = (value ?? {}) as Partial<FinderContribution>;
|
|
59
|
+
const problems: string[] = [];
|
|
60
|
+
if (typeof record.name !== 'string' || record.name.length === 0)
|
|
61
|
+
problems.push('`name` must be a non-empty string');
|
|
62
|
+
const schema = record.schema as { safeParse?: unknown } | undefined;
|
|
63
|
+
if (!schema || typeof schema.safeParse !== 'function')
|
|
64
|
+
problems.push('`schema` must be a Zod schema');
|
|
65
|
+
if (typeof record.run !== 'function') problems.push('`run(selection, input)` must be a function');
|
|
66
|
+
if (problems.length > 0) throw new Error(`${source}: not a finder — ${problems.join('; ')}`);
|
|
67
|
+
return registerFinder(record as FinderContribution);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Every registered finder name, in registration order. */
|
|
71
|
+
export function registeredFinderNames(): readonly string[] {
|
|
72
|
+
return [...registry.keys()];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Validate a selection against its finder's schema and run it. */
|
|
76
|
+
export function runRegisteredFinder<I>(selection: FinderSelection, input: I): FinderResult {
|
|
77
|
+
const registration = registry.get(selection.finder);
|
|
78
|
+
if (!registration) {
|
|
79
|
+
const known = registeredFinderNames();
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Unknown finder ${JSON.stringify(selection.finder)} — registered: ${
|
|
82
|
+
known.length > 0 ? known.map((n) => `"${n}"`).join(', ') : 'none'
|
|
83
|
+
}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
const parsed = registration.schema.safeParse(selection);
|
|
87
|
+
if (!parsed.success) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Finder ${JSON.stringify(selection.finder)}: ${parsed.error.issues
|
|
90
|
+
.map((issue) => `${issue.path.join('.') || '(selection)'}: ${issue.message}`)
|
|
91
|
+
.join('; ')}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return registration.run(parsed.data, input);
|
|
95
|
+
}
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
import ts from 'typescript';
|
|
40
40
|
import type {
|
|
41
|
-
|
|
41
|
+
DocumentEntry,
|
|
42
42
|
SceneSource,
|
|
43
43
|
ScenesFromEntrypointSelectionParams,
|
|
44
44
|
} from '../adapter-module';
|
|
@@ -263,7 +263,7 @@ function targetedEntrypoints(
|
|
|
263
263
|
/** DEGENERATE: the entrypoint mounts one composition, and mounting the region
|
|
264
264
|
* IS opening it. Labelled by the module's own default export — the game's own
|
|
265
265
|
* word for this composition. */
|
|
266
|
-
function degenerateEntry(entrypoint: EntrypointSource, sf: ts.SourceFile):
|
|
266
|
+
function degenerateEntry(entrypoint: EntrypointSource, sf: ts.SourceFile): DocumentEntry {
|
|
267
267
|
return {
|
|
268
268
|
id: entrypoint.regionId,
|
|
269
269
|
label: defaultExportName(sf) ?? basename(entrypoint.path),
|
|
@@ -286,7 +286,7 @@ function selectionEntries(
|
|
|
286
286
|
selection: string,
|
|
287
287
|
resolveModule: ScenesFromEntrypointSelectionInput['resolveModule'],
|
|
288
288
|
notes: string[],
|
|
289
|
-
): { entries:
|
|
289
|
+
): { entries: DocumentEntry[]; active: string | undefined } {
|
|
290
290
|
const table = selectionTable(sf, selection);
|
|
291
291
|
if (!table) {
|
|
292
292
|
notes.push(
|
|
@@ -352,7 +352,7 @@ export function scenesFromEntrypointSelection(
|
|
|
352
352
|
input: ScenesFromEntrypointSelectionInput,
|
|
353
353
|
): FinderResult {
|
|
354
354
|
const notes: string[] = [];
|
|
355
|
-
const entries:
|
|
355
|
+
const entries: DocumentEntry[] = [];
|
|
356
356
|
const active: string[] = [];
|
|
357
357
|
|
|
358
358
|
for (const entrypoint of targetedEntrypoints(params, input.entrypoints, notes)) {
|
|
@@ -649,30 +649,59 @@ export function installSceneCapture(
|
|
|
649
649
|
recordLoop: recordAnimationLoop,
|
|
650
650
|
});
|
|
651
651
|
|
|
652
|
-
// DOM-backed Three renderers (CSS3DRenderer is the first)
|
|
653
|
-
//
|
|
654
|
-
//
|
|
652
|
+
// DOM-backed Three renderers (CSS3DRenderer is the first): trap only classes
|
|
653
|
+
// explicitly supplied by the host, and restore each byte-for-byte. Two
|
|
654
|
+
// shapes exist across three revisions and both are met: an ordinary
|
|
655
|
+
// prototype `render` is wrapped in place; a CONSTRUCTOR-ASSIGNED `render`
|
|
656
|
+
// (r180's CSS3DRenderer does `this.render = function …` exactly like
|
|
657
|
+
// WebGLRenderer, so the prototype carries none) gets the same
|
|
658
|
+
// getter/setter trap the WebGLRenderer path uses — the constructor's
|
|
659
|
+
// assignment hits the setter, the getter hands back the observing wrapper.
|
|
660
|
+
// Skipping that shape silently was measured as "the game never rendered"
|
|
661
|
+
// on css3d_periodictable while 118 of its elements sat in the DOM.
|
|
655
662
|
const additionalRendererRestores: Array<() => void> = [];
|
|
656
663
|
for (const candidate of opts?.additionalRendererCtors ?? []) {
|
|
657
664
|
const ctor = candidate as PrototypeRendererCtorLike;
|
|
658
665
|
const rendererProto = ctor?.prototype;
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
666
|
+
if (!rendererProto || rendererProto === proto) continue;
|
|
667
|
+
const priorRender = rendererProto.render;
|
|
668
|
+
if (typeof priorRender === 'function') {
|
|
669
|
+
rendererProto.render = function (this: unknown, ...args: unknown[]) {
|
|
670
|
+
observeRendererRender(this, args[0], args[1]);
|
|
671
|
+
const hooks = renderPassHooks;
|
|
672
|
+
if (hooks === null || !captured || this !== captured.renderer) {
|
|
673
|
+
return priorRender.apply(this, args);
|
|
674
|
+
}
|
|
675
|
+
hooks.before();
|
|
676
|
+
try {
|
|
677
|
+
return priorRender.apply(this, args);
|
|
678
|
+
} finally {
|
|
679
|
+
hooks.after();
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
additionalRendererRestores.push(() => {
|
|
683
|
+
rendererProto.render = priorRender;
|
|
684
|
+
});
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
const priorAddonDescriptor = Object.getOwnPropertyDescriptor(rendererProto, 'render');
|
|
688
|
+
Object.defineProperty(rendererProto, 'render', {
|
|
689
|
+
configurable: true,
|
|
690
|
+
set(this: Record<symbol, unknown>, fn: unknown) {
|
|
691
|
+
this[REAL] = fn;
|
|
692
|
+
},
|
|
693
|
+
get(this: Record<symbol, unknown>) {
|
|
694
|
+
const self = this;
|
|
695
|
+
return function render(this: unknown, ...args: unknown[]) {
|
|
696
|
+
observeRendererRender(self, args[0], args[1]);
|
|
697
|
+
return forwardRender(self, args);
|
|
698
|
+
};
|
|
699
|
+
},
|
|
700
|
+
});
|
|
674
701
|
additionalRendererRestores.push(() => {
|
|
675
|
-
|
|
702
|
+
if (priorAddonDescriptor)
|
|
703
|
+
Object.defineProperty(rendererProto, 'render', priorAddonDescriptor);
|
|
704
|
+
else delete (rendererProto as { render?: unknown }).render;
|
|
676
705
|
});
|
|
677
706
|
}
|
|
678
707
|
|
|
@@ -168,7 +168,9 @@ export interface NetPeer {
|
|
|
168
168
|
* running. `null` from `getServerConfig` means no server is declared. */
|
|
169
169
|
export interface NetServerConfig {
|
|
170
170
|
endpoint: string;
|
|
171
|
-
|
|
171
|
+
/** Known once the game's own networking adapter reports it; in edit mode
|
|
172
|
+
* the declared `process` gives only the endpoint. */
|
|
173
|
+
roomName?: string;
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
/** The signed-in player's authored identity, used as the connect default. */
|