@vgai/engine 0.5.2 → 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
|
@@ -2,12 +2,16 @@ import { z } from 'zod';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `.instances.json` — a declarative, authored-data instance-transform asset
|
|
5
|
-
* (F3
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* (F3). Referenced by `mesh.instances` as a bare array of 10-number tuples,
|
|
6
|
+
* one per instance, each `[posX,posY,posZ, quatX,quatY,quatZ,quatW,
|
|
7
|
+
* scaleX,scaleY,scaleZ]`. The scene loader fetches+parses this file and
|
|
8
|
+
* renders it as one `THREE.InstancedMesh` (one draw call) — bulk transform
|
|
9
|
+
* data is referenced as an asset, never inlined into the referencing
|
|
10
|
+
* document (gate G4).
|
|
11
|
+
*
|
|
12
|
+
* WO-8: the document that referenced it was `.vscn.json`, and that format is
|
|
13
|
+
* deleted. `.instances.json` survives as a FETCHED-ASSET format with its own
|
|
14
|
+
* `parseInstancesFile` — nothing in the shipped tree references one today.
|
|
11
15
|
*/
|
|
12
16
|
export const InstancesFileSchema = z
|
|
13
17
|
.array(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const LightDescriptorBase = z.object({
|
|
4
4
|
type: z.enum(['directional', 'point', 'spot', 'hemisphere', 'area']).describe('Light type'),
|
|
5
5
|
color: z.string().optional().describe('Light color as CSS hex string'),
|
|
6
6
|
intensity: z
|
|
@@ -42,12 +42,9 @@ const SceneLightBase = z.object({
|
|
|
42
42
|
),
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
export const
|
|
45
|
+
export const LightDescriptorSchema = LightDescriptorBase.refine(
|
|
46
46
|
(l) => l.type !== 'spot' || l.angle === undefined || l.angle > 0,
|
|
47
47
|
{ message: 'Spot light angle must be positive' },
|
|
48
48
|
).describe('Light source configuration');
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
export const SceneLightOverrideSchema = SceneLightBase.partial();
|
|
52
|
-
|
|
53
|
-
export type SceneLight = z.infer<typeof SceneLightSchema>;
|
|
50
|
+
export type LightDescriptor = z.infer<typeof LightDescriptorSchema>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const MaterialDescriptorObjectSchema = z
|
|
4
4
|
.object({
|
|
5
5
|
type: z.enum(['standard', 'physical', 'basic', 'toon']).describe('Material shading model'),
|
|
6
6
|
color: z.string().optional().describe('Base color as CSS hex string (e.g. "#ff0000")'),
|
|
@@ -86,21 +86,18 @@ const SceneMaterialObjectSchema = z
|
|
|
86
86
|
})
|
|
87
87
|
.strict();
|
|
88
88
|
|
|
89
|
-
export const
|
|
89
|
+
export const MaterialDescriptorSchema = MaterialDescriptorObjectSchema.describe(
|
|
90
90
|
'Material appearance properties',
|
|
91
91
|
);
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
export const SceneMaterialOverrideSchema = SceneMaterialObjectSchema.partial();
|
|
95
|
-
|
|
96
|
-
export type SceneMaterial = z.infer<typeof SceneMaterialSchema>;
|
|
93
|
+
export type MaterialDescriptor = z.infer<typeof MaterialDescriptorSchema>;
|
|
97
94
|
|
|
98
95
|
/** .mat.json file format for shared/reusable material assets. */
|
|
99
96
|
export const MaterialFileSchema = z
|
|
100
97
|
.object({
|
|
101
98
|
version: z.number().describe('File format version'),
|
|
102
99
|
name: z.string().describe('Human-readable material name'),
|
|
103
|
-
material:
|
|
100
|
+
material: MaterialDescriptorSchema.describe('Material definition'),
|
|
104
101
|
})
|
|
105
102
|
.describe('Shared material asset file (.mat.json)');
|
|
106
103
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const MeshDescriptorBase = z
|
|
4
4
|
.object({
|
|
5
5
|
type: z
|
|
6
6
|
.enum(['box', 'sphere', 'plane', 'cylinder', 'capsule', 'gltf', 'splat'])
|
|
@@ -37,7 +37,7 @@ const SceneMeshBase = z
|
|
|
37
37
|
'Path to an instance-transform asset (.instances.json: an array of ' +
|
|
38
38
|
'[posX,posY,posZ, quatX,quatY,quatZ,quatW, scaleX,scaleY,scaleZ] tuples). Renders one ' +
|
|
39
39
|
'THREE.InstancedMesh (one draw call). Bulk transform data is referenced as an asset, ' +
|
|
40
|
-
'never inlined — keeps
|
|
40
|
+
'never inlined — keeps the document thin.',
|
|
41
41
|
),
|
|
42
42
|
lod: z
|
|
43
43
|
.array(
|
|
@@ -66,7 +66,7 @@ const SceneMeshBase = z
|
|
|
66
66
|
})
|
|
67
67
|
.strict();
|
|
68
68
|
|
|
69
|
-
export const
|
|
69
|
+
export const MeshDescriptorSchema = MeshDescriptorBase.refine(
|
|
70
70
|
(m) => (m.type !== 'gltf' && m.type !== 'splat') || m.src != null,
|
|
71
71
|
{
|
|
72
72
|
message: "External render asset requires 'src' field",
|
|
@@ -88,7 +88,4 @@ export const SceneMeshSchema = SceneMeshBase.refine(
|
|
|
88
88
|
})
|
|
89
89
|
.describe('Mesh geometry definition');
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
export const SceneMeshOverrideSchema = SceneMeshBase.partial();
|
|
93
|
-
|
|
94
|
-
export type SceneMesh = z.infer<typeof SceneMeshSchema>;
|
|
91
|
+
export type MeshDescriptor = z.infer<typeof MeshDescriptorSchema>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AssetParseError } from '../asset-parse-error';
|
|
2
|
+
import type { InstancesFile } from './instances';
|
|
3
|
+
import { InstancesFileSchema } from './instances';
|
|
4
|
+
import type { MaterialFile } from './material';
|
|
5
|
+
import { MaterialFileSchema } from './material';
|
|
6
|
+
|
|
7
|
+
// The error class itself is format-neutral and lives in
|
|
8
|
+
// `engine/src/asset-parse-error.ts` (the input-map loader raises it too).
|
|
9
|
+
// Re-exported here so callers have one import for parse + error.
|
|
10
|
+
export { AssetParseError };
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse a `.mat.json` shared material asset (T4.6). There is no version-range
|
|
14
|
+
* gate for this format — `MaterialFileSchema`'s `version` field is RESERVED
|
|
15
|
+
* (see `schema-consumption-map.ts`).
|
|
16
|
+
*
|
|
17
|
+
* Formerly `@engine/scene/parse`'s `parseMaterialFile`; moved here when WO-8
|
|
18
|
+
* deleted the `.vscn` scene format that module was named for.
|
|
19
|
+
*/
|
|
20
|
+
export function parseMaterialFile(json: unknown, filePath?: string): MaterialFile {
|
|
21
|
+
const result = MaterialFileSchema.safeParse(json);
|
|
22
|
+
if (!result.success) {
|
|
23
|
+
throw new AssetParseError(result.error.issues, filePath);
|
|
24
|
+
}
|
|
25
|
+
return result.data;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse a `.instances.json` instance-transform asset (F3). Mirrors
|
|
30
|
+
* `parseMaterialFile` exactly — no version-range gate (the format is a bare
|
|
31
|
+
* tuple array, no `version` field).
|
|
32
|
+
*/
|
|
33
|
+
export function parseInstancesFile(json: unknown, filePath?: string): InstancesFile {
|
|
34
|
+
const result = InstancesFileSchema.safeParse(json);
|
|
35
|
+
if (!result.success) {
|
|
36
|
+
throw new AssetParseError(result.error.issues, filePath);
|
|
37
|
+
}
|
|
38
|
+
return result.data;
|
|
39
|
+
}
|
|
@@ -320,9 +320,9 @@ const BurstSchema = z
|
|
|
320
320
|
})
|
|
321
321
|
.describe('Timed particle burst');
|
|
322
322
|
|
|
323
|
-
// --- Top-level
|
|
323
|
+
// --- Top-level ParticlesDescriptor ---
|
|
324
324
|
|
|
325
|
-
export const
|
|
325
|
+
export const ParticlesDescriptorSchema = z
|
|
326
326
|
.object({
|
|
327
327
|
// Lifecycle
|
|
328
328
|
autoDestroy: z
|
|
@@ -392,7 +392,4 @@ export const SceneParticlesSchema = z
|
|
|
392
392
|
})
|
|
393
393
|
.describe('Particle system configuration');
|
|
394
394
|
|
|
395
|
-
|
|
396
|
-
export const SceneParticlesOverrideSchema = SceneParticlesSchema.partial();
|
|
397
|
-
|
|
398
|
-
export type SceneParticles = z.infer<typeof SceneParticlesSchema>;
|
|
395
|
+
export type ParticlesDescriptor = z.infer<typeof ParticlesDescriptorSchema>;
|
|
@@ -1,45 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render settings — the tone-mapping / post-processing / render-feature scope
|
|
3
|
+
* the engine's renderer setup reads (`setup/setup-renderer.ts`).
|
|
4
|
+
*
|
|
5
|
+
* This used to be the `environment` block of the retired `.vscn.json` scene
|
|
6
|
+
* format (WO-8). The format is gone; these three scopes stayed because
|
|
7
|
+
* `setup-renderer.ts` genuinely reads every field and builds the
|
|
8
|
+
* postprocessing chain from them. Everything that was `.vscn`-only (`skybox`,
|
|
9
|
+
* `fog`, `ambient`, `background`, `envMapIntensity`, `navigation`, `physics`)
|
|
10
|
+
* went with the format.
|
|
11
|
+
*/
|
|
1
12
|
import { z } from 'zod';
|
|
2
|
-
import { buildRenderingZod } from '
|
|
13
|
+
import { buildRenderingZod } from '../render/render-features';
|
|
3
14
|
|
|
4
|
-
export const
|
|
5
|
-
.object({
|
|
6
|
-
cellSize: z
|
|
7
|
-
.number()
|
|
8
|
-
.optional()
|
|
9
|
-
.describe('Navmesh voxel cell size (smaller = more detail, slower bake)'),
|
|
10
|
-
cellHeight: z.number().optional().describe('Navmesh voxel cell height'),
|
|
11
|
-
walkableSlopeAngle: z
|
|
12
|
-
.number()
|
|
13
|
-
.optional()
|
|
14
|
-
.describe('Maximum slope angle (degrees) agents can walk on'),
|
|
15
|
-
walkableHeight: z
|
|
16
|
-
.number()
|
|
17
|
-
.optional()
|
|
18
|
-
.describe('Minimum ceiling height for walkable areas (in voxels)'),
|
|
19
|
-
walkableClimb: z
|
|
20
|
-
.number()
|
|
21
|
-
.optional()
|
|
22
|
-
.describe('Maximum step height agents can climb (in voxels)'),
|
|
23
|
-
walkableRadius: z.number().optional().describe('Agent radius for navmesh erosion (in voxels)'),
|
|
24
|
-
maxEdgeLen: z.number().optional().describe('Maximum contour edge length in the navmesh'),
|
|
25
|
-
maxSimplificationError: z
|
|
26
|
-
.number()
|
|
27
|
-
.optional()
|
|
28
|
-
.describe('Maximum deviation for navmesh edge simplification'),
|
|
29
|
-
minRegionArea: z
|
|
30
|
-
.number()
|
|
31
|
-
.optional()
|
|
32
|
-
.describe('Minimum region area — smaller regions are removed'),
|
|
33
|
-
mergeRegionArea: z
|
|
34
|
-
.number()
|
|
35
|
-
.optional()
|
|
36
|
-
.describe('Regions smaller than this are merged with neighbors'),
|
|
37
|
-
})
|
|
38
|
-
.describe('Recast navigation mesh bake parameters');
|
|
39
|
-
|
|
40
|
-
export type SceneNavigation = z.infer<typeof SceneNavigationSchema>;
|
|
41
|
-
|
|
42
|
-
export const SceneToneMappingSchema = z
|
|
15
|
+
export const ToneMappingDescriptorSchema = z
|
|
43
16
|
.object({
|
|
44
17
|
mode: z
|
|
45
18
|
.enum(['none', 'linear', 'reinhard', 'cineon', 'aces', 'agx'])
|
|
@@ -49,9 +22,9 @@ export const SceneToneMappingSchema = z
|
|
|
49
22
|
})
|
|
50
23
|
.describe('Tone mapping configuration');
|
|
51
24
|
|
|
52
|
-
export type
|
|
25
|
+
export type ToneMappingDescriptor = z.infer<typeof ToneMappingDescriptorSchema>;
|
|
53
26
|
|
|
54
|
-
export const
|
|
27
|
+
export const PostProcessingDescriptorSchema = z
|
|
55
28
|
.object({
|
|
56
29
|
// ── Bloom & Vignette ──────────────────────────────────────────────────
|
|
57
30
|
bloom: z
|
|
@@ -329,56 +302,20 @@ export const ScenePostProcessingSchema = z
|
|
|
329
302
|
})
|
|
330
303
|
.describe('Post-processing effects');
|
|
331
304
|
|
|
332
|
-
export type
|
|
305
|
+
export type PostProcessingDescriptor = z.infer<typeof PostProcessingDescriptorSchema>;
|
|
333
306
|
|
|
334
|
-
export const
|
|
307
|
+
export const RenderEnvironmentSchema = z
|
|
335
308
|
.object({
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
.describe('Named collision layers (up to 16). Index = bit position in collision bitmasks'),
|
|
340
|
-
})
|
|
341
|
-
.describe('Physics world settings');
|
|
342
|
-
|
|
343
|
-
export type ScenePhysicsSettings = z.infer<typeof ScenePhysicsSettingsSchema>;
|
|
344
|
-
|
|
345
|
-
export const SceneEnvironmentSchema = z
|
|
346
|
-
.object({
|
|
347
|
-
background: z.string().optional().describe('Background color as CSS hex string'),
|
|
348
|
-
ambient: z
|
|
349
|
-
.object({
|
|
350
|
-
color: z.string().describe('Ambient light color'),
|
|
351
|
-
intensity: z.number().describe('Ambient light intensity'),
|
|
352
|
-
})
|
|
353
|
-
.optional()
|
|
354
|
-
.describe('Global ambient light'),
|
|
355
|
-
fog: z
|
|
356
|
-
.object({
|
|
357
|
-
type: z.enum(['linear', 'exponential']).describe('Fog falloff type'),
|
|
358
|
-
color: z.string().describe('Fog color'),
|
|
359
|
-
near: z.number().optional().describe('Fog start distance (linear only)'),
|
|
360
|
-
far: z.number().optional().describe('Fog end distance (linear only)'),
|
|
361
|
-
density: z.number().optional().describe('Fog density (exponential only)'),
|
|
362
|
-
})
|
|
363
|
-
.optional()
|
|
364
|
-
.describe('Distance fog settings'),
|
|
365
|
-
skybox: z.string().optional().describe('Path to HDR/EXR skybox environment map'),
|
|
366
|
-
envMapIntensity: z
|
|
367
|
-
.number()
|
|
368
|
-
.optional()
|
|
369
|
-
.describe('Environment map reflection intensity multiplier'),
|
|
370
|
-
toneMapping: SceneToneMappingSchema.optional().describe('Tone mapping configuration'),
|
|
371
|
-
postProcessing: ScenePostProcessingSchema.optional().describe(
|
|
372
|
-
'Post-processing effects (bloom, vignette)',
|
|
309
|
+
toneMapping: ToneMappingDescriptorSchema.optional().describe('Tone mapping configuration'),
|
|
310
|
+
postProcessing: PostProcessingDescriptorSchema.optional().describe(
|
|
311
|
+
'Post-processing effects (bloom, vignette, SSAO, SSR, ...)',
|
|
373
312
|
),
|
|
374
|
-
navigation: SceneNavigationSchema.optional().describe('Navigation mesh bake settings'),
|
|
375
|
-
physics: ScenePhysicsSettingsSchema.optional().describe('Physics world settings'),
|
|
376
313
|
rendering: buildRenderingZod()
|
|
377
314
|
.optional()
|
|
378
315
|
.describe(
|
|
379
|
-
'
|
|
316
|
+
'Render feature toggles (auto-batching, culling, LOD, shadows, etc.). Omit a field to inherit the engine default. Derived from the render-feature registry.',
|
|
380
317
|
),
|
|
381
318
|
})
|
|
382
|
-
.describe('
|
|
319
|
+
.describe('Renderer-level settings read by setup/setup-renderer.ts');
|
|
383
320
|
|
|
384
|
-
export type
|
|
321
|
+
export type RenderEnvironment = z.infer<typeof RenderEnvironmentSchema>;
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
import type { SplatMesh } from '@sparkjsdev/spark';
|
|
26
26
|
import * as THREE from 'three';
|
|
27
27
|
import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
28
|
+
import { AssetParseError } from './asset-parse-error';
|
|
29
|
+
import { setUserData } from './ecs/user-data';
|
|
30
|
+
import { gltfLoader, resolveUrl, textureLoader } from './loader';
|
|
31
31
|
|
|
32
32
|
const textureCache = new Map<string, THREE.Texture>();
|
|
33
33
|
const gltfCache = new Map<
|
|
@@ -146,9 +146,8 @@ export async function loadSplat(url: string, providedBytes?: Uint8Array): Promis
|
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
148
|
* Resolve a single named node inside an already-loaded glTF scene graph (F4,
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* got from {@link loadGLTF}.
|
|
149
|
+
* `mesh.node`). Pure and headlessly testable — no fetch, no cache lookups;
|
|
150
|
+
* callers pass the `scene` they already got from {@link loadGLTF}.
|
|
152
151
|
*
|
|
153
152
|
* The returned node is the SAME object (and shares the SAME geometry) as
|
|
154
153
|
* found in `scene` — nothing is cloned or copied out. `scene` is already a
|
|
@@ -158,7 +157,7 @@ export async function loadSplat(url: string, providedBytes?: Uint8Array): Promis
|
|
|
158
157
|
* Atomic-subtree guard: a `SkinnedMesh` cannot be lifted out of its armature
|
|
159
158
|
* (its skeleton/bind matrices reference sibling bone nodes elsewhere in the
|
|
160
159
|
* hierarchy), so resolving a node that IS a SkinnedMesh, or that CONTAINS one,
|
|
161
|
-
* throws a loud `
|
|
160
|
+
* throws a loud `AssetParseError` instead of silently producing broken skinning.
|
|
162
161
|
*/
|
|
163
162
|
export function resolveGltfNode(
|
|
164
163
|
scene: THREE.Object3D,
|
|
@@ -167,7 +166,7 @@ export function resolveGltfNode(
|
|
|
167
166
|
): THREE.Object3D {
|
|
168
167
|
const node = scene.getObjectByName(nodeName);
|
|
169
168
|
if (!node) {
|
|
170
|
-
throw new
|
|
169
|
+
throw new AssetParseError(
|
|
171
170
|
[
|
|
172
171
|
{
|
|
173
172
|
code: 'custom',
|
|
@@ -187,7 +186,7 @@ export function resolveGltfNode(
|
|
|
187
186
|
});
|
|
188
187
|
}
|
|
189
188
|
if (containsSkinned) {
|
|
190
|
-
throw new
|
|
189
|
+
throw new AssetParseError(
|
|
191
190
|
[
|
|
192
191
|
{
|
|
193
192
|
code: 'custom',
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GENERIC fetched-asset parse error.
|
|
3
|
+
*
|
|
4
|
+
* Every authored asset format the engine validates at fetch time throws this
|
|
5
|
+
* when Zod rejects the payload, naming the offending file (T4.6). It lives
|
|
6
|
+
* here — beside `loader.ts`/`assets.ts` — rather than inside a format module so
|
|
7
|
+
* every caller (the `.mat.json` parser, the input-map loader in
|
|
8
|
+
* `input/input-manager.ts`) does not have to reach into the format half for
|
|
9
|
+
* it.
|
|
10
|
+
*
|
|
11
|
+
* It was called `SceneParseError` until WO-9 task #6. The extraction that gave
|
|
12
|
+
* this file its name deliberately deferred the class rename ("extraction, not
|
|
13
|
+
* redesign"); with `.vscn` gone the old name described nothing — a `.mat.json`
|
|
14
|
+
* or `.inputmap.json` failure is not a scene failure — so the class and its
|
|
15
|
+
* message header now say `asset`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ZodIssue } from 'zod';
|
|
19
|
+
|
|
20
|
+
export class AssetParseError extends Error {
|
|
21
|
+
readonly issues: ZodIssue[];
|
|
22
|
+
/** The offending file's path/URL, when the caller knows it (T4.6). */
|
|
23
|
+
readonly filePath?: string | undefined;
|
|
24
|
+
|
|
25
|
+
constructor(issues: ZodIssue[], filePath?: string) {
|
|
26
|
+
const msg = issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\n');
|
|
27
|
+
const header = filePath ? `Asset validation failed (${filePath}):` : 'Asset validation failed:';
|
|
28
|
+
super(`${header}\n${msg}`);
|
|
29
|
+
this.name = 'AssetParseError';
|
|
30
|
+
this.issues = issues;
|
|
31
|
+
this.filePath = filePath;
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/assets.ts
CHANGED
|
@@ -34,7 +34,7 @@ export function createAssetCache(): AssetCache {
|
|
|
34
34
|
// Use the shared, DRACO-wired GLTFLoader (A2 / CB2) — no second loader instance.
|
|
35
35
|
// This cache keeps the parsed source GLTFResult (load/get semantics for gameplay
|
|
36
36
|
// code); it does NOT clone. Callers that need disposal-safe clones use
|
|
37
|
-
//
|
|
37
|
+
// asset-loaders `loadGLTF`, which clones + tags `__sharedGeometry`.
|
|
38
38
|
|
|
39
39
|
function load<T = unknown>(url: string): Promise<T> {
|
|
40
40
|
const existing = cache.get(url);
|
package/src/audio/wav-encode.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure PCM -> 16-bit WAV encoder (spec §15 I6 "Encode and Mux", audio half
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
2
|
+
* Pure PCM -> 16-bit WAV encoder (spec §15 I6 "Encode and Mux", audio half).
|
|
3
|
+
* This module takes already-rendered Float32 channel data (e.g.
|
|
4
|
+
* `AudioBuffer.getChannelData(n)` from a `Tone.Offline` render,
|
|
5
|
+
* `audio/tone-offline-render.ts`) and produces a standard little-endian,
|
|
6
|
+
* 16-bit-PCM, interleaved RIFF/WAVE file as raw bytes — no
|
|
7
|
+
* `AudioBuffer`/`AudioContext` dependency, so this runs equally in a browser
|
|
8
|
+
* render-mode page (`runtime/render-audio-control.ts`) and in plain Node (this
|
|
9
|
+
* module's own `packages/engine/test/audio/wav-encode.test.ts`, no Playwright
|
|
10
|
+
* needed).
|
|
11
11
|
*
|
|
12
12
|
* 16-bit PCM (not 32-bit float) is the deliberate choice named by the I6 AC
|
|
13
13
|
* ("16-bit PCM WAV is fine") — it is universally decodable by every FFmpeg
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@engine/canvas-react` — the React context that carries a canvas world's
|
|
3
|
+
* engine runtime into its `@pixi/react` tree: the world's `GameContext`, read
|
|
4
|
+
* with {@link useGameContext}.
|
|
5
|
+
*
|
|
6
|
+
* The bridge value is built by `createPixiReactAdapter`'s `mount()`
|
|
7
|
+
* (./pixi-react-adapter.tsx), whose `update(dt)` runs this world's phases under
|
|
8
|
+
* the HOST loop — so registered systems tick in engine phase order, pause
|
|
9
|
+
* included (no update while the world is frozen).
|
|
10
|
+
*
|
|
11
|
+
* Behavior in a canvas world is components + hooks: `useGameContext` reaches
|
|
12
|
+
* engine services, `@pixi/react`'s own `useTick` is the per-frame hook, and
|
|
13
|
+
* `useApplication` is the door to the live `Application`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createContext, useContext } from 'react';
|
|
17
|
+
import type { GameContext } from '../runtime/types';
|
|
18
|
+
|
|
19
|
+
export interface EngineBridgeValue {
|
|
20
|
+
/** This root's `GameContext` — `input`, `debug`, `random`, `clock`, `game`,
|
|
21
|
+
* `roots`, `systems`, `registerSystemAdapter`. Present so ordinary
|
|
22
|
+
* components can reach engine services with a HOOK (see
|
|
23
|
+
* {@link useGameContext}). */
|
|
24
|
+
readonly ctx?: GameContext | undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const EngineBridge = createContext<EngineBridgeValue | null>(null);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* This canvas root's `GameContext`, for use inside any component in the tree.
|
|
31
|
+
*
|
|
32
|
+
* An ordinary component does what a model would write:
|
|
33
|
+
*
|
|
34
|
+
* ```tsx
|
|
35
|
+
* function Player({ speed = 220 }) {
|
|
36
|
+
* const { input } = useGameContext();
|
|
37
|
+
* const ref = useRef<Sprite>(null);
|
|
38
|
+
* useTick(({ deltaMS }) => {
|
|
39
|
+
* if (input.isPressed('move_right')) ref.current!.x += speed * (deltaMS / 1000);
|
|
40
|
+
* });
|
|
41
|
+
* return <pixiSprite ref={ref} texture={Texture.WHITE} />;
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* Throws when called outside a canvas root mounted by
|
|
46
|
+
* `createPixiReactAdapter` — absent context is a wiring bug, and a silent
|
|
47
|
+
* `null` would surface as a mystery `undefined` deep in a tick callback.
|
|
48
|
+
*/
|
|
49
|
+
export function useGameContext(): GameContext {
|
|
50
|
+
const bridge = useContext(EngineBridge);
|
|
51
|
+
if (!bridge?.ctx) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
'useGameContext(): no engine root context. This hook only works inside a canvas ' +
|
|
54
|
+
'root mounted by createPixiReactAdapter (@engine/canvas-react) — check that the ' +
|
|
55
|
+
'component is rendered inside your world entry, not in a standalone <Application>.',
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return bridge.ctx;
|
|
59
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* canvas-react — the `@pixi/react` lane for vgai's canvas surface
|
|
3
|
+
* (`@engine/canvas-react`).
|
|
4
|
+
*
|
|
5
|
+
* The canvas surface is source-as-truth: a canvas root's document IS its
|
|
6
|
+
* TSX world file, exactly as a three root's document is its R3F source. This
|
|
7
|
+
* module is the peer of `world3d-react/` — same seam, same shape, Pixi instead
|
|
8
|
+
* of three — and the peer of `pixi/`, which holds what the host needs to mount
|
|
9
|
+
* and inspect a PixiJS game it did NOT write.
|
|
10
|
+
*
|
|
11
|
+
* The engine CORE never imports this (enforced by
|
|
12
|
+
* `packages/engine/test/react-core-import-ban.test.ts` — this directory is an
|
|
13
|
+
* allowed react-importing entry alongside `react/` and `world3d-react/`, and
|
|
14
|
+
* core files may not import it), so a three-only bundle never pays for react
|
|
15
|
+
* or the Pixi reconciler.
|
|
16
|
+
*
|
|
17
|
+
* Surface:
|
|
18
|
+
* - `createPixiReactAdapter({ id, content })` — mount a `@pixi/react` tree as
|
|
19
|
+
* a first-party `surface: "canvas"` root under the host's gated loop.
|
|
20
|
+
* - `pixiReactRootFactory` / `resolveCanvasEntryAdapter` — what a canvas entry
|
|
21
|
+
* module MEANS: a default-exported component (or an explicit `adapter`).
|
|
22
|
+
* - `PixiPrimitive` / `adoptNow` — the canvas surface's `<primitive object={…}>`:
|
|
23
|
+
* render a container the game already owns, and place a spawn in the
|
|
24
|
+
* display tree ahead of the commit that renders it.
|
|
25
|
+
* - `useGameContext()` — this root's `GameContext` (`input`, `debug`,
|
|
26
|
+
* `random`, `clock`, `game`, `roots`) from inside any component in the tree.
|
|
27
|
+
* Behavior in a canvas world is components + hooks, never a class under a
|
|
28
|
+
* string key.
|
|
29
|
+
* - `EngineBridge` / `EngineBridgeValue` — the context contract, exported for
|
|
30
|
+
* adopters building their own bridge.
|
|
31
|
+
*
|
|
32
|
+
* The Pixi runtime itself is NOT re-exported here. A component reaches the live
|
|
33
|
+
* `Application` (and therefore `stage`, `renderer`, `ticker`) with
|
|
34
|
+
* `@pixi/react`'s own `useApplication()`, ticks with its `useTick()`, and loads
|
|
35
|
+
* assets with Pixi's own `Assets` — the library's API, not a second one.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export { EngineBridge, type EngineBridgeValue, useGameContext } from './engine-bridge';
|
|
39
|
+
export { adoptNow, PixiPrimitive, type PixiPrimitiveProps } from './pixi-primitive';
|
|
40
|
+
export {
|
|
41
|
+
type CreatePixiReactAdapterOptions,
|
|
42
|
+
createPixiReactAdapter,
|
|
43
|
+
} from './pixi-react-adapter';
|
|
44
|
+
export { pixiReactRootFactory, resolveCanvasEntryAdapter } from './pixi-react-root-factory';
|
|
45
|
+
export {
|
|
46
|
+
type CanvasRootContextOptions,
|
|
47
|
+
type CanvasRootRuntime,
|
|
48
|
+
createCanvasRootContext,
|
|
49
|
+
DEFAULT_INPUT_MAP_PATH,
|
|
50
|
+
} from './world-context';
|