@vgai/engine 0.5.16 → 0.5.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/adapter-module.d.ts +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/root-adapter.d.ts +4 -0
- 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 +2 -23
- 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 +1 -20
- 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 +1 -1
- package/dist/world3d-react/index.js +1 -1
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +89 -14
- 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/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 +1 -6
- package/src/adapter/adapter-module.ts +1 -25
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +3 -8
- package/src/adapter/root-adapter.ts +4 -0
- 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 +2 -25
- 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 +1 -32
- package/src/world3d-react/engine-bridge.ts +28 -3
- package/src/world3d-react/index.ts +1 -1
- package/src/world3d-react/r3f-adapter.tsx +87 -18
- package/src/world3d-react/r3f-root-factory.tsx +12 -2
- package/src/world3d-react/rapier-physics-bridge.tsx +4 -4
- 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
|
@@ -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
|
-
}
|