@vgai/engine 0.5.1 → 0.5.3
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/README.md +18 -11
- package/package.json +10 -6
- package/schemas/engine-api.json +1 -68
- package/schemas/engine-api.md +1 -32
- package/schemas/engine-capabilities.json +30 -42
- package/schemas/{vgai-game.schema.json → vgai-project.schema.json} +52 -34
- package/src/adapter/adapter-surface.ts +5 -5
- package/src/adapter/authoring.ts +168 -226
- package/src/adapter/colyseus-networking-adapter.ts +44 -5
- package/src/adapter/first-party-systems.ts +156 -42
- package/src/adapter/host-context.ts +177 -46
- package/src/adapter/index.ts +47 -51
- package/src/adapter/ingest/game-contract.ts +2 -2
- package/src/adapter/ingest/scene-capture.ts +18 -19
- package/src/adapter/ingest/structural-ids.ts +127 -0
- package/src/adapter/ingest/upstream-pin.ts +9 -12
- package/src/adapter/loop-gate-report.ts +11 -11
- package/src/adapter/rapier-physics-adapter.ts +27 -9
- package/src/adapter/root-adapter.ts +217 -0
- package/src/adapter/{vgai-scene-game-adapter.ts → setup-three-root-adapter.ts} +173 -351
- package/src/adapter/system-adapter.ts +80 -63
- package/src/ai/navigation.ts +1 -1
- package/src/animation/animation-clock.ts +1 -1
- package/src/animation/camera-ownership.ts +1 -2
- package/src/animation/cubic-spline-interpolant.ts +132 -0
- package/src/animation/theatre-clock-binding.ts +2 -2
- package/src/animation/theatre-object-binding.ts +4 -4
- package/src/animation/xstate-animation-binding.ts +75 -5
- package/src/{scene/schema → asset-formats}/camera.ts +2 -5
- package/src/{scene/schema → asset-formats}/collider.ts +2 -5
- package/src/asset-formats/index.ts +54 -0
- package/src/{scene/schema → asset-formats}/instances.ts +10 -6
- package/src/{scene/schema → asset-formats}/light.ts +3 -6
- package/src/{scene/schema → asset-formats}/material.ts +4 -7
- package/src/{scene/schema → asset-formats}/mesh.ts +4 -7
- package/src/asset-formats/parse.ts +39 -0
- package/src/{scene/schema → asset-formats}/particles.ts +3 -6
- package/src/{scene/schema/environment.ts → asset-formats/render-env.ts} +23 -86
- package/src/{scene/schema → asset-formats}/tuples.ts +1 -1
- package/src/{scene/asset-loaders.ts → asset-loaders.ts} +8 -9
- package/src/asset-parse-error.ts +33 -0
- package/src/{scene/asset-registry.ts → asset-registry.ts} +1 -1
- package/src/assets.ts +1 -1
- package/src/audio/wav-encode.ts +9 -9
- package/src/canvas-react/engine-bridge.ts +59 -0
- package/src/canvas-react/index.ts +50 -0
- package/src/canvas-react/pixi-primitive.tsx +202 -0
- package/src/canvas-react/pixi-react-adapter.tsx +290 -0
- package/src/canvas-react/pixi-react-root-factory.tsx +88 -0
- package/src/canvas-react/world-context.ts +328 -0
- package/src/core/frame-pacing.ts +100 -0
- package/src/core/game-loop.ts +50 -28
- package/src/core/seeded-random.ts +7 -7
- package/src/core/sim-clock.ts +388 -0
- package/src/core/system-runner.ts +17 -63
- package/src/core/types.ts +34 -15
- package/src/data/data-asset.ts +3 -3
- package/src/data/data-check-core.ts +6 -7
- package/src/data/data-ref.ts +11 -11
- package/src/data/vite-plugin-data.ts +10 -10
- package/src/{scene/defaults.ts → defaults.ts} +18 -40
- package/src/dev/render-debug-adapter.ts +1 -1
- package/src/dev/webgl-frame-capture.ts +1 -1
- package/src/ecs/scene-index.ts +439 -0
- package/src/ecs/scene-query.ts +43 -0
- package/src/{scene → ecs}/user-data.ts +17 -36
- package/src/index.ts +7 -9
- package/src/input/input-manager.ts +29 -32
- package/src/input/input-types.ts +2 -2
- package/src/input/schema.ts +5 -5
- package/src/loader.ts +57 -0
- package/src/manifest/editor-port.ts +69 -0
- package/src/manifest/filename.ts +49 -0
- package/src/manifest/index.ts +8 -2
- package/src/manifest/load-file.ts +11 -0
- package/src/manifest/load.ts +65 -77
- package/src/manifest/locate.ts +55 -0
- package/src/manifest/schema.ts +400 -233
- package/src/{scene → physics}/collider-dimensions.ts +3 -3
- package/src/physics/physics-registry.ts +1 -1
- package/src/{world2d/authoring-2d.ts → pixi/authoring.ts} +24 -11
- package/src/pixi/index.ts +43 -0
- package/src/{world2d/ingest-iframe-2d.ts → pixi/ingest-iframe.ts} +9 -9
- package/src/{world2d/ingest2d.ts → pixi/ingest.ts} +28 -28
- package/src/{world2d/physics2d-registry.ts → pixi/physics-registry.ts} +1 -1
- package/src/{world2d/scene-capture-2d.ts → pixi/scene-capture.ts} +3 -3
- package/src/{world2d/system-adapters-2d.ts → pixi/system-adapters.ts} +2 -2
- package/src/react/unmanaged-root-detector.ts +26 -1
- package/src/react/use-data.ts +5 -5
- package/src/react/use-selection.tsx +15 -42
- package/src/react/{game-state.tsx → world-state.tsx} +44 -46
- package/src/render/auto-batcher.ts +1 -2
- package/src/{scene → render}/instance-mesh.ts +1 -1
- package/src/{scene → render}/light-camera-factory.ts +14 -13
- package/src/render/lod.ts +17 -0
- package/src/{scene → render}/material-factory.ts +8 -7
- package/src/{scene → render}/particles-factory.ts +62 -12
- package/src/render/render-batch-system.ts +14 -41
- package/src/render/render-features.ts +1 -1
- package/src/render/render-settings.ts +1 -2
- package/src/render/spark-renderer-lifecycle.ts +1 -1
- package/src/runtime/create-runtime.ts +352 -463
- package/src/runtime/debug-bridge.ts +148 -93
- package/src/runtime/debug-registry.ts +79 -54
- package/src/runtime/dev-layers.ts +40 -0
- package/src/runtime/frame-selector-cache.ts +4 -4
- package/src/runtime/game.ts +618 -369
- package/src/runtime/gameplay-rng-trap.ts +6 -7
- package/src/runtime/input-router.ts +11 -11
- package/src/runtime/mount-game.ts +54 -55
- package/src/runtime/mount-manifest.ts +154 -150
- package/src/runtime/presentation.ts +141 -0
- package/src/runtime/render-audio-control.ts +64 -53
- package/src/runtime/render-control.ts +45 -51
- package/src/runtime/render-seed.ts +3 -4
- package/src/runtime/state-bridge.ts +17 -18
- package/src/runtime/types.ts +94 -61
- package/src/setup/setup-renderer.ts +11 -6
- package/src/world3d-react/engine-bridge.ts +46 -33
- package/src/world3d-react/index.ts +31 -26
- package/src/world3d-react/r3f-adapter.tsx +211 -96
- package/src/world3d-react/r3f-root-factory.tsx +91 -0
- package/src/world3d-react/renderer-config.ts +137 -0
- package/src/world3d-react/world-context.ts +141 -111
- package/schemas/entity2d.schema.json +0 -468
- package/schemas/prefab.schema.json +0 -9992
- package/schemas/scn2d.schema.json +0 -494
- package/schemas/vscn.schema.json +0 -10851
- package/src/adapter/game-adapter.ts +0 -164
- package/src/adapter/ingest/overlay-applier.ts +0 -207
- package/src/adapter/ingest/overlay-apply.ts +0 -168
- package/src/adapter/ingest/overlay-file.ts +0 -126
- package/src/adapter/ingest/overlay-report.ts +0 -176
- package/src/animation/gsap-registration.ts +0 -184
- package/src/audio/audio-introspection.ts +0 -290
- package/src/audio/index.ts +0 -39
- package/src/audio/tone-clock-binding.ts +0 -98
- package/src/audio/tone-context.ts +0 -175
- package/src/audio/tone-offline-render.ts +0 -167
- package/src/ecs/component-manager.ts +0 -814
- package/src/ecs/game-component.ts +0 -260
- package/src/ecs/hmr-swap-report.ts +0 -65
- package/src/physics/trigger-dispatch.ts +0 -97
- package/src/react/root-adapter.tsx +0 -49
- package/src/scene/asset-paths.ts +0 -121
- package/src/scene/asset-ref-check.ts +0 -248
- package/src/scene/component-registry.ts +0 -51
- package/src/scene/parse.ts +0 -204
- package/src/scene/scene-apply.ts +0 -407
- package/src/scene/scene-diff-schema.ts +0 -115
- package/src/scene/scene-diff-types.ts +0 -29
- package/src/scene/scene-loader.ts +0 -1526
- package/src/scene/scene-query.ts +0 -63
- package/src/scene/scene-types.ts +0 -33
- package/src/scene/scene-version.ts +0 -40
- package/src/scene/schema/animation.ts +0 -46
- package/src/scene/schema/audio.ts +0 -25
- package/src/scene/schema/entity-ref.ts +0 -78
- package/src/scene/schema/entity.ts +0 -189
- package/src/scene/schema/index.ts +0 -51
- package/src/scene/schema/joint.ts +0 -26
- package/src/scene/schema/physics.ts +0 -49
- package/src/scene/schema/scene-file.ts +0 -292
- package/src/scene/schema/shadow.ts +0 -24
- package/src/scene/schema/spline.ts +0 -21
- package/src/world2d/asset-paths2d.ts +0 -44
- package/src/world2d/capture-to-scene2d.ts +0 -52
- package/src/world2d/collision-2d.ts +0 -99
- package/src/world2d/entity2d-asset.ts +0 -22
- package/src/world2d/index.ts +0 -91
- package/src/world2d/physics2d-transform.ts +0 -173
- package/src/world2d/physics2d-units.ts +0 -10
- package/src/world2d/pixi-game-adapter.ts +0 -439
- package/src/world2d/pixi-surface.ts +0 -78
- package/src/world2d/scene2d-identity.ts +0 -49
- package/src/world2d/scene2d-loader.ts +0 -433
- package/src/world2d/schema/entity2d.ts +0 -163
- package/src/world2d/schema/physics2d.ts +0 -64
- package/src/world2d/schema/sprite.ts +0 -99
- package/src/world2d/schema/tilemap.ts +0 -39
- package/src/world2d/schema/tuples2d.ts +0 -25
- package/src/world2d/transform-writer-2d.ts +0 -42
- package/src/world2d/types.ts +0 -74
- package/src/world3d-react/behavior.tsx +0 -146
- /package/src/{scene → render}/mesh-shadow.ts +0 -0
|
@@ -15,24 +15,24 @@
|
|
|
15
15
|
*
|
|
16
16
|
* ## Key reference
|
|
17
17
|
*
|
|
18
|
-
* Serialization / identity (load-bearing — editor identity
|
|
19
|
-
* - `entity` — the full {@link SceneEntity} descriptor backing this
|
|
20
|
-
* Object3D. Source of truth the editor reads/writes and
|
|
21
|
-
* the serializer walks to rebuild `.vscn.json`.
|
|
18
|
+
* Serialization / identity (load-bearing — editor identity):
|
|
22
19
|
* - `entityId` — the entity's stable string id. Presence also marks an
|
|
23
20
|
* Object3D as a "real" entity (vs editor helper/env object).
|
|
24
21
|
*
|
|
25
|
-
* Runtime scene-query
|
|
26
|
-
* - `tags` — string[]
|
|
27
|
-
* - `
|
|
28
|
-
* `
|
|
22
|
+
* Runtime scene-query metadata (read by `scene-query.ts` and `scene-index.ts`):
|
|
23
|
+
* - `tags` — string[] tags for `queryByTag` and the live index.
|
|
24
|
+
* - `attributes` — per-object `Record<string, string|number|boolean>`
|
|
25
|
+
* of game-defined attributes (P2 `observe`). Written
|
|
26
|
+
* through `SceneIndex.setAttribute`, which emits an
|
|
27
|
+
* `attributechanged` signal; JSON-simple values only,
|
|
28
|
+
* so an attribute survives serialization unchanged.
|
|
29
29
|
*
|
|
30
|
-
* Runtime gameplay metadata
|
|
30
|
+
* Runtime gameplay metadata:
|
|
31
31
|
* - `forward` — `[x,y,z]` model-space visual forward exported as
|
|
32
32
|
* ordinary glTF extras for asset-facing validation.
|
|
33
33
|
* - `navRole` — `'walkable' | 'obstacle'` navmesh role; collected at runtime.
|
|
34
34
|
* - `pivot` — `[x,y,z]` local-space pivot for rotate/scale-around-pivot.
|
|
35
|
-
* - `splineCurve` — resolved THREE curve
|
|
35
|
+
* - `splineCurve` — resolved THREE curve for a spline-following object.
|
|
36
36
|
* - `_camera` — THREE.Camera owned by a camera entity (collected on load).
|
|
37
37
|
* - `_particleSystem` — three.quarks ParticleSystem (scene-sync registration + census).
|
|
38
38
|
* - `gaussianSplat` — native Spark splat metadata used for renderer discovery,
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
* Animation (load-bearing — ED5 disposal contract):
|
|
42
42
|
* - `_animMixer` — THREE.AnimationMixer driving this subtree's clips.
|
|
43
43
|
* - `_animClips` — Map<string, AnimationClip> discovered on the GLTF (E5 —
|
|
44
|
-
* lets
|
|
44
|
+
* lets game code build its own XState-driven
|
|
45
45
|
* binding via `bindXStateAnimation` over the SAME
|
|
46
46
|
* mixer; see xstate-animation-binding.ts).
|
|
47
47
|
* - `_availableClips` — string[] of clip names discovered on the GLTF
|
|
@@ -58,9 +58,8 @@
|
|
|
58
58
|
* - `__shadeUnlit` — generated unlit material(s) for the unlit view mode.
|
|
59
59
|
*
|
|
60
60
|
* Unmodified-game ingestion (editor-only, see `editor/src/ingest/`):
|
|
61
|
-
* - `__ingest` — `true` on every Object3D minted by ingestion
|
|
62
|
-
*
|
|
63
|
-
* mutation instead of descriptor rebuild.
|
|
61
|
+
* - `__ingest` — `true` on every Object3D minted by ingestion; routes
|
|
62
|
+
* edits to live mutation.
|
|
64
63
|
* - `__ingestNextId` — monotonic id counter stashed on the captured Scene
|
|
65
64
|
* root so re-reflects keep minting unique ids.
|
|
66
65
|
*
|
|
@@ -69,10 +68,6 @@
|
|
|
69
68
|
* (particle BatchedRenderer, debug-draw + its subtree).
|
|
70
69
|
* The editor's play-mode hierarchy skips these so they
|
|
71
70
|
* don't show up as selectable "entities".
|
|
72
|
-
* - `liveObject` — `true` on a runtime/procedural Object3D that the editor
|
|
73
|
-
* surfaced in the play-mode hierarchy with a *synthetic*
|
|
74
|
-
* descriptor (it has no `.vscn` record). Marks it as
|
|
75
|
-
* ephemeral: selectable/movable for testing, never saved.
|
|
76
71
|
* - `authoringRoot` — explicit opt-in for a runtime descendant to become
|
|
77
72
|
* its own authoring object instead of a part of the
|
|
78
73
|
* nearest ancestor owner.
|
|
@@ -81,8 +76,6 @@
|
|
|
81
76
|
* implementation parts without adding wrappers.
|
|
82
77
|
* - `authoringLabel` — authored literal name (or component tag fallback)
|
|
83
78
|
* for that source-backed component instance.
|
|
84
|
-
* - `behaviorType` — registry key on the compatibility R3F Behavior
|
|
85
|
-
* attachment group; authoring projects it as a component.
|
|
86
79
|
* - `editorHelper` — `true` for editor-only helper objects (gizmos, wireframes).
|
|
87
80
|
* - `editorHelperType` — which kind of helper (lights/particles/pivot/navmesh/...).
|
|
88
81
|
* - `editorIcon` — `true` for editor billboard icon sprites.
|
|
@@ -95,9 +88,7 @@
|
|
|
95
88
|
|
|
96
89
|
import type * as THREE from 'three';
|
|
97
90
|
import type { ParticleSystem } from 'three.quarks';
|
|
98
|
-
import type { AnyActor } from 'xstate';
|
|
99
91
|
import type { XStateAnimationBinding } from '../animation/xstate-animation-binding';
|
|
100
|
-
import type { SceneEntity } from './scene-types';
|
|
101
92
|
|
|
102
93
|
/** The kinds of editor helper objects tagged via `editorHelperType`. */
|
|
103
94
|
export type EditorHelperType =
|
|
@@ -121,22 +112,17 @@ export type EditorHelperType =
|
|
|
121
112
|
| 'particle-shape'
|
|
122
113
|
| 'pivot'
|
|
123
114
|
| 'navmesh'
|
|
124
|
-
| 'skeletons'
|
|
125
|
-
// Per-component viewport gizmo contributions (W3a extension contract,
|
|
126
|
-
// critique O11): objects a project GameComponent's `static editorGizmo`
|
|
127
|
-
// hook draws for entities carrying that component.
|
|
128
|
-
| 'components';
|
|
115
|
+
| 'skeletons';
|
|
129
116
|
|
|
130
117
|
/**
|
|
131
118
|
* Maps each canonical accessor name to its value type. This is the single
|
|
132
119
|
* source of truth for what every known `userData` key holds.
|
|
133
120
|
*/
|
|
134
121
|
export interface UserDataSchema {
|
|
135
|
-
entity: SceneEntity;
|
|
136
122
|
entityId: string;
|
|
137
123
|
forward: [number, number, number];
|
|
138
124
|
tags: string[];
|
|
139
|
-
|
|
125
|
+
attributes: Record<string, string | number | boolean>;
|
|
140
126
|
navRole: 'walkable' | 'obstacle';
|
|
141
127
|
pivot: [number, number, number];
|
|
142
128
|
splineCurve: THREE.Curve<THREE.Vector3>;
|
|
@@ -147,18 +133,16 @@ export interface UserDataSchema {
|
|
|
147
133
|
_animMixer: THREE.AnimationMixer;
|
|
148
134
|
_animClips: Map<string, THREE.AnimationClip>;
|
|
149
135
|
_availableClips: string[];
|
|
150
|
-
_xstateAnimation: XStateAnimationBinding
|
|
136
|
+
_xstateAnimation: XStateAnimationBinding;
|
|
151
137
|
__sharedGeometry: boolean;
|
|
152
138
|
__shadeOrig: THREE.Material | THREE.Material[];
|
|
153
139
|
__shadeUnlit: THREE.Material[];
|
|
154
140
|
__ingest: boolean;
|
|
155
141
|
__ingestNextId: number;
|
|
156
142
|
engineInternal: boolean;
|
|
157
|
-
liveObject: boolean;
|
|
158
143
|
authoringRoot: boolean;
|
|
159
144
|
authoringInstance: string;
|
|
160
145
|
authoringLabel: string;
|
|
161
|
-
behaviorType: string;
|
|
162
146
|
editorHelper: boolean;
|
|
163
147
|
editorHelperType: EditorHelperType;
|
|
164
148
|
editorIcon: boolean;
|
|
@@ -181,11 +165,10 @@ export type UserDataKey = keyof UserDataSchema;
|
|
|
181
165
|
* literal strings appear in exactly this one place.
|
|
182
166
|
*/
|
|
183
167
|
export const UserDataKeys = {
|
|
184
|
-
entity: 'entity',
|
|
185
168
|
entityId: 'entityId',
|
|
186
169
|
forward: 'forward',
|
|
187
170
|
tags: 'tags',
|
|
188
|
-
|
|
171
|
+
attributes: 'attributes',
|
|
189
172
|
navRole: 'navRole',
|
|
190
173
|
pivot: 'pivot',
|
|
191
174
|
splineCurve: 'splineCurve',
|
|
@@ -203,11 +186,9 @@ export const UserDataKeys = {
|
|
|
203
186
|
__ingest: '__ingest',
|
|
204
187
|
__ingestNextId: '__ingestNextId',
|
|
205
188
|
engineInternal: 'engineInternal',
|
|
206
|
-
liveObject: 'liveObject',
|
|
207
189
|
authoringRoot: 'authoringRoot',
|
|
208
190
|
authoringInstance: 'authoringInstance',
|
|
209
191
|
authoringLabel: 'authoringLabel',
|
|
210
|
-
behaviorType: 'behaviorType',
|
|
211
192
|
editorHelper: 'editorHelper',
|
|
212
193
|
editorHelperType: 'editorHelperType',
|
|
213
194
|
editorIcon: 'editorIcon',
|
package/src/index.ts
CHANGED
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
* Groups (import order below is alphabetical-by-specifier, per the repo's
|
|
19
19
|
* biome `organizeImports` rule, not by group — see this list instead):
|
|
20
20
|
* - game setup contract: `GameContext`/`GameCleanup`/`GameSetupFn`, `fromSetup`
|
|
21
|
-
* -
|
|
22
|
-
* - manifest-driven mount: `
|
|
23
|
-
* -
|
|
24
|
-
* - game manifest (vgai.
|
|
21
|
+
* - surface vocabulary: `AdapterSurface`
|
|
22
|
+
* - manifest-driven mount: `mountManifestRoots` + its option/result types
|
|
23
|
+
* - asset-format entry points: `parseMaterialFile` (.mat.json), `parseInstancesFile`
|
|
24
|
+
* - game manifest (vgai.project.json) entry point: `loadGameManifest`
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
export type { AdapterSurface } from './adapter/adapter-surface';
|
|
28
|
-
export { fromSetup } from './adapter/
|
|
29
|
-
export {
|
|
28
|
+
export { fromSetup } from './adapter/setup-three-root-adapter';
|
|
29
|
+
export { AssetParseError, parseInstancesFile, parseMaterialFile } from './asset-formats/parse';
|
|
30
30
|
export type {
|
|
31
31
|
AdapterRoot,
|
|
32
32
|
GameManifest,
|
|
@@ -42,7 +42,5 @@ export type {
|
|
|
42
42
|
MountedManifestSession,
|
|
43
43
|
MountManifestOptions,
|
|
44
44
|
} from './runtime/mount-manifest';
|
|
45
|
-
export {
|
|
45
|
+
export { mountManifestRoots } from './runtime/mount-manifest';
|
|
46
46
|
export type { GameCleanup, GameContext, GameSetupFn } from './runtime/types';
|
|
47
|
-
export { parsePrefabFile, parseSceneFile, SceneParseError } from './scene/parse';
|
|
48
|
-
export type { PrefabFile, SceneFile } from './scene/schema';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { AssetParseError } from '../asset-parse-error';
|
|
1
2
|
import { resolveUrl } from '../loader';
|
|
2
|
-
import { SceneParseError } from '../scene/parse';
|
|
3
3
|
import type {
|
|
4
4
|
ActionValueOf,
|
|
5
5
|
ActionValueSource,
|
|
@@ -39,9 +39,8 @@ function isTextEntryFocused(): boolean {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Thrown by `InputManager.setVirtualAction`/`tapVirtualAction` (Task 1.4
|
|
43
|
-
*
|
|
44
|
-
* action name that was never `registerAction`/`loadMap`-ed. Carries a
|
|
42
|
+
* Thrown by `InputManager.setVirtualAction`/`tapVirtualAction` (Task 1.4)
|
|
43
|
+
* for an action name that was never `registerAction`/`loadMap`-ed. Carries a
|
|
45
44
|
* machine-readable `code` and `data.registered` (every declared action name)
|
|
46
45
|
* rather than requiring a caller to parse the message.
|
|
47
46
|
*/
|
|
@@ -60,12 +59,12 @@ export class InputActionError extends Error {
|
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
/**
|
|
63
|
-
* Thrown by `InputManager.scheduleActionAtTick` (D15/T-D15.5
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
62
|
+
* Thrown by `InputManager.scheduleActionAtTick` (D15/T-D15.5) when the
|
|
63
|
+
* named `tick` has already elapsed — scheduling only ever applies to the
|
|
64
|
+
* CURRENT or a future tick, never one already serviced by `poll()`.
|
|
65
|
+
* Carries `data.currentTick` — the NEXT tick `poll()` will service (not
|
|
66
|
+
* the tick that just elapsed) — so a caller can retry by scheduling for
|
|
67
|
+
* `data.currentTick` or later.
|
|
69
68
|
*/
|
|
70
69
|
export class InputTickError extends Error {
|
|
71
70
|
readonly code = 'TICK_ALREADY_PASSED' as const;
|
|
@@ -360,7 +359,7 @@ export class InputManager {
|
|
|
360
359
|
private static isStickActive(v: Vector2): boolean {
|
|
361
360
|
return Math.hypot(v.x, v.y) > 0.3;
|
|
362
361
|
}
|
|
363
|
-
// Task 1.4
|
|
362
|
+
// Task 1.4 — action-level virtual input
|
|
364
363
|
// for a synthetic player, promoted from hollowstone's `VirtualInput`. Held
|
|
365
364
|
// digitals OR into isPressed alongside binding contributions (like
|
|
366
365
|
// touchButtonsDown above); tap queues, then is promoted to "active" for
|
|
@@ -382,14 +381,13 @@ export class InputManager {
|
|
|
382
381
|
private virtualDigitalJustReleased = new Set<string>();
|
|
383
382
|
private virtualScalarValues = new Map<string, number>();
|
|
384
383
|
private virtualVector2Values = new Map<string, Vector2>();
|
|
385
|
-
// D15/T-D15.5
|
|
386
|
-
// scheduling + post-gate recording.
|
|
384
|
+
// D15/T-D15.5 — tick-indexed input scheduling + post-gate recording.
|
|
387
385
|
//
|
|
388
386
|
// `currentTick` is a FALLBACK, self-incrementing counter, used only when a
|
|
389
387
|
// caller never tells `poll()` which tick it's servicing (every existing
|
|
390
388
|
// bare/headless caller — tests, a mount with no `Game` shell behind it —
|
|
391
389
|
// keeps this exact pre-existing behavior, zero regression). The REAL
|
|
392
|
-
// per-world wiring (`
|
|
390
|
+
// per-world wiring (`setup-three-root-adapter.ts`) instead passes the
|
|
393
391
|
// shared `Game`-level tick counter into every `poll(tick)` call — this is
|
|
394
392
|
// deliberate: an InputManager-local counter drifts from the actual game
|
|
395
393
|
// tick for a paused/frozen world (its `poll()` isn't called every game
|
|
@@ -422,7 +420,7 @@ export class InputManager {
|
|
|
422
420
|
* action}`) emitted whenever a scheduled entry is discarded because its
|
|
423
421
|
* target tick's input phase never ran. Wired by whoever constructs this
|
|
424
422
|
* `InputManager` with access to a `DebugRegistry`
|
|
425
|
-
* (`
|
|
423
|
+
* (`setup-three-root-adapter.ts`, the same seed spot as
|
|
426
424
|
* `setVirtualInputTarget`/`setInputActionsSource`) — `null` (the
|
|
427
425
|
* default) for a bare/headless `InputManager`, in which case a drop
|
|
428
426
|
* stays silent (matching pre-D15/T-D15.5 behavior). */
|
|
@@ -634,7 +632,7 @@ export class InputManager {
|
|
|
634
632
|
/**
|
|
635
633
|
* Load action map from a .inputmap.json file. Validated via
|
|
636
634
|
* `InputMapFileSchema` (T4.6) — a malformed input map throws a
|
|
637
|
-
* `
|
|
635
|
+
* `AssetParseError` naming the file, not a deep TypeError once the bad
|
|
638
636
|
* data reaches `isPressed`/`isJustPressed`/`isJustReleased`.
|
|
639
637
|
*/
|
|
640
638
|
async loadMap(url: string) {
|
|
@@ -646,14 +644,14 @@ export class InputManager {
|
|
|
646
644
|
}
|
|
647
645
|
const json = await res.json();
|
|
648
646
|
const result = InputMapFileSchema.safeParse(json);
|
|
649
|
-
if (!result.success) throw new
|
|
647
|
+
if (!result.success) throw new AssetParseError(result.error.issues, url);
|
|
650
648
|
data = result.data;
|
|
651
649
|
} catch (err) {
|
|
652
650
|
const message = `InputManager.loadMap: failed to load input map "${url}": ${
|
|
653
651
|
err instanceof Error ? err.message : String(err)
|
|
654
652
|
}`;
|
|
655
653
|
console.error(message);
|
|
656
|
-
throw err instanceof
|
|
654
|
+
throw err instanceof AssetParseError ? err : new Error(message);
|
|
657
655
|
}
|
|
658
656
|
this.applyParsedMap(data);
|
|
659
657
|
}
|
|
@@ -661,7 +659,7 @@ export class InputManager {
|
|
|
661
659
|
/**
|
|
662
660
|
* F3 — `loadMap`'s synchronous, no-`fetch` sibling: apply an already-in-
|
|
663
661
|
* memory (or freshly-deserialized) input-map DOCUMENT, validated through
|
|
664
|
-
* the identical `InputMapFileSchema` (throws `
|
|
662
|
+
* the identical `InputMapFileSchema` (throws `AssetParseError` naming no
|
|
665
663
|
* file, matching `loadMap`'s error shape minus the URL). This is the other
|
|
666
664
|
* half of the F3 persist round-trip: `toInputMapFile()` serializes,
|
|
667
665
|
* `loadMapObject` re-applies — no network/file I/O required, so it's also
|
|
@@ -669,7 +667,7 @@ export class InputManager {
|
|
|
669
667
|
*/
|
|
670
668
|
loadMapObject(data: unknown): InputMapFile {
|
|
671
669
|
const result = InputMapFileSchema.safeParse(data);
|
|
672
|
-
if (!result.success) throw new
|
|
670
|
+
if (!result.success) throw new AssetParseError(result.error.issues);
|
|
673
671
|
this.applyParsedMap(result.data);
|
|
674
672
|
return result.data;
|
|
675
673
|
}
|
|
@@ -723,7 +721,8 @@ export class InputManager {
|
|
|
723
721
|
}
|
|
724
722
|
}
|
|
725
723
|
|
|
726
|
-
/** The names of all registered actions (
|
|
724
|
+
/** The names of all registered actions (r3f `world-context` enumerates these
|
|
725
|
+
* for its input-binding projection). */
|
|
727
726
|
actionNames(): string[] {
|
|
728
727
|
return [...this.actions.keys()];
|
|
729
728
|
}
|
|
@@ -732,7 +731,7 @@ export class InputManager {
|
|
|
732
731
|
* Poll gamepads and virtual look stick (call at start of frame).
|
|
733
732
|
*
|
|
734
733
|
* `tick` (D15/T-D15.3/.5) — the GAME tick this call services. Pass the
|
|
735
|
-
* live, shared `Game` tick counter here (`
|
|
734
|
+
* live, shared `Game` tick counter here (`setup-three-root-adapter.ts` does,
|
|
736
735
|
* via `DebugRegistry.getGameTick()`) so `scheduleActionAtTick`'s numbering
|
|
737
736
|
* never drifts from the actual game tick — the bug an earlier revision of
|
|
738
737
|
* this feature had: an InputManager-LOCAL counter, incremented once per
|
|
@@ -2007,8 +2006,7 @@ export class InputManager {
|
|
|
2007
2006
|
}
|
|
2008
2007
|
|
|
2009
2008
|
// ==========================================================================
|
|
2010
|
-
// Task 1.4
|
|
2011
|
-
// — action-level virtual input for a synthetic player. Promoted from
|
|
2009
|
+
// Task 1.4 — action-level virtual input for a synthetic player. Promoted from
|
|
2012
2010
|
// hollowstone's proven `VirtualInput` (`bot/virtual-input.ts`): a bot drives
|
|
2013
2011
|
// NAMED ACTIONS (not raw devices), and its contributions join action
|
|
2014
2012
|
// resolution at exactly the same points binding contributions do (see
|
|
@@ -2183,13 +2181,12 @@ export class InputManager {
|
|
|
2183
2181
|
}
|
|
2184
2182
|
|
|
2185
2183
|
/**
|
|
2186
|
-
* D15/T-D15.5
|
|
2187
|
-
*
|
|
2188
|
-
*
|
|
2189
|
-
*
|
|
2190
|
-
*
|
|
2191
|
-
*
|
|
2192
|
-
* `applyScheduledActionsForTick`).
|
|
2184
|
+
* D15/T-D15.5 — schedule a virtual actuation for exactly `tick`: applied
|
|
2185
|
+
* at the START of that tick's `poll()` (composing with `runTicks` — a
|
|
2186
|
+
* schedule for tick 500 fires exactly once the sim has been driven through
|
|
2187
|
+
* tick 500, regardless of burst size). A digital `true` produces a genuine
|
|
2188
|
+
* `isJustPressed` edge exactly at the target tick (not merely a held value
|
|
2189
|
+
* — see `applyScheduledActionsForTick`).
|
|
2193
2190
|
*
|
|
2194
2191
|
* Throws `InputActionError` for an unregistered `action` and the same
|
|
2195
2192
|
* valueType-mismatch error every typed getter throws (`assertValueType`) —
|
|
@@ -2222,7 +2219,7 @@ export class InputManager {
|
|
|
2222
2219
|
* `'input.schedule.dropped'` (`{tick, action}`) whenever it discards a
|
|
2223
2220
|
* scheduled entry for a tick whose input phase never ran. Called once by
|
|
2224
2221
|
* whoever constructs this `InputManager` with a `DebugRegistry` behind it
|
|
2225
|
-
* (`
|
|
2222
|
+
* (`setup-three-root-adapter.ts`), the same seed spot as
|
|
2226
2223
|
* `setVirtualInputTarget`/`setInputActionsSource` — never called directly
|
|
2227
2224
|
* by gameplay code. */
|
|
2228
2225
|
setDebugEmit(fn: ((event: string, detail?: unknown) => void) | null): void {
|
package/src/input/input-types.ts
CHANGED
|
@@ -90,8 +90,8 @@ export type ActionValueOf<T extends ActionValueType> = T extends 'digital'
|
|
|
90
90
|
* value. Returned by the `*Source` sibling of each typed getter (e.g. `getScalarSource`).
|
|
91
91
|
*/
|
|
92
92
|
export interface ActionValueSource {
|
|
93
|
-
/** The binding kind that produced the current value. `'virtual'`
|
|
94
|
-
*
|
|
93
|
+
/** The binding kind that produced the current value. `'virtual'`
|
|
94
|
+
* (Task 1.4) is not a real `InputBinding` — it marks a
|
|
95
95
|
* value written by `InputManager.setVirtualAction` (the synthetic-player
|
|
96
96
|
* seat), not a device binding. */
|
|
97
97
|
bindingType: InputBinding['type'] | 'virtual';
|
package/src/input/schema.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// T4.6 — the `.inputmap.json` schema.
|
|
2
2
|
//
|
|
3
|
-
// Was TS-types-only (`input-types.ts`) until T4.6 authored this Zod schema
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// Was TS-types-only (`input-types.ts`) until T4.6 authored this Zod schema.
|
|
4
|
+
// `InputManager.loadMap` (input-manager.ts) is this schema's runtime reader: it
|
|
5
|
+
// parses via this schema, then copies each action's `bindings` array (and its
|
|
6
|
+
// declared `valueType`, F1) straight into
|
|
7
|
+
// `this.actions`/`this.actionValueTypes`.
|
|
8
8
|
//
|
|
9
9
|
// Every field has a `.describe()` (repo policy — powers
|
|
10
10
|
// `scripts/generate-schema.ts` and the T4.1/T4.6 schema-walk coverage test).
|
package/src/loader.ts
CHANGED
|
@@ -14,6 +14,7 @@ import * as THREE from 'three';
|
|
|
14
14
|
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
|
|
15
15
|
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
16
16
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
17
|
+
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
|
|
17
18
|
|
|
18
19
|
let _prefix = '/';
|
|
19
20
|
|
|
@@ -49,9 +50,65 @@ export const textureLoader = new THREE.TextureLoader(loadingManager);
|
|
|
49
50
|
export const dracoLoader = new DRACOLoader(loadingManager);
|
|
50
51
|
dracoLoader.setDecoderPath('/jsm/libs/draco/gltf/');
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Shared KTX2Loader for decoding KHR_texture_basisu (KTX2/Basis) textures.
|
|
55
|
+
*
|
|
56
|
+
* Vendored and served EXACTLY like the DRACO decoder above — the Basis
|
|
57
|
+
* transcoder js/wasm live in repo-root `public/jsm/libs/basis/` (the editor)
|
|
58
|
+
* and `packages/editor/template/public/jsm/libs/basis/` (a scaffolded game's
|
|
59
|
+
* own build), pinned in `vendor/upstream-assets.lock.json`, and KTX2Loader
|
|
60
|
+
* appends `basis_transcoder.js` / `.wasm` to this absolute path.
|
|
61
|
+
*
|
|
62
|
+
* Without this wiring, the Asset Budget's "Compress textures · KTX2" output
|
|
63
|
+
* is a GLB nothing in the engine can load — which is why the op and this
|
|
64
|
+
* loader landed together.
|
|
65
|
+
*/
|
|
66
|
+
export const ktx2Loader = new KTX2Loader(loadingManager);
|
|
67
|
+
ktx2Loader.setTranscoderPath('/jsm/libs/basis/');
|
|
68
|
+
|
|
69
|
+
let ktx2SupportDetected = false;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Teach the shared KTX2Loader which compressed formats THIS GPU accepts.
|
|
73
|
+
*
|
|
74
|
+
* KTX2Loader refuses to transcode until it has seen a renderer, and the
|
|
75
|
+
* answer is a property of the page's GPU rather than of any one renderer —
|
|
76
|
+
* so the FIRST renderer to exist supplies it and every later one is a no-op.
|
|
77
|
+
* Called by the engine's own renderer setup (`setup/setup-renderer.ts`) and
|
|
78
|
+
* by the editor's viewport, which between them cover every surface that
|
|
79
|
+
* parses a GLB.
|
|
80
|
+
*
|
|
81
|
+
* This sits on the MOUNT path, so it must never throw for a renderer it
|
|
82
|
+
* cannot question. `detectSupport` reads `renderer.extensions.has/get` (or
|
|
83
|
+
* `renderer.hasFeature` on a WebGPU renderer) — surface a headless stand-in
|
|
84
|
+
* (the test harnesses' fake renderers, any non-WebGL host) does not have.
|
|
85
|
+
* Such a renderer is skipped WITHOUT latching, so the first renderer that
|
|
86
|
+
* can actually answer still configures the loader for the whole page.
|
|
87
|
+
*/
|
|
88
|
+
export function detectKtx2Support(renderer: THREE.WebGLRenderer): void {
|
|
89
|
+
if (ktx2SupportDetected) return;
|
|
90
|
+
const probe = renderer as unknown as
|
|
91
|
+
| {
|
|
92
|
+
isWebGPURenderer?: boolean;
|
|
93
|
+
hasFeature?: unknown;
|
|
94
|
+
extensions?: { has?: unknown; get?: unknown };
|
|
95
|
+
}
|
|
96
|
+
| null
|
|
97
|
+
| undefined;
|
|
98
|
+
const canAnswer =
|
|
99
|
+
probe != null &&
|
|
100
|
+
(probe.isWebGPURenderer === true
|
|
101
|
+
? typeof probe.hasFeature === 'function'
|
|
102
|
+
: typeof probe.extensions?.has === 'function' && typeof probe.extensions?.get === 'function');
|
|
103
|
+
if (!canAnswer) return;
|
|
104
|
+
ktx2Loader.detectSupport(renderer);
|
|
105
|
+
ktx2SupportDetected = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
52
108
|
/** Pre-configured GLTFLoader using the shared manager, with DRACO decoding wired in. */
|
|
53
109
|
export const gltfLoader = new GLTFLoader(loadingManager);
|
|
54
110
|
gltfLoader.setDRACOLoader(dracoLoader);
|
|
111
|
+
gltfLoader.setKTX2Loader(ktx2Loader);
|
|
55
112
|
// EXT_meshopt_compression decoding (three's own bundled decoder — inline
|
|
56
113
|
// wasm, no fetch). Without this, a meshopt-compressed GLB — including the
|
|
57
114
|
// output of the editor's Asset Budget "Compress · meshopt" action (W4a M3) —
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor's built-in port fallback, and the paired HMR-socket derivation.
|
|
3
|
+
*
|
|
4
|
+
* ONE owner for the number, because it is spelled in a launcher (`vgai edit`),
|
|
5
|
+
* three servers (dev / prod / packaged), and a client default — a duplicated
|
|
6
|
+
* literal across those drifts silently.
|
|
7
|
+
*
|
|
8
|
+
* **Why not Vite's 5173.** It used to be exactly that, and 5173 is the default
|
|
9
|
+
* of every Vite app on the machine. On a box running any other Vite dev server
|
|
10
|
+
* the engine-repo editor and that app fight over one port, and — worse —
|
|
11
|
+
* `http://…:5173` can resolve to a FOREIGN application. Measured 2026-08-07:
|
|
12
|
+
* an unrelated process on 5173 was what the editor's tab self-heal opened.
|
|
13
|
+
*
|
|
14
|
+
* **The band.** 20000-20199 is vgai's own reserved band: fixed, hand-picked
|
|
15
|
+
* ports that must never be minted for a worktree session. Machine-local
|
|
16
|
+
* allocations start at 20200, so one can never land on this default by chance.
|
|
17
|
+
* 20173 keeps Vite's memorable `173` tail while sitting in a range nothing
|
|
18
|
+
* else claims.
|
|
19
|
+
*/
|
|
20
|
+
export const DEFAULT_EDITOR_PORT = 20_173;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The Vite HMR websocket port paired with an editor port.
|
|
24
|
+
*
|
|
25
|
+
* Editor ports in 20000-29999 map into the parallel 30000-39999 band, so two
|
|
26
|
+
* worktree sessions cannot collide merely because their editor ports differ
|
|
27
|
+
* by 100. Anything outside that band keeps the simple +100 offset.
|
|
28
|
+
*
|
|
29
|
+
* `DEFAULT_EDITOR_PORT` derives 30173 through the first arm — no special case,
|
|
30
|
+
* which is the point: the old expression carried a hardcoded `5173 -> 24678`
|
|
31
|
+
* exception precisely because the default sat outside the band it designed.
|
|
32
|
+
*/
|
|
33
|
+
export function editorHmrPort(editorPort: number): number {
|
|
34
|
+
return editorPort >= 20_000 && editorPort < 30_100 ? editorPort + 10_000 : editorPort + 100;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Wrap a bare IPv6 literal in brackets so it is legal inside a URL authority.
|
|
39
|
+
* `127.0.0.1`, a hostname, and an already-bracketed literal pass through.
|
|
40
|
+
*/
|
|
41
|
+
function urlAuthorityHost(host: string): string {
|
|
42
|
+
if (host.startsWith('[')) return host;
|
|
43
|
+
return host.includes(':') ? `[${host}]` : host;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The ONE composer for an editor origin — every URL vgai PRINTS, OPENS,
|
|
48
|
+
* SELF-HEALS with, records in `.vgai/session.json`, or dials.
|
|
49
|
+
*
|
|
50
|
+
* **It never says `localhost`.** A bare-`localhost` URL is a different address
|
|
51
|
+
* from the one the server is listening on: on a dual-stack box `localhost`
|
|
52
|
+
* resolves `::1` first while the editor binds `127.0.0.1`, so the URL reaches
|
|
53
|
+
* whatever else happens to hold that port on IPv6. Measured 2026-08-07 — the
|
|
54
|
+
* tab bijection's self-heal opened an UNRELATED application in the owner's
|
|
55
|
+
* browser because it composed its reopen URL from `localhost` instead of the
|
|
56
|
+
* host the server had actually bound.
|
|
57
|
+
*
|
|
58
|
+
* `bindHost` is the host the server bound (`resolveBindHost`); callers that
|
|
59
|
+
* only know a port — the CLI and SDK clients dialing a local session — omit it
|
|
60
|
+
* and get the IPv4 loopback literal. A wildcard bind (`0.0.0.0` / `::`, which
|
|
61
|
+
* is what WSL gets) is not an address a browser can visit, so it degrades to
|
|
62
|
+
* that same literal: reachable from the same box, and reachable from Windows
|
|
63
|
+
* through WSL2's loopback forwarding.
|
|
64
|
+
*/
|
|
65
|
+
export function editorOrigin(port: number, bindHost?: string): string {
|
|
66
|
+
const host = (bindHost ?? '').trim();
|
|
67
|
+
const unusable = host === '' || host === '0.0.0.0' || host === '::' || host === '[::]';
|
|
68
|
+
return `http://${unusable ? '127.0.0.1' : urlAuthorityHost(host)}:${port}`;
|
|
69
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// The project manifest's FILENAME. There is exactly ONE.
|
|
2
|
+
//
|
|
3
|
+
// The manifest is `vgai.project.json`. It used to be `vgai.game.json`, and for
|
|
4
|
+
// a while both names were read — WO-9 #62 recorded that dual-read as
|
|
5
|
+
// permanent, on the presumption of an EXTERNAL constituency of projects
|
|
6
|
+
// already on disk. The owner reversed that on 2026-08-02 (the legacy-removal
|
|
7
|
+
// doctrine, `docs/ARCHITECTURE-CORE.md` §Vocabulary): this is pre-release
|
|
8
|
+
// software, no such constituency exists, and a second way to spell the
|
|
9
|
+
// manifest is the defect — two accepted names meant both kept being written,
|
|
10
|
+
// and copies of the "try both" probe grew in five modules that agreed with the
|
|
11
|
+
// real resolver only by coincidence.
|
|
12
|
+
//
|
|
13
|
+
// So the old name is REMOVED, and removed means it REJECTS LOUDLY naming the
|
|
14
|
+
// exact one-line fix — never silently ignored, never redirected. A directory
|
|
15
|
+
// carrying only `vgai.game.json` is an error with a `git mv` in the message,
|
|
16
|
+
// not a project that quietly fails to open.
|
|
17
|
+
//
|
|
18
|
+
// This module is the ONE place either literal is spelled out, and it is
|
|
19
|
+
// dependency-free (no `node:fs`) so browser bundles can import it. The
|
|
20
|
+
// filesystem side lives in `./locate` (`resolveManifestPath`, `hasManifest`,
|
|
21
|
+
// `assertNoRemovedManifestFilename`).
|
|
22
|
+
|
|
23
|
+
/** The manifest filename. Every reader reads it; every writer writes it. */
|
|
24
|
+
export const MANIFEST_FILENAME = 'vgai.project.json';
|
|
25
|
+
|
|
26
|
+
/** The removed filename. Read by nothing — its only job is the error message. */
|
|
27
|
+
export const REMOVED_MANIFEST_FILENAME = 'vgai.game.json';
|
|
28
|
+
|
|
29
|
+
/** True for the manifest filename (basename comparison). */
|
|
30
|
+
export function isManifestFilename(name: string): boolean {
|
|
31
|
+
return name === MANIFEST_FILENAME;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The verbatim rejection an on-disk `vgai.game.json` earns — the one-line fix
|
|
36
|
+
* spelled out, per the removed-format contract. Shared by the Node
|
|
37
|
+
* (`./locate`) and browser (storage-backend) halves so a project author sees
|
|
38
|
+
* the same sentence wherever the stale name is found.
|
|
39
|
+
*/
|
|
40
|
+
export function removedManifestFilenameMessage(where: string): string {
|
|
41
|
+
return (
|
|
42
|
+
`${where}: found \`${REMOVED_MANIFEST_FILENAME}\` and no \`${MANIFEST_FILENAME}\`. ` +
|
|
43
|
+
`\`${REMOVED_MANIFEST_FILENAME}\` was REMOVED (WO-LR1; the legacy-removal doctrine, ` +
|
|
44
|
+
'docs/ARCHITECTURE-CORE.md §Vocabulary) — nothing reads it any more, and it is ' +
|
|
45
|
+
'deliberately NOT read as a fallback, because a second accepted name is the defect.\n' +
|
|
46
|
+
`Fix: rename it — \`git mv ${REMOVED_MANIFEST_FILENAME} ${MANIFEST_FILENAME}\`. ` +
|
|
47
|
+
'The contents are unchanged; only the filename moved.'
|
|
48
|
+
);
|
|
49
|
+
}
|
package/src/manifest/index.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
// Game manifest (vgai.
|
|
2
|
-
//
|
|
1
|
+
// Game manifest (vgai.project.json) — schema + loader (T3.1 slice
|
|
2
|
+
// 1)..
|
|
3
3
|
|
|
4
|
+
export {
|
|
5
|
+
isManifestFilename,
|
|
6
|
+
MANIFEST_FILENAME,
|
|
7
|
+
REMOVED_MANIFEST_FILENAME,
|
|
8
|
+
removedManifestFilenameMessage,
|
|
9
|
+
} from './filename';
|
|
4
10
|
export type { ResolvedAdapter, ResolvedAdapterRoot, ResolvedGameManifest } from './load';
|
|
5
11
|
export { CAPABILITY_CEILINGS, DEFAULT_SERVER_MODULE, loadGameManifest } from './load';
|
|
6
12
|
export type {
|
|
@@ -8,9 +8,20 @@
|
|
|
8
8
|
|
|
9
9
|
import { readFileSync } from 'node:fs';
|
|
10
10
|
import { loadGameManifest, type ResolvedGameManifest } from './load';
|
|
11
|
+
import { resolveManifestPath } from './locate';
|
|
12
|
+
|
|
13
|
+
// The dual-name filesystem lookup lives in `./locate` (Zod-free on purpose);
|
|
14
|
+
// re-exported here so the common "load a project's manifest" import is one
|
|
15
|
+
// module.
|
|
16
|
+
export { hasManifest, manifestWritePath, resolveManifestPath } from './locate';
|
|
11
17
|
|
|
12
18
|
/** Read + JSON.parse `path`, then resolve it via `loadGameManifest`. Node-only. */
|
|
13
19
|
export function loadGameManifestFile(path: string): ResolvedGameManifest {
|
|
14
20
|
const raw: unknown = JSON.parse(readFileSync(path, 'utf-8'));
|
|
15
21
|
return loadGameManifest(raw);
|
|
16
22
|
}
|
|
23
|
+
|
|
24
|
+
/** {@link loadGameManifestFile} against `dir`'s `vgai.project.json`. */
|
|
25
|
+
export function loadGameManifestDir(dir: string): ResolvedGameManifest {
|
|
26
|
+
return loadGameManifestFile(resolveManifestPath(dir));
|
|
27
|
+
}
|