@vgai/engine 0.5.16 → 0.5.18
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 +1 -16
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +1 -11
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +0 -2
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +3 -6
- package/dist/adapter/renderer-config.d.ts +83 -0
- package/dist/adapter/renderer-config.d.ts.map +1 -0
- package/dist/adapter/renderer-config.js +121 -0
- package/dist/adapter/root-adapter.d.ts +6 -2
- package/dist/adapter/root-adapter.d.ts.map +1 -1
- package/dist/adapter/root-seam-contract.d.ts +5 -0
- package/dist/adapter/root-seam-contract.d.ts.map +1 -1
- package/dist/adapter/root-seam-contract.js +1 -0
- package/dist/adapter/setup-three-root-adapter.js +9 -9
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-adapter.js +92 -41
- package/dist/data/curve.d.ts +1 -1
- package/dist/data/curve.js +2 -2
- package/dist/dev/instruments.d.ts +4 -4
- package/dist/dev/instruments.js +4 -4
- package/dist/dev/register-render-vitals.d.ts +2 -2
- package/dist/dev/register-render-vitals.js +2 -2
- package/dist/dev/static-batch-advisor.d.ts +1 -1
- package/dist/dev/static-batch-advisor.js +2 -2
- package/dist/input/input-manager.d.ts +15 -0
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +22 -0
- package/dist/manifest/load.d.ts +0 -3
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +0 -4
- package/dist/manifest/schema.d.ts +0 -2
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +3 -24
- package/dist/pixi/authoring.d.ts +33 -0
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +86 -6
- package/dist/react/world-state.d.ts +12 -0
- package/dist/react/world-state.d.ts.map +1 -1
- package/dist/react/world-state.js +14 -0
- package/dist/render/soft-particle-depth.d.ts +3 -3
- package/dist/render/soft-particle-depth.d.ts.map +1 -1
- package/dist/render/soft-particle-depth.js +8 -13
- package/dist/runtime/create-runtime.d.ts +2 -16
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +36 -38
- package/dist/runtime/dev-build.d.ts +37 -0
- package/dist/runtime/dev-build.d.ts.map +1 -0
- package/dist/runtime/dev-build.js +49 -0
- package/dist/runtime/game.d.ts +9 -1
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +25 -1
- package/dist/runtime/mount-manifest.d.ts +0 -13
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +2 -21
- package/dist/world3d-react/engine-bridge.d.ts.map +1 -1
- package/dist/world3d-react/engine-bridge.js +28 -3
- package/dist/world3d-react/index.d.ts +2 -2
- package/dist/world3d-react/index.d.ts.map +1 -1
- package/dist/world3d-react/index.js +2 -2
- package/dist/world3d-react/r3f-adapter.d.ts +2 -2
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +90 -15
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +12 -2
- package/dist/world3d-react/rapier-physics-bridge.d.ts +4 -4
- package/dist/world3d-react/rapier-physics-bridge.js +3 -3
- package/dist/world3d-react/renderer-config.d.ts +7 -69
- package/dist/world3d-react/renderer-config.d.ts.map +1 -1
- package/dist/world3d-react/renderer-config.js +7 -108
- package/dist/world3d-react/world-context.d.ts +43 -0
- package/dist/world3d-react/world-context.d.ts.map +1 -1
- package/dist/world3d-react/world-context.js +89 -46
- package/dist-config/config.js.map +1 -1
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +3 -3
- package/schemas/vgai-project.schema.json +2 -7
- package/src/adapter/adapter-module.ts +1 -25
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +3 -8
- package/src/adapter/renderer-config.ts +178 -0
- package/src/adapter/root-adapter.ts +6 -2
- package/src/adapter/root-seam-contract.ts +1 -0
- package/src/adapter/setup-three-root-adapter.ts +9 -9
- package/src/canvas-react/pixi-react-adapter.tsx +98 -53
- package/src/data/curve.ts +2 -2
- package/src/dev/instruments.ts +4 -4
- package/src/dev/register-render-vitals.ts +2 -2
- package/src/dev/static-batch-advisor.ts +2 -2
- package/src/input/input-manager.ts +23 -0
- package/src/manifest/load.ts +0 -7
- package/src/manifest/schema.ts +3 -26
- package/src/pixi/authoring.ts +98 -6
- package/src/react/world-state.tsx +15 -0
- package/src/render/soft-particle-depth.ts +8 -13
- package/src/runtime/create-runtime.ts +38 -52
- package/src/runtime/dev-build.ts +49 -0
- package/src/runtime/game.ts +31 -2
- package/src/runtime/mount-manifest.ts +2 -33
- package/src/world3d-react/engine-bridge.ts +28 -3
- package/src/world3d-react/index.ts +5 -5
- package/src/world3d-react/r3f-adapter.tsx +89 -20
- package/src/world3d-react/r3f-root-factory.tsx +12 -2
- package/src/world3d-react/rapier-physics-bridge.tsx +4 -4
- package/src/world3d-react/renderer-config.ts +7 -164
- package/src/world3d-react/world-context.ts +100 -51
- package/dist/runtime/dev-layers.d.ts +0 -34
- package/dist/runtime/dev-layers.d.ts.map +0 -1
- package/dist/runtime/dev-layers.js +0 -46
- package/src/runtime/dev-layers.ts +0 -46
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `@react-three/rapier` physics VISIBLE to the engine's editor/dev seams.
|
|
4
4
|
*
|
|
5
5
|
* WHY IT EXISTS. `SystemAdapters.physics` is what the editor's transform
|
|
6
|
-
* coordination (`freeze → apply → unfreeze`) and the
|
|
6
|
+
* coordination (`freeze → apply → unfreeze`) and the universal
|
|
7
7
|
* "Colliders drawn" instrument (`@engine/dev/instruments`) read. The
|
|
8
8
|
* first-party imperative three root registers one from
|
|
9
9
|
* `../adapter/setup-three-root-adapter.ts`; an R3F world builds no first-party
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* on the R3F root it is mounted in. It allocates nothing else — no timers, no
|
|
45
45
|
* subscriptions, no per-frame work (see the phase note below), and no Rapier
|
|
46
46
|
* objects: the bodies it edits belong to `<Physics>`.
|
|
47
|
-
* SHARERS: every reader of `game.systemAdapters.physics` — the
|
|
47
|
+
* SHARERS: every reader of `game.systemAdapters.physics` — the
|
|
48
48
|
* colliders instrument, the editor's transform coordination. They read; they
|
|
49
49
|
* never dispose.
|
|
50
50
|
* TEARDOWN: the effect's cleanup, and nothing else — it clears the SAME key it
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
*
|
|
56
56
|
* PHASE ORDERING: none to respect — this component registers pull-only
|
|
57
57
|
* callbacks and runs no per-frame work. Every method it exposes is invoked
|
|
58
|
-
* BETWEEN frames, by an editor gesture or
|
|
58
|
+
* BETWEEN frames, by an editor gesture or an instrument command, never from a
|
|
59
59
|
* phase.
|
|
60
60
|
*/
|
|
61
61
|
|
|
@@ -78,7 +78,7 @@ import { useGameContext } from './engine-bridge';
|
|
|
78
78
|
export interface RapierPhysicsBridgeProps {
|
|
79
79
|
/**
|
|
80
80
|
* Set the `debug` prop of the SAME `<Physics>` this bridge is mounted in.
|
|
81
|
-
* The
|
|
81
|
+
* The "Colliders drawn" instrument calls it; the world's own state is
|
|
82
82
|
* what `<Physics debug={…}>` reads, so the library's debug rendering is the
|
|
83
83
|
* only thing that ever draws a collider.
|
|
84
84
|
*/
|
|
@@ -1,168 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* world3d-react/renderer-config
|
|
2
|
+
* `@engine/world3d-react/renderer-config` — a published entry point.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
* engine
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* So a world may DECLARE the pipeline it was authored for, and `createR3FAdapter` applies it to the
|
|
11
|
-
* host's renderer for the life of the mount, restoring what it found on dispose. Three properties
|
|
12
|
-
* of that shape are load-bearing:
|
|
13
|
-
*
|
|
14
|
-
* - **It is per-renderer, never engine-wide.** Every field here is a `WebGLRenderer` instance
|
|
15
|
-
* property, and a play root gets its own renderer (`create-runtime.ts`). Nothing here reaches a
|
|
16
|
-
* module-level three global, so one world's declaration cannot change how the editor's own
|
|
17
|
-
* viewport, another root, or a thumbnail bake renders.
|
|
18
|
-
* - **Absent means "leave the host's value alone".** Every field is optional and an omitted one
|
|
19
|
-
* is never written, so declaring a tone mapping does not silently reset the clear colour.
|
|
20
|
-
* - **It is restored on dispose.** The renderer outlives the mount, so a world that did not put
|
|
21
|
-
* back what it found would leak its pipeline into whatever mounts next.
|
|
22
|
-
*
|
|
23
|
-
* This is deliberately NOT a general render-settings system. It carries what a world can honestly
|
|
24
|
-
* state about its own colour pipeline and nothing else; a property the host fixes at CONSTRUCTION
|
|
25
|
-
* (the WebGL context's `antialias` attribute, and therefore the MSAA sample count) cannot be
|
|
26
|
-
* declared here, because there would be no honest moment to apply it.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import type * as THREE from 'three';
|
|
30
|
-
|
|
31
|
-
/** The tone-mapping operators three exposes, named as data rather than as three's numeric enum. */
|
|
32
|
-
export type WorldToneMapping =
|
|
33
|
-
| 'none'
|
|
34
|
-
| 'linear'
|
|
35
|
-
| 'reinhard'
|
|
36
|
-
| 'cineon'
|
|
37
|
-
| 'aces'
|
|
38
|
-
| 'agx'
|
|
39
|
-
| 'neutral';
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* The output transfer function the frame is written with.
|
|
43
|
-
*
|
|
44
|
-
* - `srgb` — three's own default and this engine's: linear lighting, sRGB encode on output.
|
|
45
|
-
* - `srgb-linear` — NO output transform. This is what a gamma-space renderer needs: the shading
|
|
46
|
-
* result is already in display space and encoding it a second time washes the frame out.
|
|
47
|
-
*/
|
|
48
|
-
export type WorldOutputColorSpace = 'srgb' | 'srgb-linear';
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* The shadow-map filter, named as data rather than as three's numeric enum.
|
|
52
|
-
*
|
|
53
|
-
* This is a renderer INSTANCE property (`WebGLRenderer.shadowMap.type`), not a context attribute,
|
|
54
|
-
* so unlike MSAA it has an honest moment at which a world can ask for it — which is the whole test
|
|
55
|
-
* this file's header states. A source engine that declares its own shadow filter (Godot 3's
|
|
56
|
-
* `rendering/quality/shadows/filter_mode`) would otherwise inherit whatever the host built with.
|
|
57
|
-
*/
|
|
58
|
-
export type WorldShadowMapType = 'basic' | 'pcf' | 'pcf-soft' | 'vsm';
|
|
59
|
-
|
|
60
|
-
/** What a world may declare about the renderer that draws it. Every field is optional; see header. */
|
|
61
|
-
export interface WorldRendererConfig {
|
|
62
|
-
readonly toneMapping?: WorldToneMapping | undefined;
|
|
63
|
-
readonly toneMappingExposure?: number | undefined;
|
|
64
|
-
readonly outputColorSpace?: WorldOutputColorSpace | undefined;
|
|
65
|
-
/**
|
|
66
|
-
* `WebGLRenderer.shadowMap.type`. Writing it after a shadow map has already been built needs
|
|
67
|
-
* `shadowMap.needsUpdate`, which this function sets — three caches the compiled depth material
|
|
68
|
-
* per type and would otherwise keep filtering with the previous one.
|
|
69
|
-
*/
|
|
70
|
-
readonly shadowMapType?: WorldShadowMapType | undefined;
|
|
71
|
-
/**
|
|
72
|
-
* The colour the frame is cleared to, as a CSS hex string. The renderer's existing clear ALPHA
|
|
73
|
-
* is preserved: a stacked canvas is transparent on purpose (`create-runtime.ts` gives every
|
|
74
|
-
* non-bottom root `alpha: true`), and forcing it opaque here would hide every layer below.
|
|
75
|
-
*/
|
|
76
|
-
readonly clearColor?: string | undefined;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Apply `config` to `renderer`, returning the restore function that puts back what was there.
|
|
81
|
-
*
|
|
82
|
-
* `three` is passed in rather than imported for values so the enum constants come from the HOST's
|
|
83
|
-
* three instance — the same identity rule `r3f-adapter.tsx` follows for the scene and camera.
|
|
4
|
+
* The engine package's export map is the wildcard `"./*"`, so every file under
|
|
5
|
+
* `packages/engine/src/` is an entry point a game outside this repo can import by path. The module
|
|
6
|
+
* itself now lives in the adapter seam (`@engine/adapter/renderer-config`, which its header
|
|
7
|
+
* explains), and this file keeps the path that shipped resolving to it. Import the seam path in
|
|
8
|
+
* new code.
|
|
84
9
|
*/
|
|
85
|
-
export function applyWorldRendererConfig(
|
|
86
|
-
three: typeof THREE,
|
|
87
|
-
renderer: THREE.WebGLRenderer,
|
|
88
|
-
config: WorldRendererConfig,
|
|
89
|
-
): () => void {
|
|
90
|
-
// A host that mounts a world WITHOUT rasterizing it hands the adapter a duck-typed renderer —
|
|
91
|
-
// the editor's design session (`createDesignTimeRenderer`: four members, deliberately never
|
|
92
|
-
// widened) and jsdom test harnesses both do. Such a surface has no colour pipeline to configure:
|
|
93
|
-
// the frame the user sees is drawn by a DIFFERENT renderer (the editor's own), so applying the
|
|
94
|
-
// world's config there is meaningless — and calling `getClearColor` on it is a TypeError that
|
|
95
|
-
// unmounts the whole world at edit time (measured: every Godot port's edit viewport blanked with
|
|
96
|
-
// '"world" failed to mount — renderer.getClearColor is not a function'). Detect the real
|
|
97
|
-
// `WebGLRenderer` surface by the one method this function must call, and no-op otherwise.
|
|
98
|
-
if (typeof renderer.getClearColor !== 'function') {
|
|
99
|
-
return () => {};
|
|
100
|
-
}
|
|
101
|
-
const toneMappings: Record<WorldToneMapping, THREE.ToneMapping> = {
|
|
102
|
-
none: three.NoToneMapping,
|
|
103
|
-
linear: three.LinearToneMapping,
|
|
104
|
-
reinhard: three.ReinhardToneMapping,
|
|
105
|
-
cineon: three.CineonToneMapping,
|
|
106
|
-
aces: three.ACESFilmicToneMapping,
|
|
107
|
-
agx: three.AgXToneMapping,
|
|
108
|
-
neutral: three.NeutralToneMapping,
|
|
109
|
-
};
|
|
110
|
-
const colorSpaces: Record<WorldOutputColorSpace, THREE.ColorSpace> = {
|
|
111
|
-
srgb: three.SRGBColorSpace,
|
|
112
|
-
'srgb-linear': three.LinearSRGBColorSpace,
|
|
113
|
-
};
|
|
114
|
-
const shadowMapTypes: Record<WorldShadowMapType, THREE.ShadowMapType> = {
|
|
115
|
-
basic: three.BasicShadowMap,
|
|
116
|
-
pcf: three.PCFShadowMap,
|
|
117
|
-
'pcf-soft': three.PCFSoftShadowMap,
|
|
118
|
-
vsm: three.VSMShadowMap,
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const restores: (() => void)[] = [];
|
|
122
|
-
|
|
123
|
-
if (config.toneMapping !== undefined) {
|
|
124
|
-
const previous = renderer.toneMapping;
|
|
125
|
-
renderer.toneMapping = toneMappings[config.toneMapping];
|
|
126
|
-
restores.push(() => {
|
|
127
|
-
renderer.toneMapping = previous;
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
if (config.toneMappingExposure !== undefined) {
|
|
131
|
-
const previous = renderer.toneMappingExposure;
|
|
132
|
-
renderer.toneMappingExposure = config.toneMappingExposure;
|
|
133
|
-
restores.push(() => {
|
|
134
|
-
renderer.toneMappingExposure = previous;
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
if (config.outputColorSpace !== undefined) {
|
|
138
|
-
const previous = renderer.outputColorSpace;
|
|
139
|
-
renderer.outputColorSpace = colorSpaces[config.outputColorSpace];
|
|
140
|
-
restores.push(() => {
|
|
141
|
-
renderer.outputColorSpace = previous;
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
if (config.shadowMapType !== undefined && renderer.shadowMap !== undefined) {
|
|
145
|
-
const previous = renderer.shadowMap.type;
|
|
146
|
-
renderer.shadowMap.type = shadowMapTypes[config.shadowMapType];
|
|
147
|
-
renderer.shadowMap.needsUpdate = true;
|
|
148
|
-
restores.push(() => {
|
|
149
|
-
renderer.shadowMap.type = previous;
|
|
150
|
-
renderer.shadowMap.needsUpdate = true;
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
if (config.clearColor !== undefined) {
|
|
154
|
-
const previousColor = new three.Color();
|
|
155
|
-
renderer.getClearColor(previousColor);
|
|
156
|
-
// Alpha is READ BACK and re-passed, never assumed: see `clearColor`'s doc above.
|
|
157
|
-
const alpha = renderer.getClearAlpha();
|
|
158
|
-
renderer.setClearColor(new three.Color(config.clearColor), alpha);
|
|
159
|
-
restores.push(() => {
|
|
160
|
-
renderer.setClearColor(previousColor, alpha);
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
10
|
|
|
164
|
-
|
|
165
|
-
// Reverse order, so a field written twice (it cannot be, today) unwinds correctly.
|
|
166
|
-
for (let i = restores.length - 1; i >= 0; i--) restores[i]?.();
|
|
167
|
-
};
|
|
168
|
-
}
|
|
11
|
+
export * from '../adapter/renderer-config';
|
|
@@ -147,6 +147,103 @@ export interface R3FRootRuntime {
|
|
|
147
147
|
dispose(): void;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* The GAME-SCOPED input wiring one three root owns — and the half that has
|
|
152
|
+
* nothing to do with the vgai runtime context.
|
|
153
|
+
*
|
|
154
|
+
* Two things happen here, both of them HOST work performed from outside the
|
|
155
|
+
* world's own tree:
|
|
156
|
+
*
|
|
157
|
+
* 1. the project's input map is loaded through `Game.loadInputMap` (game-owned,
|
|
158
|
+
* load-once), so the actions the project declared exist;
|
|
159
|
+
* 2. this root's seams are registered on the game-scoped debug registry
|
|
160
|
+
* (`setVirtualInputTarget`/`setInputActionsSource`/`setInputTraceSource`),
|
|
161
|
+
* which is what makes `game.input.hold/tap/...` (the bot/`vgai eval` input
|
|
162
|
+
* doctrine) and the built-in `input.actions`/`input.trace` providers
|
|
163
|
+
* resolve to this world instead of throwing `DEBUG_INPUT_UNAVAILABLE`.
|
|
164
|
+
*
|
|
165
|
+
* It is called on BOTH three-root paths, and that is the point: a world that
|
|
166
|
+
* mounts as a default-exported component receives no vgai runtime context
|
|
167
|
+
* (`engineRuntime: false` — see `r3f-root-factory.tsx`), but "no context in the
|
|
168
|
+
* tree" was never meant to say "the host does not wire this game's input". It
|
|
169
|
+
* was read that way, and the cost was measured on a fresh scaffold: the
|
|
170
|
+
* declared map never loaded and every `game.input.*` door threw, so the QA
|
|
171
|
+
* bot, autoplay routes and `vgai eval` could not drive the ONE world shape
|
|
172
|
+
* `vgai create` produces. Same split, same reasoning, as `WorldProvider`
|
|
173
|
+
* riding `host.game` rather than the runtime (see `r3f-adapter.tsx`).
|
|
174
|
+
*
|
|
175
|
+
* `optionalInputMap` is the CONVENTIONAL-PATH probe used when the project
|
|
176
|
+
* never named a map: an absent file is the ordinary state of a brand-new
|
|
177
|
+
* project, so it resolves quietly instead of printing an error on every boot.
|
|
178
|
+
* A map that exists and fails to parse is as loud as ever.
|
|
179
|
+
*
|
|
180
|
+
* LIFETIME, honestly: `registry.strip(id)` runs on the mount's dispose (both
|
|
181
|
+
* paths), but it walks providers/commands only — the three seams set here
|
|
182
|
+
* survive it, keyed by this world's id, until a remount of the same id
|
|
183
|
+
* overwrites them. That residue is inert rather than hidden: every closure
|
|
184
|
+
* below reads GAME-scoped state (`game.input`, `game.loop.fixedDt`, the game's
|
|
185
|
+
* seed), so a stale entry actuates exactly what a live one would. See the
|
|
186
|
+
* dispose comment in `r3f-adapter.tsx` for the full accounting.
|
|
187
|
+
*/
|
|
188
|
+
export function wireGameInputSeams(
|
|
189
|
+
host: ThreeHostContext,
|
|
190
|
+
registry: DebugRegistry,
|
|
191
|
+
options: {
|
|
192
|
+
readonly id: string;
|
|
193
|
+
readonly inputMapPath?: string | null | undefined;
|
|
194
|
+
readonly optionalInputMap?: boolean | undefined;
|
|
195
|
+
},
|
|
196
|
+
): Promise<void> {
|
|
197
|
+
const { id, inputMapPath = DEFAULT_INPUT_MAP_PATH, optionalInputMap = false } = options;
|
|
198
|
+
if (!host.game) return Promise.resolve();
|
|
199
|
+
const game = host.game;
|
|
200
|
+
const input = game.input;
|
|
201
|
+
registry.setInputActionsSource(id, () =>
|
|
202
|
+
input.actionNames().map((name) => ({ name, valueType: input.getActionValueType(name) })),
|
|
203
|
+
);
|
|
204
|
+
registry.setInputTraceSource(id, () => {
|
|
205
|
+
const raw = input.getInputTrace();
|
|
206
|
+
return {
|
|
207
|
+
version: raw.version,
|
|
208
|
+
seed: getSeededRandom(game)?.seed ?? null,
|
|
209
|
+
fixedDt: game.loop.fixedDt,
|
|
210
|
+
ticks: raw.ticks,
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
registry.setVirtualInputTarget(id, {
|
|
214
|
+
setVirtualAction: (action, value) => input.setVirtualAction(action, value),
|
|
215
|
+
tapVirtualAction: (action) => input.tapVirtualAction(action),
|
|
216
|
+
clearVirtualActions: () => input.clearVirtualActions(),
|
|
217
|
+
scheduleActionAtTick: (tick, action, value) => input.scheduleActionAtTick(tick, action, value),
|
|
218
|
+
startInputRecording: () => input.startInputRecording(),
|
|
219
|
+
stopInputRecording: () => input.stopInputRecording(),
|
|
220
|
+
isInputRecording: () => input.isInputRecording(),
|
|
221
|
+
injectAxis: (sourceId, value) => input.injectAxis(sourceId, value),
|
|
222
|
+
injectVector2: (sourceId, value) => input.injectVector2(sourceId, value),
|
|
223
|
+
injectPointerDelta: (sourceId, delta) => input.injectPointerDelta(sourceId, delta),
|
|
224
|
+
injectPointerPosition: (sourceId, value) => input.injectPointerPosition(sourceId, value),
|
|
225
|
+
});
|
|
226
|
+
if (host.headless || inputMapPath === null) return Promise.resolve();
|
|
227
|
+
// Load-once through the game-owned path (competing paths across roots throw
|
|
228
|
+
// THERE, loudly). A FAILED load (missing/bad file) must not fail the mount:
|
|
229
|
+
// a three world with no declared actions is legal. It degrades loudly
|
|
230
|
+
// instead — naming exactly what breaks.
|
|
231
|
+
// The non-optional call passes ONE argument, exactly as it always has — a
|
|
232
|
+
// trailing `undefined` is a different call to any observer of it.
|
|
233
|
+
const load = optionalInputMap
|
|
234
|
+
? game.loadInputMap(inputMapPath, { optional: true })
|
|
235
|
+
: game.loadInputMap(inputMapPath);
|
|
236
|
+
return load.catch((err: unknown) => {
|
|
237
|
+
// biome-ignore lint/suspicious/noConsole: deliberate loud degrade — the documented alternative to failing the mount (see comment above)
|
|
238
|
+
console.error(
|
|
239
|
+
`three world "${id}": failed to load input map "${inputMapPath}" — declared input ` +
|
|
240
|
+
'actions and `game.input.*` (bot/virtual input) will not work until a valid map ' +
|
|
241
|
+
'loads. Ship one at the conventional path or pass `inputMapPath` to ' +
|
|
242
|
+
`createR3FAdapter (null to opt out). Cause: ${err instanceof Error ? err.message : String(err)}`,
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
150
247
|
/**
|
|
151
248
|
* Build the engine runtime for one R3F world from a `ThreeHostContext` — the same
|
|
152
249
|
* wiring, at the same layer, as the classic `SetupThreeRootAdapter.mount()`
|
|
@@ -216,57 +313,9 @@ export function createR3FRootContext(
|
|
|
216
313
|
gameClock ?? createSimClock({ dispose: (obj) => disposeDebrisSubtree(obj, []) });
|
|
217
314
|
ctx.clock = clock;
|
|
218
315
|
|
|
219
|
-
// --- ctx.input + the debug-registry input seams (gap 3) —
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
// `game.input.*` (virtual input) and the built-in `input.actions`/
|
|
223
|
-
// `input.trace` providers resolve to it. ---
|
|
224
|
-
let inputMapReady: Promise<void> = Promise.resolve();
|
|
225
|
-
if (host.game) {
|
|
226
|
-
const game = host.game;
|
|
227
|
-
const input = game.input;
|
|
228
|
-
debugRegistry.setInputActionsSource(id, () =>
|
|
229
|
-
input.actionNames().map((name) => ({ name, valueType: input.getActionValueType(name) })),
|
|
230
|
-
);
|
|
231
|
-
debugRegistry.setInputTraceSource(id, () => {
|
|
232
|
-
const raw = input.getInputTrace();
|
|
233
|
-
return {
|
|
234
|
-
version: raw.version,
|
|
235
|
-
seed: getSeededRandom(game)?.seed ?? null,
|
|
236
|
-
fixedDt: game.loop.fixedDt,
|
|
237
|
-
ticks: raw.ticks,
|
|
238
|
-
};
|
|
239
|
-
});
|
|
240
|
-
debugRegistry.setVirtualInputTarget(id, {
|
|
241
|
-
setVirtualAction: (action, value) => input.setVirtualAction(action, value),
|
|
242
|
-
tapVirtualAction: (action) => input.tapVirtualAction(action),
|
|
243
|
-
clearVirtualActions: () => input.clearVirtualActions(),
|
|
244
|
-
scheduleActionAtTick: (tick, action, value) =>
|
|
245
|
-
input.scheduleActionAtTick(tick, action, value),
|
|
246
|
-
startInputRecording: () => input.startInputRecording(),
|
|
247
|
-
stopInputRecording: () => input.stopInputRecording(),
|
|
248
|
-
isInputRecording: () => input.isInputRecording(),
|
|
249
|
-
injectAxis: (sourceId, value) => input.injectAxis(sourceId, value),
|
|
250
|
-
injectVector2: (sourceId, value) => input.injectVector2(sourceId, value),
|
|
251
|
-
injectPointerDelta: (sourceId, delta) => input.injectPointerDelta(sourceId, delta),
|
|
252
|
-
injectPointerPosition: (sourceId, value) => input.injectPointerPosition(sourceId, value),
|
|
253
|
-
});
|
|
254
|
-
if (!host.headless && inputMapPath !== null) {
|
|
255
|
-
// Load-once through the game-owned path (classic parity — competing
|
|
256
|
-
// paths across roots throw THERE, loudly). A FAILED load (missing/bad
|
|
257
|
-
// file) must not fail this mount: an R3F tree with no declared actions
|
|
258
|
-
// is legal. It degrades loudly instead — naming exactly what breaks.
|
|
259
|
-
inputMapReady = game.loadInputMap(inputMapPath).catch((err: unknown) => {
|
|
260
|
-
// biome-ignore lint/suspicious/noConsole: deliberate loud degrade — the documented alternative to failing the mount (see comment above)
|
|
261
|
-
console.error(
|
|
262
|
-
`R3F world "${id}": failed to load input map "${inputMapPath}" — declared input ` +
|
|
263
|
-
'actions and `game.input.*` (bot/virtual input) will not work until a valid map ' +
|
|
264
|
-
'loads. Ship one at the conventional path or pass `inputMapPath` to ' +
|
|
265
|
-
`createR3FAdapter (null to opt out). Cause: ${err instanceof Error ? err.message : String(err)}`,
|
|
266
|
-
);
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
}
|
|
316
|
+
// --- ctx.input + the debug-registry input seams (gap 3) — see
|
|
317
|
+
// `wireGameInputSeams` below; `ctx.input` is that same game-owned manager. ---
|
|
318
|
+
const inputMapReady = wireGameInputSeams(host, debugRegistry, { id, inputMapPath });
|
|
270
319
|
|
|
271
320
|
return {
|
|
272
321
|
ctx,
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* THE one owner of "are dev layers enabled" (`vgai.project.json`'s `dev: true`
|
|
3
|
-
* root flag). Every reader that has to answer that question calls
|
|
4
|
-
* {@link devLayersEnabled}; there is deliberately NO second source of truth —
|
|
5
|
-
* not a module-level cached boolean, not a `globalThis` flag, not a per-host
|
|
6
|
-
* copy of the `import.meta.env` read below. A dev GUI that ships to players
|
|
7
|
-
* because two places disagreed about what "dev" means is exactly the failure
|
|
8
|
-
* this single owner exists to make impossible.
|
|
9
|
-
*
|
|
10
|
-
* Ownership, stated in one place (the build rule):
|
|
11
|
-
* - OWNER: this function. It resolves the answer; nothing else derives it.
|
|
12
|
-
* - SHARERS: `mountManifestRoots` (the ship gate — a dev root only mounts
|
|
13
|
-
* when this returns true). Any future host that mounts manifest roots
|
|
14
|
-
* itself calls this too, with its own `override`.
|
|
15
|
-
* - TEARDOWN: none. This is a pure predicate over build config and one
|
|
16
|
-
* caller-supplied argument — it owns no resource, allocates nothing, and
|
|
17
|
-
* has no lifecycle to end.
|
|
18
|
-
*
|
|
19
|
-
* The three inputs, highest precedence first:
|
|
20
|
-
* 1. `override` — the explicit per-mount answer. A headless test, a capture
|
|
21
|
-
* harness, or a host that knows better passes `true`/`false` and gets
|
|
22
|
-
* exactly that. Passing `undefined` (or omitting it) means "decide for
|
|
23
|
-
* me" and falls through.
|
|
24
|
-
* 2. A dev build — `import.meta.env.DEV`. The ordinary local/editor case:
|
|
25
|
-
* dev layers are on.
|
|
26
|
-
* 3. A production build's EXPLICIT opt-in — `VITE_VGAI_DEV_LAYERS=true`.
|
|
27
|
-
* Shipping the dev GUI in a production bundle is a real, legitimate
|
|
28
|
-
* choice (an internal playtest build, a QA build), and it must be an
|
|
29
|
-
* opt-in someone had to type, never something a default drifts into.
|
|
30
|
-
*
|
|
31
|
-
* Anything else — a production build with no opt-in — is `false`.
|
|
32
|
-
*/
|
|
33
|
-
export declare function devLayersEnabled(override?: boolean | undefined): boolean;
|
|
34
|
-
//# sourceMappingURL=dev-layers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dev-layers.d.ts","sourceRoot":"","sources":["../../src/runtime/dev-layers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAaxE"}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* THE one owner of "are dev layers enabled" (`vgai.project.json`'s `dev: true`
|
|
3
|
-
* root flag). Every reader that has to answer that question calls
|
|
4
|
-
* {@link devLayersEnabled}; there is deliberately NO second source of truth —
|
|
5
|
-
* not a module-level cached boolean, not a `globalThis` flag, not a per-host
|
|
6
|
-
* copy of the `import.meta.env` read below. A dev GUI that ships to players
|
|
7
|
-
* because two places disagreed about what "dev" means is exactly the failure
|
|
8
|
-
* this single owner exists to make impossible.
|
|
9
|
-
*
|
|
10
|
-
* Ownership, stated in one place (the build rule):
|
|
11
|
-
* - OWNER: this function. It resolves the answer; nothing else derives it.
|
|
12
|
-
* - SHARERS: `mountManifestRoots` (the ship gate — a dev root only mounts
|
|
13
|
-
* when this returns true). Any future host that mounts manifest roots
|
|
14
|
-
* itself calls this too, with its own `override`.
|
|
15
|
-
* - TEARDOWN: none. This is a pure predicate over build config and one
|
|
16
|
-
* caller-supplied argument — it owns no resource, allocates nothing, and
|
|
17
|
-
* has no lifecycle to end.
|
|
18
|
-
*
|
|
19
|
-
* The three inputs, highest precedence first:
|
|
20
|
-
* 1. `override` — the explicit per-mount answer. A headless test, a capture
|
|
21
|
-
* harness, or a host that knows better passes `true`/`false` and gets
|
|
22
|
-
* exactly that. Passing `undefined` (or omitting it) means "decide for
|
|
23
|
-
* me" and falls through.
|
|
24
|
-
* 2. A dev build — `import.meta.env.DEV`. The ordinary local/editor case:
|
|
25
|
-
* dev layers are on.
|
|
26
|
-
* 3. A production build's EXPLICIT opt-in — `VITE_VGAI_DEV_LAYERS=true`.
|
|
27
|
-
* Shipping the dev GUI in a production bundle is a real, legitimate
|
|
28
|
-
* choice (an internal playtest build, a QA build), and it must be an
|
|
29
|
-
* opt-in someone had to type, never something a default drifts into.
|
|
30
|
-
*
|
|
31
|
-
* Anything else — a production build with no opt-in — is `false`.
|
|
32
|
-
*/
|
|
33
|
-
export function devLayersEnabled(override) {
|
|
34
|
-
if (override !== undefined)
|
|
35
|
-
return override;
|
|
36
|
-
// `import.meta` is cast whole, not just its `.env`: this module is reachable
|
|
37
|
-
// from programs whose tsconfig does not pull in `vite/client` (the session
|
|
38
|
-
// client's, for one, which reaches the three adapter transitively), and there
|
|
39
|
-
// `ImportMeta` has no declared `env` at all. The cast keeps the single owner
|
|
40
|
-
// of the dev answer importable from ANY program rather than forcing every
|
|
41
|
-
// downstream tsconfig to adopt Vite's ambient types.
|
|
42
|
-
const env = import.meta.env;
|
|
43
|
-
if (env?.DEV === true)
|
|
44
|
-
return true;
|
|
45
|
-
return env?.VITE_VGAI_DEV_LAYERS === 'true';
|
|
46
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* THE one owner of "are dev layers enabled" (`vgai.project.json`'s `dev: true`
|
|
3
|
-
* root flag). Every reader that has to answer that question calls
|
|
4
|
-
* {@link devLayersEnabled}; there is deliberately NO second source of truth —
|
|
5
|
-
* not a module-level cached boolean, not a `globalThis` flag, not a per-host
|
|
6
|
-
* copy of the `import.meta.env` read below. A dev GUI that ships to players
|
|
7
|
-
* because two places disagreed about what "dev" means is exactly the failure
|
|
8
|
-
* this single owner exists to make impossible.
|
|
9
|
-
*
|
|
10
|
-
* Ownership, stated in one place (the build rule):
|
|
11
|
-
* - OWNER: this function. It resolves the answer; nothing else derives it.
|
|
12
|
-
* - SHARERS: `mountManifestRoots` (the ship gate — a dev root only mounts
|
|
13
|
-
* when this returns true). Any future host that mounts manifest roots
|
|
14
|
-
* itself calls this too, with its own `override`.
|
|
15
|
-
* - TEARDOWN: none. This is a pure predicate over build config and one
|
|
16
|
-
* caller-supplied argument — it owns no resource, allocates nothing, and
|
|
17
|
-
* has no lifecycle to end.
|
|
18
|
-
*
|
|
19
|
-
* The three inputs, highest precedence first:
|
|
20
|
-
* 1. `override` — the explicit per-mount answer. A headless test, a capture
|
|
21
|
-
* harness, or a host that knows better passes `true`/`false` and gets
|
|
22
|
-
* exactly that. Passing `undefined` (or omitting it) means "decide for
|
|
23
|
-
* me" and falls through.
|
|
24
|
-
* 2. A dev build — `import.meta.env.DEV`. The ordinary local/editor case:
|
|
25
|
-
* dev layers are on.
|
|
26
|
-
* 3. A production build's EXPLICIT opt-in — `VITE_VGAI_DEV_LAYERS=true`.
|
|
27
|
-
* Shipping the dev GUI in a production bundle is a real, legitimate
|
|
28
|
-
* choice (an internal playtest build, a QA build), and it must be an
|
|
29
|
-
* opt-in someone had to type, never something a default drifts into.
|
|
30
|
-
*
|
|
31
|
-
* Anything else — a production build with no opt-in — is `false`.
|
|
32
|
-
*/
|
|
33
|
-
export function devLayersEnabled(override?: boolean | undefined): boolean {
|
|
34
|
-
if (override !== undefined) return override;
|
|
35
|
-
// `import.meta` is cast whole, not just its `.env`: this module is reachable
|
|
36
|
-
// from programs whose tsconfig does not pull in `vite/client` (the session
|
|
37
|
-
// client's, for one, which reaches the three adapter transitively), and there
|
|
38
|
-
// `ImportMeta` has no declared `env` at all. The cast keeps the single owner
|
|
39
|
-
// of the dev answer importable from ANY program rather than forcing every
|
|
40
|
-
// downstream tsconfig to adopt Vite's ambient types.
|
|
41
|
-
const env = (import.meta as unknown as { env?: unknown }).env as
|
|
42
|
-
| { DEV?: boolean | undefined; VITE_VGAI_DEV_LAYERS?: string | undefined }
|
|
43
|
-
| undefined;
|
|
44
|
-
if (env?.DEV === true) return true;
|
|
45
|
-
return env?.VITE_VGAI_DEV_LAYERS === 'true';
|
|
46
|
-
}
|