@vgai/engine 0.5.42 → 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 +5 -0
- package/dist/adapter/authoring-seam-contract.d.ts.map +1 -1
- package/dist/adapter/authoring-seam-contract.js +1 -0
- package/dist/adapter/authoring.d.ts +16 -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/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/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/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 +1 -0
- package/src/adapter/authoring.ts +16 -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/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/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/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 },
|
|
@@ -83,6 +83,7 @@ export const INSPECTOR_PROVIDER_SHAPE = defineSeamShape<InspectorProvider>()({
|
|
|
83
83
|
properties: { optional: false, kind: 'function', required: 'operation' },
|
|
84
84
|
get: { optional: false, kind: 'function', required: 'operation' },
|
|
85
85
|
set: { optional: false, kind: 'function', required: 'effect' },
|
|
86
|
+
preview: { optional: true, kind: 'function', required: 'effect' },
|
|
86
87
|
editability: { optional: true, kind: 'function', required: 'operation' },
|
|
87
88
|
remove: { optional: true, kind: 'function', required: 'round-trip' },
|
|
88
89
|
});
|
package/src/adapter/authoring.ts
CHANGED
|
@@ -346,6 +346,22 @@ export interface InspectorProvider {
|
|
|
346
346
|
* performed by this provider, and the shell reports the live-only floor.
|
|
347
347
|
*/
|
|
348
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;
|
|
349
365
|
/** Optional per-subject write preflight for shell affordances outside the
|
|
350
366
|
* Inspector field grid (notably hierarchy eye/lock controls). The property
|
|
351
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. */
|
|
@@ -150,19 +150,19 @@ function canvasWorldAdapter(id: string, component: ComponentType): RootAdapter<'
|
|
|
150
150
|
const unmounted = new Promise<void>((resolve) => {
|
|
151
151
|
resolveUnmounted = resolve;
|
|
152
152
|
});
|
|
153
|
+
let resolveEffectsUnmounted!: () => void;
|
|
154
|
+
const effectsUnmounted = new Promise<void>((resolve) => {
|
|
155
|
+
resolveEffectsUnmounted = resolve;
|
|
156
|
+
});
|
|
153
157
|
/**
|
|
154
|
-
* Signals the tree's FIRST COMMIT and
|
|
155
|
-
*
|
|
158
|
+
* Signals the tree's FIRST COMMIT and both unmount cleanup phases. Rendered as the LAST
|
|
159
|
+
* child deliberately:
|
|
156
160
|
*
|
|
157
161
|
* - mount: layout effects run synchronously inside the commit, before
|
|
158
162
|
* any passive effect and before any tick can be scheduled;
|
|
159
|
-
* - unmount:
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* finished tearing down" signal — and it is load-bearing, because
|
|
163
|
-
* `useTick`'s own cleanup calls `ticker.remove()`. Destroying the
|
|
164
|
-
* Application before that cleanup lands throws inside React's commit
|
|
165
|
-
* on every teardown of any world with a tick hook.
|
|
163
|
+
* - unmount: the layout cleanup fences `useTick` and the passive cleanup fences ordinary
|
|
164
|
+
* project effects. Native destruction waits for BOTH; resolving only from layout left
|
|
165
|
+
* passive listener/resource cleanup racing an Application whose renderer was null.
|
|
166
166
|
*/
|
|
167
167
|
function Lifecycle(): null {
|
|
168
168
|
useLayoutEffect(() => {
|
|
@@ -172,7 +172,10 @@ function canvasWorldAdapter(id: string, component: ComponentType): RootAdapter<'
|
|
|
172
172
|
// This component is the last sibling. Reaching its passive effect
|
|
173
173
|
// means the initial tree's provider/command effects have registered,
|
|
174
174
|
// so mount() can truthfully back Play's ack.
|
|
175
|
-
useEffect(
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
resolveEffectsReady();
|
|
177
|
+
return resolveEffectsUnmounted;
|
|
178
|
+
}, []);
|
|
176
179
|
return null;
|
|
177
180
|
}
|
|
178
181
|
|
|
@@ -282,7 +285,14 @@ function canvasWorldAdapter(id: string, component: ComponentType): RootAdapter<'
|
|
|
282
285
|
// Adapter surface: the host's shared debug registry. The game's own
|
|
283
286
|
// capabilities arrive as the entry module's declared `systems`.
|
|
284
287
|
const systemAdapters: SystemAdapters = registry ? { debug: registry.adapter } : {};
|
|
285
|
-
const disposeComplete = unmounted.then(() => {
|
|
288
|
+
const disposeComplete = Promise.all([unmounted, effectsUnmounted]).then(() => {
|
|
289
|
+
// React can tear the root down itself after a fatal layout/effect error. In that path the
|
|
290
|
+
// host has not called dispose() yet, but Application.destroy() still clears Pixi's ticker
|
|
291
|
+
// and renderer. Wait for both layout and passive cleanup first: project effects may own
|
|
292
|
+
// window/document listeners and can still read the Pixi Application while React drains
|
|
293
|
+
// passive deletion effects. Fence the mounted root before clearing the native slots so
|
|
294
|
+
// subsequent host frames and the eventual host teardown cannot re-enter them.
|
|
295
|
+
disposed = true;
|
|
286
296
|
mountedApp.destroy(false, { children: true });
|
|
287
297
|
});
|
|
288
298
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* target file" hole, closed for declared refs — see
|
|
28
28
|
* `collectDeclaredRefFields`/`findMissingRefTargets` in `data-check-core.ts`).
|
|
29
29
|
* 3. **Tools never ship** (`generateBundle`): no emitted chunk may contain a
|
|
30
|
-
* module from `src/tools/` or a `*.tool.*` file (§4: tester serves and
|
|
30
|
+
* module from `src/contributions/`, `src/tools/` or a `*.tool.*` file (§4: tester serves and
|
|
31
31
|
* standalone builds strip tools entirely; W4 made this true by
|
|
32
32
|
* construction — nothing imports tools — and this check PINS it against
|
|
33
33
|
* the day some game module imports a tool "just for a helper").
|
|
@@ -268,7 +268,9 @@ export async function collectDataCheckProblems(options: VgaiDataCheckOptions): P
|
|
|
268
268
|
/** True iff a bundled module id is tool code that must never ship (§4). Pure, unit-tested. */
|
|
269
269
|
export function isToolModuleId(id: string): boolean {
|
|
270
270
|
const normalized = id.split('\\').join('/');
|
|
271
|
-
return
|
|
271
|
+
return (
|
|
272
|
+
/\/src\/(?:tools|contributions)\//.test(normalized) || /\.tool\.[tj]sx?(\?|$)/.test(normalized)
|
|
273
|
+
);
|
|
272
274
|
}
|
|
273
275
|
|
|
274
276
|
/**
|