@vgai/engine 0.2.0 → 0.4.0-canary.20260715.0
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 +3 -1
- package/package.json +24 -4
- package/schemas/engine-api.json +124 -0
- package/schemas/engine-api.md +53 -0
- package/schemas/engine-capabilities.json +124 -0
- package/schemas/inputmap.schema.json +314 -0
- package/schemas/mat.schema.json +286 -0
- package/schemas/prefab.schema.json +10148 -0
- package/schemas/scn2d.schema.json +475 -0
- package/schemas/vgai-game.schema.json +383 -0
- package/schemas/vscn.schema.json +11007 -0
- package/src/adapter/{world-kind.ts → adapter-surface.ts} +6 -6
- package/src/adapter/authoring.ts +77 -0
- package/src/adapter/first-party-systems.ts +23 -34
- package/src/adapter/game-adapter.ts +8 -8
- package/src/adapter/host-context.ts +2 -4
- package/src/adapter/index.ts +4 -4
- package/src/adapter/system-adapter.ts +88 -22
- package/src/adapter/vgai-scene-game-adapter.ts +244 -194
- package/src/animation/anim-graph-types.ts +12 -43
- package/src/animation/animation-clock.ts +479 -0
- package/src/animation/camera-ownership.ts +467 -0
- package/src/animation/cinematic-cues.ts +451 -0
- package/src/animation/clip-map.ts +41 -0
- package/src/animation/gsap-registration.ts +184 -0
- package/src/animation/theatre-clock-binding.ts +111 -0
- package/src/animation/theatre-director.ts +347 -0
- package/src/animation/theatre-object-binding.ts +661 -0
- package/src/animation/xstate-animation-binding.ts +436 -0
- package/src/animation/xstate-animation-meta.ts +319 -0
- package/src/audio/index.ts +39 -7
- package/src/audio/tone-clock-binding.ts +98 -0
- package/src/audio/tone-context.ts +129 -0
- package/src/audio/tone-offline-render.ts +167 -0
- package/src/audio/wav-encode.ts +119 -0
- package/src/character/cloth-sim.ts +533 -0
- package/src/character/spring-chain.ts +307 -0
- package/src/core/game-loop.ts +57 -2
- package/src/core/seeded-random.ts +161 -0
- package/src/core/system-runner.ts +20 -3
- package/src/core/types.ts +50 -0
- package/src/data/data-asset.ts +167 -0
- package/src/data/data-check-core.ts +242 -0
- package/src/data/data-ref.ts +145 -0
- package/src/data/vite-plugin-data.ts +290 -0
- package/src/dev/performance-profiler.ts +213 -0
- package/src/dev/webgl-gpu-timer.ts +53 -0
- package/src/ecs/component-manager.ts +45 -12
- package/src/ecs/game-component.ts +95 -11
- package/src/humanoid/bake.operation.ts +326 -0
- package/src/humanoid/body.ts +663 -0
- package/src/humanoid/clips.ts +149 -0
- package/src/humanoid/compose.ts +209 -0
- package/src/humanoid/generate.ts +189 -0
- package/src/humanoid/index.ts +36 -0
- package/src/humanoid/schema.ts +108 -0
- package/src/humanoid/skeleton.ts +345 -0
- package/src/index.ts +48 -0
- package/src/input/input-manager.ts +1886 -33
- package/src/input/input-types.ts +158 -3
- package/src/input/prompt-labels.ts +122 -0
- package/src/input/rebind-controller.ts +105 -0
- package/src/input/schema.ts +206 -52
- package/src/manifest/index.ts +5 -5
- package/src/manifest/load.ts +125 -72
- package/src/manifest/schema.ts +362 -255
- package/src/react/game-state.tsx +135 -32
- package/src/react/root-adapter.tsx +49 -0
- package/src/react/unmanaged-root-detector.ts +66 -0
- package/src/react/use-data.ts +124 -0
- package/src/react/use-selection.tsx +135 -0
- package/src/runtime/create-runtime.ts +112 -273
- package/src/runtime/debug-bridge.ts +483 -0
- package/src/runtime/debug-registry.ts +856 -0
- package/src/runtime/game.ts +342 -93
- package/src/runtime/gameplay-rng-trap.ts +134 -0
- package/src/runtime/input-router.ts +7 -7
- package/src/runtime/mount-game.ts +40 -38
- package/src/runtime/mount-manifest.ts +169 -37
- package/src/runtime/render-audio-control.ts +168 -0
- package/src/runtime/render-control.ts +522 -0
- package/src/runtime/render-seed.ts +79 -0
- package/src/runtime/state-bridge.ts +24 -10
- package/src/runtime/types.ts +110 -33
- package/src/scene/asset-loaders.ts +10 -36
- package/src/scene/asset-paths.ts +0 -2
- package/src/scene/asset-ref-check.ts +248 -0
- package/src/scene/asset-registry.ts +22 -0
- package/src/scene/component-registry.ts +14 -3
- package/src/scene/defaults.ts +1 -0
- package/src/scene/light-camera-factory.ts +11 -3
- package/src/scene/parse.ts +133 -0
- package/src/scene/scene-apply.ts +55 -4
- package/src/scene/scene-loader.ts +91 -123
- package/src/scene/scene-types.ts +0 -1
- package/src/scene/schema/animation.ts +30 -79
- package/src/scene/schema/entity.ts +20 -0
- package/src/scene/schema/index.ts +2 -46
- package/src/scene/schema/light.ts +16 -1
- package/src/scene/schema/material.ts +96 -91
- package/src/scene/schema/scene-file.ts +1 -7
- package/src/scene/user-data.ts +22 -10
- package/src/setup/setup-renderer.ts +10 -3
- package/src/tools/define-tool.ts +191 -0
- package/src/world2d/authoring-2d.ts +17 -1
- package/src/world2d/collision-2d.ts +1 -1
- package/src/world2d/pixi-game-adapter.ts +19 -17
- package/src/world2d/scene2d-loader.ts +1 -0
- package/src/world2d/types.ts +8 -2
- package/src/animation/anim-graph.ts +0 -406
- package/src/animation/anim-system.ts +0 -28
- package/src/animation/property-track.ts +0 -178
- package/src/animation/schema.ts +0 -204
- package/src/audio/ambient.ts +0 -300
- package/src/audio/impacts.ts +0 -212
- package/src/audio/movement.ts +0 -140
- package/src/audio/musical.ts +0 -200
- package/src/audio/ui-sounds.ts +0 -171
- package/src/audio/vehicle.ts +0 -235
- package/src/audio/weapons.ts +0 -152
- package/src/runtime/scene-ui-bridge.ts +0 -86
- package/src/runtime/scene-ui-data.ts +0 -119
- package/src/scene/schema/ui.ts +0 -602
|
@@ -1,94 +1,45 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
// Reuse the UI track easing vocabulary (S1) so there is ONE easing enum across
|
|
3
|
-
// the codebase — see F5 (docs/VSCN-STRUCTURAL-GAPS-DESIGN.md). Do NOT reuse
|
|
4
|
-
// UIKeyframeSchema itself: its `value` is `z.union([z.number(), z.string()])`,
|
|
5
|
-
// and property-track values MUST be numbers only (no expression/string-logic
|
|
6
|
-
// values — in-scene scripting is banned, G1).
|
|
7
|
-
import { UIEasingSchema } from './ui';
|
|
8
|
-
|
|
9
|
-
/** Validated at runtime against AnimGraph parameter definitions. */
|
|
10
|
-
const AnimParameterOverridesSchema = z.record(z.string(), z.union([z.number(), z.boolean()]));
|
|
11
2
|
|
|
12
3
|
// ---------------------------------------------------------------------------
|
|
13
|
-
//
|
|
4
|
+
// D8 — declarative property tracks (formerly F5,
|
|
5
|
+
// docs/VSCN-STRUCTURAL-GAPS-DESIGN.md) were REMOVED: Theatre
|
|
6
|
+
// (`@theatre/core`) is the sole authored continuous-animation source (spec
|
|
7
|
+
// §3.2, §10 D2-D4/D8). `PropertyTrackSchema`/`PropertyTrackRunner` and the
|
|
8
|
+
// `tracks` field are deleted, not deprecated-in-place — see
|
|
9
|
+
// packages/engine/src/scene/parse.ts (`assertNoRemovedPropertyTracks`) for
|
|
10
|
+
// the loud migration-error guard that rejects any `.vscn`/`.prefab.json`
|
|
11
|
+
// still authoring `animation.tracks`, since Zod would otherwise silently
|
|
12
|
+
// strip an unrecognized field rather than failing loudly (Global AC §6).
|
|
13
|
+
//
|
|
14
|
+
// E5 — the AnimGraph state-machine runtime, `.animgraph.json` format, and
|
|
15
|
+
// every consumer were removed (spec §3.3, §11 E5; XState + native Three
|
|
16
|
+
// `AnimationMixer` replace it — see `xstate-animation-binding.ts`). The
|
|
17
|
+
// `animGraph` field is deleted outright, not deprecated-in-place, for the
|
|
18
|
+
// same reason as `tracks` above: Zod's default object mode would otherwise
|
|
19
|
+
// silently STRIP an authored `animation.animGraph` rather than reporting it,
|
|
20
|
+
// violating Global AC §6 ("Removed formats fail with a concise migration/
|
|
21
|
+
// removal error rather than being partially read"). The raw-JSON guard
|
|
22
|
+
// (`assertNoRemovedAnimGraph` in packages/engine/src/scene/parse.ts) rejects
|
|
23
|
+
// any `.vscn`/`.prefab.json` still authoring it before Zod ever parses the
|
|
24
|
+
// data — see packages/engine/test/anim-graph-removed.test.ts.
|
|
14
25
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
26
|
+
// `parameters` is deleted alongside `animGraph`, though it isn't itself named
|
|
27
|
+
// in the spec/removal-inventory: its ONLY documented purpose was seeding an
|
|
28
|
+
// `AnimGraph`'s initial parameter values (its only readers were
|
|
29
|
+
// `scene-loader.ts`'s former animGraph branch, the editor's entity-factory.ts,
|
|
30
|
+
// and AnimationSection.tsx's parameter-row UI — all deleted with the runtime).
|
|
31
|
+
// Leaving it declared with zero remaining readers would violate this repo's
|
|
32
|
+
// "no schema field without a runtime reader" policy (CLAUDE.md).
|
|
21
33
|
// ---------------------------------------------------------------------------
|
|
22
34
|
|
|
23
|
-
export const PropertyTrackTargetSchema = z
|
|
24
|
-
.enum([
|
|
25
|
-
'position.x',
|
|
26
|
-
'position.y',
|
|
27
|
-
'position.z',
|
|
28
|
-
'rotation.x',
|
|
29
|
-
'rotation.y',
|
|
30
|
-
'rotation.z',
|
|
31
|
-
'scale.x',
|
|
32
|
-
'scale.y',
|
|
33
|
-
'scale.z',
|
|
34
|
-
'material.opacity',
|
|
35
|
-
'light.intensity',
|
|
36
|
-
])
|
|
37
|
-
.describe(
|
|
38
|
-
'The Object3D property this track drives. A FIXED allowlist, not an arbitrary property ' +
|
|
39
|
-
'path — an open path would let scene data address/mutate arbitrary runtime state, which ' +
|
|
40
|
-
'is in-scene scripting (banned, G1). Add new targets here deliberately, one at a time.',
|
|
41
|
-
);
|
|
42
|
-
export type PropertyTrackTarget = z.infer<typeof PropertyTrackTargetSchema>;
|
|
43
|
-
|
|
44
|
-
export const PropertyTrackKeyframeSchema = z
|
|
45
|
-
.object({
|
|
46
|
-
time: z.number().describe('Seconds from track start'),
|
|
47
|
-
value: z
|
|
48
|
-
.number()
|
|
49
|
-
.describe(
|
|
50
|
-
'Numeric value at this time — numbers only; no expressions/strings (in-scene scripting is banned)',
|
|
51
|
-
),
|
|
52
|
-
easing: UIEasingSchema.optional().describe(
|
|
53
|
-
'Easing of the segment after this keyframe (reuses the UI track easing vocabulary)',
|
|
54
|
-
),
|
|
55
|
-
})
|
|
56
|
-
.describe('A single keyframe (time + numeric value + optional easing) on a property track');
|
|
57
|
-
export type PropertyTrackKeyframe = z.infer<typeof PropertyTrackKeyframeSchema>;
|
|
58
|
-
|
|
59
|
-
export const PropertyTrackSchema = z
|
|
60
|
-
.object({
|
|
61
|
-
target: PropertyTrackTargetSchema,
|
|
62
|
-
keyframes: z.array(PropertyTrackKeyframeSchema).describe('Ordered by time'),
|
|
63
|
-
loop: z.boolean().optional().describe('Whether the track repeats after reaching `duration`'),
|
|
64
|
-
duration: z.number().describe('Track length in seconds'),
|
|
65
|
-
})
|
|
66
|
-
.describe('A deterministic keyframed timeline driving one allowlisted property');
|
|
67
|
-
export type PropertyTrack = z.infer<typeof PropertyTrackSchema>;
|
|
68
|
-
|
|
69
35
|
export const SceneAnimationSchema = z
|
|
70
36
|
.object({
|
|
71
|
-
|
|
72
|
-
parameters: AnimParameterOverridesSchema.optional().describe(
|
|
73
|
-
'Initial parameter values for the animation graph',
|
|
74
|
-
),
|
|
75
|
-
autoplay: z
|
|
76
|
-
.string()
|
|
77
|
-
.optional()
|
|
78
|
-
.describe('Clip name to play automatically on load (when no animGraph is set)'),
|
|
37
|
+
autoplay: z.string().optional().describe('Clip name to play automatically on load'),
|
|
79
38
|
loop: z.boolean().optional().describe('Whether the autoplay clip should loop'),
|
|
80
39
|
clipAliases: z
|
|
81
40
|
.record(z.string(), z.string())
|
|
82
41
|
.optional()
|
|
83
|
-
.describe('Map raw GLTF clip names to clean names
|
|
84
|
-
tracks: z
|
|
85
|
-
.array(PropertyTrackSchema)
|
|
86
|
-
.optional()
|
|
87
|
-
.describe(
|
|
88
|
-
'Deterministic keyframed property timelines (data, not behavior — pure function of ' +
|
|
89
|
-
'time; state-dependent motion stays a GameComponent). Runs in the animation phase, ' +
|
|
90
|
-
'honors pause/timeScale.',
|
|
91
|
-
),
|
|
42
|
+
.describe('Map raw GLTF clip names to clean names'),
|
|
92
43
|
})
|
|
93
44
|
.describe('Animation playback configuration');
|
|
94
45
|
|
|
@@ -13,6 +13,15 @@ import { SceneShadowOverrideSchema, SceneShadowSchema } from './shadow';
|
|
|
13
13
|
import { SceneSplineOverrideSchema, SceneSplineSchema } from './spline';
|
|
14
14
|
import { TransformSchema, Vec3Schema } from './tuples';
|
|
15
15
|
|
|
16
|
+
const BoneAttachmentSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
target: z.string().describe('Entity ID whose animated skeleton owns the target bone'),
|
|
19
|
+
bone: z.string().describe('Exact name of the target bone or socket Object3D'),
|
|
20
|
+
})
|
|
21
|
+
.describe(
|
|
22
|
+
'Attach this entity to a named bone/socket after the complete scene has loaded. The entity transform becomes its local attachment offset',
|
|
23
|
+
);
|
|
24
|
+
|
|
16
25
|
/**
|
|
17
26
|
* User-defined ECS component data. Validated at runtime against ComponentRegistry.
|
|
18
27
|
*
|
|
@@ -65,6 +74,9 @@ const SharedEntityFields = {
|
|
|
65
74
|
.optional()
|
|
66
75
|
.describe('Path to shared .mat.json material asset. Mutually exclusive with inline material'),
|
|
67
76
|
transform: TransformSchema.optional().describe('Local transform relative to parent'),
|
|
77
|
+
boneAttachment: BoneAttachmentSchema.optional().describe(
|
|
78
|
+
'Animated bone/socket attachment. Use transform for the local position, rotation, and scale offset',
|
|
79
|
+
),
|
|
68
80
|
pivot: Vec3Schema.optional().describe(
|
|
69
81
|
'Local-space pivot point for rotation/scale. [0,0,0] = geometry center',
|
|
70
82
|
),
|
|
@@ -165,5 +177,13 @@ export const SceneEntitySchema: z.ZodType<SceneEntity> = z.lazy(() =>
|
|
|
165
177
|
path: ['material'],
|
|
166
178
|
});
|
|
167
179
|
}
|
|
180
|
+
if (entity.boneAttachment !== undefined && entity.physics !== undefined) {
|
|
181
|
+
ctx.addIssue({
|
|
182
|
+
code: z.ZodIssueCode.custom,
|
|
183
|
+
message:
|
|
184
|
+
'A bone-attached entity cannot declare physics. Attachments follow an animated transform; add gameplay collision to a separate entity.',
|
|
185
|
+
path: ['boneAttachment'],
|
|
186
|
+
});
|
|
187
|
+
}
|
|
168
188
|
}),
|
|
169
189
|
) as z.ZodType<SceneEntity>;
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
// Zod schemas
|
|
2
2
|
|
|
3
|
-
export type {
|
|
4
|
-
|
|
5
|
-
PropertyTrackKeyframe,
|
|
6
|
-
PropertyTrackTarget,
|
|
7
|
-
SceneAnimation,
|
|
8
|
-
} from './animation';
|
|
9
|
-
export {
|
|
10
|
-
PropertyTrackKeyframeSchema,
|
|
11
|
-
PropertyTrackSchema,
|
|
12
|
-
PropertyTrackTargetSchema,
|
|
13
|
-
SceneAnimationSchema,
|
|
14
|
-
} from './animation';
|
|
3
|
+
export type { SceneAnimation } from './animation';
|
|
4
|
+
export { SceneAnimationSchema } from './animation';
|
|
15
5
|
export type { SceneAudio } from './audio';
|
|
16
6
|
export { SceneAudioSchema } from './audio';
|
|
17
7
|
export type { SceneCamera } from './camera';
|
|
@@ -59,37 +49,3 @@ export { SceneSplineOverrideSchema, SceneSplineSchema } from './spline';
|
|
|
59
49
|
// Inferred types
|
|
60
50
|
export type { Quat, SceneTransform, Vec3 } from './tuples';
|
|
61
51
|
export { QuatSchema, TransformSchema, Vec3Schema } from './tuples';
|
|
62
|
-
// UI scene-graph schema (editable React UI in the scene graph)
|
|
63
|
-
export type {
|
|
64
|
-
UIAnimatableProperty,
|
|
65
|
-
UIAnimation,
|
|
66
|
-
UIBinding,
|
|
67
|
-
UIEasing,
|
|
68
|
-
UIKeyframe,
|
|
69
|
-
UILayout,
|
|
70
|
-
UINode,
|
|
71
|
-
UINodeKind,
|
|
72
|
-
UIPrefabFile,
|
|
73
|
-
UIRoot,
|
|
74
|
-
UIStyle,
|
|
75
|
-
UITheme,
|
|
76
|
-
UIThemeFile,
|
|
77
|
-
UITrack,
|
|
78
|
-
} from './ui';
|
|
79
|
-
export {
|
|
80
|
-
UIAnimatablePropertySchema,
|
|
81
|
-
UIAnimationSchema,
|
|
82
|
-
UIBindingSchema,
|
|
83
|
-
UIEasingSchema,
|
|
84
|
-
UIKeyframeSchema,
|
|
85
|
-
UILayoutSchema,
|
|
86
|
-
UINodeKindSchema,
|
|
87
|
-
UINodeSchema,
|
|
88
|
-
UIPrefabFileSchema,
|
|
89
|
-
UIRootSchema,
|
|
90
|
-
UIStateStylesSchema,
|
|
91
|
-
UIStyleSchema,
|
|
92
|
-
UIThemeFileSchema,
|
|
93
|
-
UIThemeSchema,
|
|
94
|
-
UITrackSchema,
|
|
95
|
-
} from './ui';
|
|
@@ -3,12 +3,27 @@ import { z } from 'zod';
|
|
|
3
3
|
const SceneLightBase = 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
|
-
intensity: z
|
|
6
|
+
intensity: z
|
|
7
|
+
.number()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe(
|
|
10
|
+
'Native Three.js light intensity. Point and spot lights use candela with inverse-square ' +
|
|
11
|
+
'falloff (a value near 1 is candle-scale); area lights use nit; directional and ' +
|
|
12
|
+
'hemisphere lights use Three.js scene-light intensity units.',
|
|
13
|
+
),
|
|
7
14
|
groundColor: z.string().optional().describe('Ground color for hemisphere lights'),
|
|
8
15
|
distance: z
|
|
9
16
|
.number()
|
|
10
17
|
.optional()
|
|
11
18
|
.describe('Maximum range of the light. 0 = infinite (point/spot only)'),
|
|
19
|
+
decay: z
|
|
20
|
+
.number()
|
|
21
|
+
.nonnegative()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe(
|
|
24
|
+
'Native Three.js distance-decay exponent for point/spot lights. Default 2 gives ' +
|
|
25
|
+
'physically correct inverse-square falloff.',
|
|
26
|
+
),
|
|
12
27
|
angle: z.number().optional().describe('Spotlight cone angle in radians (spot only)'),
|
|
13
28
|
penumbra: z.number().optional().describe('Spotlight penumbra softness 0–1 (spot only)'),
|
|
14
29
|
width: z
|
|
@@ -1,103 +1,108 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
flatShading: z.boolean().optional().describe('Use flat (faceted) shading instead of smooth'),
|
|
3
|
+
const SceneMaterialObjectSchema = z.object({
|
|
4
|
+
type: z
|
|
5
|
+
.enum(['standard', 'physical', 'basic', 'toon', 'custom'])
|
|
6
|
+
.describe('Material shading model. "custom" resolves `def` against the material registry'),
|
|
7
|
+
def: z
|
|
8
|
+
.string()
|
|
9
|
+
.optional()
|
|
10
|
+
.describe('Registered custom-material name (required when type is "custom")'),
|
|
11
|
+
uniforms: z
|
|
12
|
+
.record(z.string(), z.union([z.number(), z.string(), z.boolean(), z.array(z.number())]))
|
|
13
|
+
.optional()
|
|
14
|
+
.describe('Authored uniform values for a custom material, keyed by uniform name'),
|
|
15
|
+
color: z.string().optional().describe('Base color as CSS hex string (e.g. "#ff0000")'),
|
|
16
|
+
metalness: z.number().optional().describe('Metalness factor 0–1 (standard/physical only)'),
|
|
17
|
+
roughness: z.number().optional().describe('Roughness factor 0–1 (standard/physical only)'),
|
|
18
|
+
map: z.string().optional().describe('Path to albedo/diffuse texture image'),
|
|
19
|
+
normalMap: z.string().optional().describe('Path to normal map texture image'),
|
|
20
|
+
emissive: z.string().optional().describe('Emissive color as CSS hex string'),
|
|
21
|
+
emissiveIntensity: z.number().optional().describe('Emissive light intensity multiplier'),
|
|
22
|
+
emissiveMap: z.string().optional().describe('Path to emissive map texture image'),
|
|
23
|
+
aoMap: z.string().optional().describe('Path to ambient occlusion map texture image'),
|
|
24
|
+
lightMap: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe(
|
|
28
|
+
"Path to a baked lightmap texture (uses the mesh's second UV set). Applies the pre-baked GI/shadow result on top of the material.",
|
|
29
|
+
),
|
|
30
|
+
lightMapIntensity: z.number().optional().describe('Lightmap intensity multiplier (default 1).'),
|
|
31
|
+
roughnessMap: z.string().optional().describe('Path to roughness map texture image'),
|
|
32
|
+
metalnessMap: z.string().optional().describe('Path to metalness map texture image'),
|
|
33
|
+
opacity: z.number().optional().describe('Opacity 0–1. Set transparent: true to enable'),
|
|
34
|
+
transparent: z.boolean().optional().describe('Enable alpha blending for this material'),
|
|
35
|
+
side: z.enum(['front', 'back', 'double']).optional().describe('Which face sides to render'),
|
|
36
|
+
flatShading: z.boolean().optional().describe('Use flat (faceted) shading instead of smooth'),
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
// Physical material feature groups (physical only, all optional)
|
|
39
|
+
clearcoat: z
|
|
40
|
+
.object({
|
|
41
|
+
clearcoat: z.number().describe('Clearcoat layer intensity 0–1'),
|
|
42
|
+
clearcoatRoughness: z.number().optional().describe('Clearcoat roughness 0–1'),
|
|
43
|
+
clearcoatMap: z.string().optional().describe('Path to clearcoat intensity map'),
|
|
44
|
+
clearcoatRoughnessMap: z.string().optional().describe('Path to clearcoat roughness map'),
|
|
45
|
+
})
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('Clearcoat layer (physical only)'),
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
transmission: z
|
|
50
|
+
.object({
|
|
51
|
+
transmission: z.number().describe('Transmission intensity 0–1 (glass, liquid)'),
|
|
52
|
+
ior: z.number().optional().describe('Index of refraction (default 1.5)'),
|
|
53
|
+
thickness: z.number().optional().describe('Volume thickness for refraction'),
|
|
54
|
+
attenuationColor: z.string().optional().describe('Color absorbed over distance'),
|
|
55
|
+
attenuationDistance: z
|
|
56
|
+
.number()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe('Distance at which attenuation color takes full effect'),
|
|
59
|
+
transmissionMap: z.string().optional().describe('Path to transmission map'),
|
|
60
|
+
})
|
|
61
|
+
.optional()
|
|
62
|
+
.describe('Light transmission (physical only)'),
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
sheen: z
|
|
65
|
+
.object({
|
|
66
|
+
sheen: z.number().describe('Sheen intensity 0–1 (fabric, velvet)'),
|
|
67
|
+
sheenColor: z.string().optional().describe('Sheen tint color'),
|
|
68
|
+
sheenRoughness: z.number().optional().describe('Sheen roughness 0–1'),
|
|
69
|
+
sheenColorMap: z.string().optional().describe('Path to sheen color map'),
|
|
70
|
+
sheenRoughnessMap: z.string().optional().describe('Path to sheen roughness map'),
|
|
71
|
+
})
|
|
72
|
+
.optional()
|
|
73
|
+
.describe('Sheen layer (physical only)'),
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
})
|
|
90
|
-
.optional()
|
|
91
|
-
.describe('Iridescence thin-film (physical only)'),
|
|
75
|
+
iridescence: z
|
|
76
|
+
.object({
|
|
77
|
+
iridescence: z.number().describe('Iridescence intensity 0–1 (soap bubbles, oil slick)'),
|
|
78
|
+
iridescenceIOR: z.number().optional().describe('Thin-film IOR (default 1.3)'),
|
|
79
|
+
iridescenceThicknessRange: z
|
|
80
|
+
.tuple([z.number(), z.number()])
|
|
81
|
+
.optional()
|
|
82
|
+
.describe('Min/max thin-film thickness in nm'),
|
|
83
|
+
iridescenceMap: z.string().optional().describe('Path to iridescence intensity map'),
|
|
84
|
+
iridescenceThicknessMap: z.string().optional().describe('Path to iridescence thickness map'),
|
|
85
|
+
})
|
|
86
|
+
.optional()
|
|
87
|
+
.describe('Iridescence thin-film (physical only)'),
|
|
92
88
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
displacementMap: z.string().optional().describe('Path to displacement/height map'),
|
|
90
|
+
displacementScale: z.number().optional().describe('Displacement height multiplier'),
|
|
91
|
+
displacementBias: z.number().optional().describe('Displacement offset'),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const SceneMaterialSchema = SceneMaterialObjectSchema.superRefine((material, ctx) => {
|
|
95
|
+
if (material.type === 'custom' && !material.def?.trim()) {
|
|
96
|
+
ctx.addIssue({
|
|
97
|
+
code: 'custom',
|
|
98
|
+
path: ['def'],
|
|
99
|
+
message: 'Custom material requires a non-empty registered definition name',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}).describe('Material appearance properties');
|
|
98
103
|
|
|
99
104
|
/** Partial schema for prefab instance overrides (type not required). */
|
|
100
|
-
export const SceneMaterialOverrideSchema =
|
|
105
|
+
export const SceneMaterialOverrideSchema = SceneMaterialObjectSchema.partial();
|
|
101
106
|
|
|
102
107
|
export type SceneMaterial = z.infer<typeof SceneMaterialSchema>;
|
|
103
108
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { type SceneEntity, SceneEntitySchema } from './entity';
|
|
3
3
|
import { SceneEnvironmentSchema } from './environment';
|
|
4
|
-
import { UIRootSchema } from './ui';
|
|
5
4
|
|
|
6
5
|
export const SceneFileSchema = z
|
|
7
6
|
.object({
|
|
@@ -9,13 +8,8 @@ export const SceneFileSchema = z
|
|
|
9
8
|
name: z.string().describe('Scene display name'),
|
|
10
9
|
environment: SceneEnvironmentSchema.optional().describe('Scene-level environment settings'),
|
|
11
10
|
entities: z.array(SceneEntitySchema).describe('Top-level entities in the scene'),
|
|
12
|
-
// UI roots (HUD/menus) attached to the scene graph as data. Optional and
|
|
13
|
-
// additive — pre-UI scenes (no `ui` field) load unchanged (A4 versioning).
|
|
14
|
-
ui: z
|
|
15
|
-
.array(UIRootSchema)
|
|
16
|
-
.optional()
|
|
17
|
-
.describe('React UI roots (canvases) attached to the scene graph (A1)'),
|
|
18
11
|
})
|
|
12
|
+
.strict()
|
|
19
13
|
.describe('Scene file (.vscn.json)');
|
|
20
14
|
|
|
21
15
|
export type SceneFile = z.infer<typeof SceneFileSchema>;
|
package/src/scene/user-data.ts
CHANGED
|
@@ -36,9 +36,14 @@
|
|
|
36
36
|
*
|
|
37
37
|
* Animation (load-bearing — ED5 disposal contract):
|
|
38
38
|
* - `_animMixer` — THREE.AnimationMixer driving this subtree's clips.
|
|
39
|
-
* - `
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* - `_animClips` — Map<string, AnimationClip> discovered on the GLTF (E5 —
|
|
40
|
+
* lets a GameComponent build its own XState-driven
|
|
41
|
+
* binding via `bindXStateAnimation` over the SAME
|
|
42
|
+
* mixer; see xstate-animation-binding.ts).
|
|
43
|
+
* - `_availableClips` — string[] of clip names discovered on the GLTF
|
|
44
|
+
* (inspector dropdown; same names as `_animClips`' keys).
|
|
45
|
+
* - `_xstateAnimation` — live native XState/Three binding exposed for
|
|
46
|
+
* play-mode inspection; removed when binding disposes.
|
|
42
47
|
*
|
|
43
48
|
* Disposal contract:
|
|
44
49
|
* - `__sharedGeometry` — `true` when a mesh's geometry is shared/cached and MUST
|
|
@@ -74,6 +79,8 @@
|
|
|
74
79
|
* - `editorHelper` — `true` for editor-only helper objects (gizmos, wireframes).
|
|
75
80
|
* - `editorHelperType` — which kind of helper (lights/particles/pivot/navmesh/...).
|
|
76
81
|
* - `editorIcon` — `true` for editor billboard icon sprites.
|
|
82
|
+
* - `skeletonVisible` — per-entity editor preference for its bone overlay.
|
|
83
|
+
* - `skeletonEnabled` — resolved visibility preference on a skeleton helper.
|
|
77
84
|
* - `envObject` — `true` for environment objects (ambient light, etc.).
|
|
78
85
|
* - `splineControlPoint` — index of a spline control-point drag handle.
|
|
79
86
|
* - `vcDirIdx` — view-cube face direction index (0=+X,1=-X,2=+Y,...).
|
|
@@ -81,8 +88,8 @@
|
|
|
81
88
|
|
|
82
89
|
import type * as THREE from 'three';
|
|
83
90
|
import type { ParticleSystem } from 'three.quarks';
|
|
84
|
-
import type {
|
|
85
|
-
import type {
|
|
91
|
+
import type { AnyActor } from 'xstate';
|
|
92
|
+
import type { XStateAnimationBinding } from '../animation/xstate-animation-binding';
|
|
86
93
|
import type { SceneEntity } from './scene-types';
|
|
87
94
|
|
|
88
95
|
/** The kinds of editor helper objects tagged via `editorHelperType`. */
|
|
@@ -94,7 +101,8 @@ export type EditorHelperType =
|
|
|
94
101
|
| 'splines'
|
|
95
102
|
| 'particles'
|
|
96
103
|
| 'pivot'
|
|
97
|
-
| 'navmesh'
|
|
104
|
+
| 'navmesh'
|
|
105
|
+
| 'skeletons';
|
|
98
106
|
|
|
99
107
|
/**
|
|
100
108
|
* Maps each canonical accessor name to its value type. This is the single
|
|
@@ -112,9 +120,9 @@ export interface UserDataSchema {
|
|
|
112
120
|
_camera: THREE.Camera;
|
|
113
121
|
_particleSystem: ParticleSystem;
|
|
114
122
|
_animMixer: THREE.AnimationMixer;
|
|
115
|
-
|
|
116
|
-
_animGraphData: AnimGraphFile;
|
|
123
|
+
_animClips: Map<string, THREE.AnimationClip>;
|
|
117
124
|
_availableClips: string[];
|
|
125
|
+
_xstateAnimation: XStateAnimationBinding & { readonly actor: AnyActor };
|
|
118
126
|
__sharedGeometry: boolean;
|
|
119
127
|
__shadeOrig: THREE.Material | THREE.Material[];
|
|
120
128
|
__shadeUnlit: THREE.Material[];
|
|
@@ -128,6 +136,8 @@ export interface UserDataSchema {
|
|
|
128
136
|
editorHelper: boolean;
|
|
129
137
|
editorHelperType: EditorHelperType;
|
|
130
138
|
editorIcon: boolean;
|
|
139
|
+
skeletonVisible: boolean;
|
|
140
|
+
skeletonEnabled: boolean;
|
|
131
141
|
envObject: boolean;
|
|
132
142
|
vcDirIdx: number;
|
|
133
143
|
}
|
|
@@ -152,9 +162,9 @@ export const UserDataKeys = {
|
|
|
152
162
|
_camera: '_camera',
|
|
153
163
|
_particleSystem: '_particleSystem',
|
|
154
164
|
_animMixer: '_animMixer',
|
|
155
|
-
|
|
156
|
-
_animGraphData: '_animGraphData',
|
|
165
|
+
_animClips: '_animClips',
|
|
157
166
|
_availableClips: '_availableClips',
|
|
167
|
+
_xstateAnimation: '_xstateAnimation',
|
|
158
168
|
__sharedGeometry: '__sharedGeometry',
|
|
159
169
|
__shadeOrig: '__shadeOrig',
|
|
160
170
|
__shadeUnlit: '__shadeUnlit',
|
|
@@ -168,6 +178,8 @@ export const UserDataKeys = {
|
|
|
168
178
|
editorHelper: 'editorHelper',
|
|
169
179
|
editorHelperType: 'editorHelperType',
|
|
170
180
|
editorIcon: 'editorIcon',
|
|
181
|
+
skeletonVisible: 'skeletonVisible',
|
|
182
|
+
skeletonEnabled: 'skeletonEnabled',
|
|
171
183
|
envObject: 'envObject',
|
|
172
184
|
vcDirIdx: 'vcDirIdx',
|
|
173
185
|
} as const satisfies Record<UserDataKey, string>;
|
|
@@ -74,7 +74,7 @@ export function applyRendererSettings(
|
|
|
74
74
|
* `opts` is additive (T6.1 slice 1, COMPOSITION-DESIGN.md D5 §1/§4): omitted
|
|
75
75
|
* entirely, construction is byte-identical to before (no `alpha`/
|
|
76
76
|
* `preserveDrawingBuffer` keys at all) — the legacy single-canvas host never
|
|
77
|
-
* passes it. The
|
|
77
|
+
* passes it. The roots path passes `alpha:true` for every stacked canvas
|
|
78
78
|
* above the bottom one (so its clear-alpha-0 shows the layer below through
|
|
79
79
|
* it) and `preserveDrawingBuffer:true` for every stacked canvas (the
|
|
80
80
|
* recorded capture-tier cost, paid once here rather than re-derived later).
|
|
@@ -86,6 +86,11 @@ export function createHostRenderer(
|
|
|
86
86
|
rendering?: RenderScope,
|
|
87
87
|
opts?: { alpha?: boolean; preserveDrawingBuffer?: boolean },
|
|
88
88
|
): THREE.WebGLRenderer {
|
|
89
|
+
// A newly-shown editor tab can report 0x0 for one layout frame. WebGL render
|
|
90
|
+
// targets cannot have zero-sized attachments, so boot at the smallest valid
|
|
91
|
+
// backing size and let the normal resize path apply the real dimensions.
|
|
92
|
+
const safeWidth = Math.max(1, width);
|
|
93
|
+
const safeHeight = Math.max(1, height);
|
|
89
94
|
const settings = resolveRenderSettings(rendering);
|
|
90
95
|
const renderer = new THREE.WebGLRenderer({
|
|
91
96
|
canvas,
|
|
@@ -94,7 +99,7 @@ export function createHostRenderer(
|
|
|
94
99
|
...(opts?.alpha ? { alpha: true } : {}),
|
|
95
100
|
...(opts?.preserveDrawingBuffer ? { preserveDrawingBuffer: true } : {}),
|
|
96
101
|
});
|
|
97
|
-
renderer.setSize(
|
|
102
|
+
renderer.setSize(safeWidth, safeHeight);
|
|
98
103
|
applyRendererSettings(renderer, rendering);
|
|
99
104
|
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
100
105
|
renderer.toneMapping = toneMappingModes[DEFAULTS.toneMapping.mode] ?? THREE.ACESFilmicToneMapping;
|
|
@@ -112,12 +117,14 @@ export function createSceneView(
|
|
|
112
117
|
width = window.innerWidth,
|
|
113
118
|
height = window.innerHeight,
|
|
114
119
|
): { scene: THREE.Scene; camera: THREE.PerspectiveCamera; composer: EffectComposer } {
|
|
120
|
+
const safeWidth = Math.max(1, width);
|
|
121
|
+
const safeHeight = Math.max(1, height);
|
|
115
122
|
const scene = new THREE.Scene();
|
|
116
123
|
scene.background = new THREE.Color(0x1a1a2e);
|
|
117
124
|
|
|
118
125
|
const camera = new THREE.PerspectiveCamera(
|
|
119
126
|
DEFAULTS.camera.fov,
|
|
120
|
-
|
|
127
|
+
safeWidth / safeHeight,
|
|
121
128
|
DEFAULTS.camera.near,
|
|
122
129
|
DEFAULTS.camera.far,
|
|
123
130
|
);
|