@skenora/scene-plan 0.1.2
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 +114 -0
- package/dist/check/check-scene-input.d.ts +38 -0
- package/dist/check/check-scene-input.d.ts.map +1 -0
- package/dist/check/check-scene-input.js +209 -0
- package/dist/check/check-scene-input.js.map +1 -0
- package/dist/compilation/compile.d.ts +4 -0
- package/dist/compilation/compile.d.ts.map +1 -0
- package/dist/compilation/compile.js +498 -0
- package/dist/compilation/compile.js.map +1 -0
- package/dist/description/example-recipes.d.ts +66 -0
- package/dist/description/example-recipes.d.ts.map +1 -0
- package/dist/description/example-recipes.js +1097 -0
- package/dist/description/example-recipes.js.map +1 -0
- package/dist/description/examples.d.ts +18 -0
- package/dist/description/examples.d.ts.map +1 -0
- package/dist/description/examples.js +58 -0
- package/dist/description/examples.js.map +1 -0
- package/dist/description/information.d.ts +415 -0
- package/dist/description/information.d.ts.map +1 -0
- package/dist/description/information.js +980 -0
- package/dist/description/information.js.map +1 -0
- package/dist/description/recipes.d.ts +20 -0
- package/dist/description/recipes.d.ts.map +1 -0
- package/dist/description/recipes.js +163 -0
- package/dist/description/recipes.js.map +1 -0
- package/dist/description/scene-fields.d.ts +11 -0
- package/dist/description/scene-fields.d.ts.map +1 -0
- package/dist/description/scene-fields.js +471 -0
- package/dist/description/scene-fields.js.map +1 -0
- package/dist/diagnostics/diagnostics.d.ts +79 -0
- package/dist/diagnostics/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics/diagnostics.js +91 -0
- package/dist/diagnostics/diagnostics.js.map +1 -0
- package/dist/generated/example-gallery.d.ts +2897 -0
- package/dist/generated/example-gallery.d.ts.map +1 -0
- package/dist/generated/example-gallery.js +3290 -0
- package/dist/generated/example-gallery.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/model/json.d.ts +14 -0
- package/dist/model/json.d.ts.map +1 -0
- package/dist/model/json.js +60 -0
- package/dist/model/json.js.map +1 -0
- package/dist/model/types.d.ts +329 -0
- package/dist/model/types.d.ts.map +1 -0
- package/dist/model/types.js +5 -0
- package/dist/model/types.js.map +1 -0
- package/dist/normalization/normalize.d.ts +7 -0
- package/dist/normalization/normalize.d.ts.map +1 -0
- package/dist/normalization/normalize.js +145 -0
- package/dist/normalization/normalize.js.map +1 -0
- package/dist/parsing/parse.d.ts +5 -0
- package/dist/parsing/parse.d.ts.map +1 -0
- package/dist/parsing/parse.js +330 -0
- package/dist/parsing/parse.js.map +1 -0
- package/dist/patch/patch.d.ts +6 -0
- package/dist/patch/patch.d.ts.map +1 -0
- package/dist/patch/patch.js +319 -0
- package/dist/patch/patch.js.map +1 -0
- package/dist/validation/validate.d.ts +16 -0
- package/dist/validation/validate.d.ts.map +1 -0
- package/dist/validation/validate.js +704 -0
- package/dist/validation/validate.js.map +1 -0
- package/package.json +31 -0
|
@@ -0,0 +1,980 @@
|
|
|
1
|
+
import { createEmptySceneDocument, describeOwnedPbrCapability, fieldRuleToJsonSchema, getAuthoredMaterialJsonSchema, getRuntimeQualityPolicyJsonSchema, getMaterialExecutionPolicyJsonSchema, describeMaterialEffects, describeMaterialEffectParameters, describeMaterialAnimation, describeMaterialProgramCapability, describeMaterialProgramV2Capability, describeTextureProgramCapability, describeRenderGraphCapability, describeSimulationProgramCapability, describeProceduralGeometryCapability, getMaterialProgramJsonSchema, getTextureProgramJsonSchema, getRenderGraphJsonSchema, getSimulationProgramJsonSchema, getProceduralGeometryProgramJsonSchema, SCENE_CAPABILITY_VERSIONS, TEXTURE_REFERENCE_RULE, } from "@skenora/contracts";
|
|
2
|
+
import { createBuiltInFlowNodeRegistry } from "@skenora/flow";
|
|
3
|
+
import { SCENE_BLUEPRINT_SCHEMA, SCENE_PLAN_VERSION, SCENE_PLAN_DEFAULTS_PROFILE, } from "../model/types.js";
|
|
4
|
+
import { applySceneRecipe, describeSceneRecipes } from "./recipes.js";
|
|
5
|
+
import { describeSkenoraExampleRecipes } from "./example-recipes.js";
|
|
6
|
+
import { BLUEPRINT_BINDING_RULE, BLUEPRINT_CAMERA_PATH_RULE, BLUEPRINT_ENTITY_RULES, BLUEPRINT_LIGHT_RULE, BLUEPRINT_RESOURCE_RULE, BLUEPRINT_SETTING_RULES, BLUEPRINT_TEXTURE_ANIMATION_RULE, BLUEPRINT_TRANSFORM_RULE, } from "./scene-fields.js";
|
|
7
|
+
export const SCENE_INFORMATION_VERSION = 1;
|
|
8
|
+
/** Detached data; safe to serialize without importing Babylon or starting a host. */
|
|
9
|
+
export function describeSceneCapabilities(options = {}) {
|
|
10
|
+
const descriptions = [
|
|
11
|
+
describeOwnedPbrCapability(),
|
|
12
|
+
...describeMaterialEffects(),
|
|
13
|
+
describeMaterialAnimation(),
|
|
14
|
+
describeMaterialProgramCapability(),
|
|
15
|
+
describeMaterialProgramV2Capability(),
|
|
16
|
+
describeTextureProgramCapability(),
|
|
17
|
+
describeRenderGraphCapability(),
|
|
18
|
+
describeSimulationProgramCapability(),
|
|
19
|
+
describeProceduralGeometryCapability(),
|
|
20
|
+
];
|
|
21
|
+
const add = (id, domain, rule, targets, semantics, requirements = []) => {
|
|
22
|
+
descriptions.push({
|
|
23
|
+
id,
|
|
24
|
+
version: SCENE_CAPABILITY_VERSIONS[id],
|
|
25
|
+
domain,
|
|
26
|
+
description: rule.description,
|
|
27
|
+
inputSchema: fieldRuleToJsonSchema(rule),
|
|
28
|
+
targets,
|
|
29
|
+
semantics,
|
|
30
|
+
requirements,
|
|
31
|
+
diagnostics: [
|
|
32
|
+
{
|
|
33
|
+
code: "unavailable-scene-capability",
|
|
34
|
+
repair: DIAGNOSTIC_REPAIRS["unavailable-scene-capability"],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
for (const [kind, rule] of Object.entries(BLUEPRINT_ENTITY_RULES)) {
|
|
40
|
+
if (kind === "procedural")
|
|
41
|
+
continue;
|
|
42
|
+
add(`skenora.entity.${kind}`, "entities", rule, [kind], [
|
|
43
|
+
"IDs are stable within a Blueprint. parentId references another entity; cycles are rejected.",
|
|
44
|
+
"Coordinates are local, Y-up, in meters. The runtime uses its existing left-handed scene; imported glTF conversion stays inside the importer.",
|
|
45
|
+
kind === "group"
|
|
46
|
+
? "A group without sourceNode is authored. Imported source groups remain borrowed from their model asset."
|
|
47
|
+
: "A capability description does not provide model bounds, texture bytes, or visual approval.",
|
|
48
|
+
], kind === "model"
|
|
49
|
+
? [
|
|
50
|
+
"A host-supplied glTF/GLB logical resource binding and permitted resource provider.",
|
|
51
|
+
]
|
|
52
|
+
: kind === "particle"
|
|
53
|
+
? [
|
|
54
|
+
"Provide textureAssetRef and its host texture binding for visible particles; no default image is downloaded.",
|
|
55
|
+
]
|
|
56
|
+
: kind === "annotation"
|
|
57
|
+
? [
|
|
58
|
+
"Renderer.projectAnnotations() returns render-pixel coordinates and visibility. The host presents text and converts render pixels to its layout coordinates.",
|
|
59
|
+
]
|
|
60
|
+
: []);
|
|
61
|
+
}
|
|
62
|
+
for (const [key, rule] of Object.entries(BLUEPRINT_SETTING_RULES)) {
|
|
63
|
+
add(`skenora.scene.${key === "postProcess" ? "post-process" : key}`, "presentation", rule, ["scene"], [
|
|
64
|
+
"Omitted fields use scene-v1 native defaults. Explicit values are preserved unless a separate host execution policy limits them.",
|
|
65
|
+
"Resource declarations do not enable a network provider. The host owns resource access.",
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
add("skenora.scene.lights", "presentation", BLUEPRINT_LIGHT_RULE, ["scene"], [
|
|
69
|
+
"Omitted lights use the explicit default light. An empty array creates no direct lights.",
|
|
70
|
+
]);
|
|
71
|
+
add("skenora.animation.texture", "animation", BLUEPRINT_TEXTURE_ANIMATION_RULE, ["material texture"], [
|
|
72
|
+
"Targets are authored material IDs; a shared texture is updated once, independent instances receive the same sample.",
|
|
73
|
+
"Texture presence and compatibility are checked at runtime.",
|
|
74
|
+
]);
|
|
75
|
+
add("skenora.camera.path", "animation", BLUEPRINT_CAMERA_PATH_RULE, ["viewport camera"], ["Playback is transient and does not write an Editor history entry."]);
|
|
76
|
+
return descriptions.filter((item) => (!options.domains || options.domains.includes(item.domain)) &&
|
|
77
|
+
(!options.capabilityIds || options.capabilityIds.includes(item.id)));
|
|
78
|
+
}
|
|
79
|
+
const text = { type: "string" };
|
|
80
|
+
const id = {
|
|
81
|
+
type: "string",
|
|
82
|
+
pattern: "^[A-Za-z][A-Za-z0-9._:-]{0,127}$",
|
|
83
|
+
};
|
|
84
|
+
const array = (items) => ({
|
|
85
|
+
type: "array",
|
|
86
|
+
items,
|
|
87
|
+
default: [],
|
|
88
|
+
});
|
|
89
|
+
const object = (properties, required = [], closed = false) => ({
|
|
90
|
+
type: "object",
|
|
91
|
+
properties,
|
|
92
|
+
required,
|
|
93
|
+
additionalProperties: !closed,
|
|
94
|
+
});
|
|
95
|
+
/** Syntax contract only. Reference, hierarchy, resource, device and host checks remain mandatory. */
|
|
96
|
+
export function getSceneBlueprintJsonSchema(options = {}) {
|
|
97
|
+
const entitySchemas = Object.entries(BLUEPRINT_ENTITY_RULES).map(([kind, rule]) => {
|
|
98
|
+
const schema = fieldRuleToJsonSchema(rule);
|
|
99
|
+
const properties = schema.properties;
|
|
100
|
+
properties.id = { ...properties.id, ...id };
|
|
101
|
+
properties.kind = { const: kind };
|
|
102
|
+
properties.transform = getTransformSchema(options.normalized ?? false);
|
|
103
|
+
if (kind === "procedural")
|
|
104
|
+
properties.program = getProceduralGeometryProgramJsonSchema();
|
|
105
|
+
if (options.normalized)
|
|
106
|
+
schema.required = [
|
|
107
|
+
...schema.required,
|
|
108
|
+
"name",
|
|
109
|
+
"parentId",
|
|
110
|
+
"enabled",
|
|
111
|
+
"visible",
|
|
112
|
+
"transform",
|
|
113
|
+
];
|
|
114
|
+
return schema;
|
|
115
|
+
});
|
|
116
|
+
const legacyMaterial = object({ id }, ["id"]);
|
|
117
|
+
legacyMaterial.not = { required: ["creation"] };
|
|
118
|
+
legacyMaterial.description =
|
|
119
|
+
"Legacy imported-material override; use owned creation for a typed new surface. Native material validation still applies.";
|
|
120
|
+
const flow = object({
|
|
121
|
+
schema: { const: "skenora.flow" },
|
|
122
|
+
version: { const: 1 },
|
|
123
|
+
id,
|
|
124
|
+
name: text,
|
|
125
|
+
enabled: { type: "boolean" },
|
|
126
|
+
concurrency: { enum: ["parallel", "restart", "drop"] },
|
|
127
|
+
errorPolicy: { enum: ["stop", "continue"] },
|
|
128
|
+
nodes: array(object({
|
|
129
|
+
id,
|
|
130
|
+
type: text,
|
|
131
|
+
name: text,
|
|
132
|
+
enabled: { type: "boolean" },
|
|
133
|
+
config: { type: "object" },
|
|
134
|
+
position: object({ x: { type: "number" }, y: { type: "number" } }, [
|
|
135
|
+
"x",
|
|
136
|
+
"y",
|
|
137
|
+
]),
|
|
138
|
+
}, ["id", "type", "config"])),
|
|
139
|
+
edges: array(object({
|
|
140
|
+
id,
|
|
141
|
+
kind: { enum: ["control", "value"] },
|
|
142
|
+
sourceNodeId: id,
|
|
143
|
+
sourcePort: text,
|
|
144
|
+
targetNodeId: id,
|
|
145
|
+
targetPort: text,
|
|
146
|
+
}, [
|
|
147
|
+
"id",
|
|
148
|
+
"kind",
|
|
149
|
+
"sourceNodeId",
|
|
150
|
+
"sourcePort",
|
|
151
|
+
"targetNodeId",
|
|
152
|
+
"targetPort",
|
|
153
|
+
])),
|
|
154
|
+
}, ["schema", "version", "id", "name", "enabled", "nodes", "edges"]);
|
|
155
|
+
const properties = {
|
|
156
|
+
schema: { const: SCENE_BLUEPRINT_SCHEMA },
|
|
157
|
+
version: { const: SCENE_PLAN_VERSION },
|
|
158
|
+
defaultsProfile: {
|
|
159
|
+
const: SCENE_PLAN_DEFAULTS_PROFILE,
|
|
160
|
+
default: SCENE_PLAN_DEFAULTS_PROFILE,
|
|
161
|
+
},
|
|
162
|
+
scene: object({ id, name: text }, ["id", "name"]),
|
|
163
|
+
requires: array(object({ id, version: text, required: { type: "boolean" }, config: {} }, [
|
|
164
|
+
"id",
|
|
165
|
+
"version",
|
|
166
|
+
"required",
|
|
167
|
+
])),
|
|
168
|
+
resources: array(fieldRuleToJsonSchema(BLUEPRINT_RESOURCE_RULE)),
|
|
169
|
+
entities: array({ oneOf: entitySchemas }),
|
|
170
|
+
materials: array({
|
|
171
|
+
oneOf: [getAuthoredMaterialJsonSchema(), legacyMaterial],
|
|
172
|
+
}),
|
|
173
|
+
materialBindings: array(fieldRuleToJsonSchema(BLUEPRINT_BINDING_RULE)),
|
|
174
|
+
textureAnimations: array(fieldRuleToJsonSchema(BLUEPRINT_TEXTURE_ANIMATION_RULE)),
|
|
175
|
+
cameraPaths: array(fieldRuleToJsonSchema(BLUEPRINT_CAMERA_PATH_RULE)),
|
|
176
|
+
lights: {
|
|
177
|
+
type: "array",
|
|
178
|
+
items: fieldRuleToJsonSchema(BLUEPRINT_LIGHT_RULE),
|
|
179
|
+
},
|
|
180
|
+
flows: array(flow),
|
|
181
|
+
initialVariables: { type: "object", default: {} },
|
|
182
|
+
extensions: array(object({
|
|
183
|
+
namespace: text,
|
|
184
|
+
version: { type: "integer" },
|
|
185
|
+
capability: text,
|
|
186
|
+
required: { type: "boolean" },
|
|
187
|
+
data: {},
|
|
188
|
+
}, ["namespace", "version", "capability", "required", "data"])),
|
|
189
|
+
};
|
|
190
|
+
for (const [key, rule] of Object.entries(BLUEPRINT_SETTING_RULES))
|
|
191
|
+
properties[key] = fieldRuleToJsonSchema(rule);
|
|
192
|
+
const required = ["schema", "version", "scene"];
|
|
193
|
+
if (options.normalized)
|
|
194
|
+
required.push("defaultsProfile", "requires", "resources", "entities", "materials", "materialBindings", "textureAnimations", "cameraPaths", "flows", "initialVariables", "extensions");
|
|
195
|
+
return {
|
|
196
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
197
|
+
$id: `urn:skenora:scene-blueprint:1:${options.normalized ? "normalized" : "input"}`,
|
|
198
|
+
...object(properties, required, true),
|
|
199
|
+
description: "Skenora SceneBlueprint V1. Schema validation does not compile, resolve references, load resources, execute behaviors or validate GPU readiness.",
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function getTransformSchema(normalized) {
|
|
203
|
+
const schema = fieldRuleToJsonSchema(BLUEPRINT_TRANSFORM_RULE);
|
|
204
|
+
schema.not = { required: ["rotationRadians", "rotationQuaternion"] };
|
|
205
|
+
if (normalized) {
|
|
206
|
+
schema.required = ["coordinateSpace", "units", "position", "scaling"];
|
|
207
|
+
schema.oneOf = [
|
|
208
|
+
{ required: ["rotationRadians"] },
|
|
209
|
+
{ required: ["rotationQuaternion"] },
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
return schema;
|
|
213
|
+
}
|
|
214
|
+
const DIAGNOSTIC_REPAIRS = Object.freeze({
|
|
215
|
+
"material-effect-pass-unsupported": "Dissolve version 1 supports opaque main-pass depth only. Remove dissolve or explicitly disable shadows, SSAO, depth-of-field and glow; quality limits do not rewrite this authoring restriction.",
|
|
216
|
+
"invalid-json": "Provide finite, serializable JSON values without functions or cycles.",
|
|
217
|
+
"invalid-input": "Provide a Blueprint object or JSON text.",
|
|
218
|
+
"invalid-schema": "Use schema skenora.scene.blueprint and version 1.",
|
|
219
|
+
"unsupported-version": "Use the protocol and defaults versions in the information bundle.",
|
|
220
|
+
"unknown-field": "Remove the unsupported field or use an explicitly registered extension.",
|
|
221
|
+
"missing-field": "Supply the required field at the reported input path.",
|
|
222
|
+
"invalid-value": "Use the declared type, range, enum and cross-field constraints.",
|
|
223
|
+
"budget-exceeded": "Reduce graph nodes, parameters, segments, array counts, vertices or triangles to fit the advertised host limits.",
|
|
224
|
+
"invalid-id": "Use a stable ID starting with a letter and containing at most 128 allowed characters.",
|
|
225
|
+
"duplicate-id": "Give each declaration a unique ID within its domain.",
|
|
226
|
+
"missing-reference": "Declare the referenced logical entity, material or resource, or correct its ID.",
|
|
227
|
+
"hierarchy-cycle": "Remove the cyclic parent relationship.",
|
|
228
|
+
"invalid-transform": "Use local meters and either Euler radians or a unit quaternion.",
|
|
229
|
+
"invalid-quaternion": "Supply all four components with unit length.",
|
|
230
|
+
"missing-resource-binding": "Ask the host for an authorized logical resource binding; do not invent a URL.",
|
|
231
|
+
"resource-kind-mismatch": "Choose a resource of the required kind.",
|
|
232
|
+
"resource-integrity-mismatch": "Use the host-supplied resource revision and integrity metadata.",
|
|
233
|
+
"missing-capability": "Choose a capability ID/version the host reports as available.",
|
|
234
|
+
"unsupported-capability": "Remove or replace the unavailable capability; JSON cannot install it.",
|
|
235
|
+
"unavailable-scene-capability": "The input uses an unavailable, disabled or unconfirmed capability. Replace that feature or ask the host to enable a compatible implementation.",
|
|
236
|
+
"flow-invalid": "Check node types, ports and configuration against the supplied Flow definitions.",
|
|
237
|
+
"flow-target-missing": "Use a declared and supported target ID for the Flow action.",
|
|
238
|
+
"native-document-invalid": "Correct the compiled-domain field indicated by source mappings and native semantic validation.",
|
|
239
|
+
});
|
|
240
|
+
/** Redacts arbitrary plugin messages/details. IDs/paths originate in authoring input. */
|
|
241
|
+
export function toPublicScenePlanDiagnostics(diagnostics) {
|
|
242
|
+
return diagnostics.map((item) => ({
|
|
243
|
+
code: Object.hasOwn(DIAGNOSTIC_REPAIRS, item.code)
|
|
244
|
+
? item.code
|
|
245
|
+
: "scene-plan-failed",
|
|
246
|
+
severity: item.severity,
|
|
247
|
+
stage: item.stage,
|
|
248
|
+
path: [...item.path],
|
|
249
|
+
repair: DIAGNOSTIC_REPAIRS[item.code] ??
|
|
250
|
+
"Review the reported input path using the public contract; ask the host for a sanitized diagnostic if needed.",
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
export function getScenePlanInformation(options = {}) {
|
|
254
|
+
return {
|
|
255
|
+
informationVersion: SCENE_INFORMATION_VERSION,
|
|
256
|
+
protocol: {
|
|
257
|
+
schema: SCENE_BLUEPRINT_SCHEMA,
|
|
258
|
+
version: SCENE_PLAN_VERSION,
|
|
259
|
+
defaultsProfile: SCENE_PLAN_DEFAULTS_PROFILE,
|
|
260
|
+
},
|
|
261
|
+
capabilities: describeSceneCapabilities(options),
|
|
262
|
+
schemas: {
|
|
263
|
+
blueprintInput: getSceneBlueprintJsonSchema(),
|
|
264
|
+
normalizedBlueprint: getSceneBlueprintJsonSchema({ normalized: true }),
|
|
265
|
+
authoredMaterial: getAuthoredMaterialJsonSchema(),
|
|
266
|
+
proceduralGeometryProgram: getProceduralGeometryProgramJsonSchema(),
|
|
267
|
+
materialProgram: getMaterialProgramJsonSchema(),
|
|
268
|
+
textureProgram: getTextureProgramJsonSchema(),
|
|
269
|
+
renderGraph: getRenderGraphJsonSchema(),
|
|
270
|
+
simulationProgram: getSimulationProgramJsonSchema(),
|
|
271
|
+
textureReference: fieldRuleToJsonSchema(TEXTURE_REFERENCE_RULE),
|
|
272
|
+
hostQualityPolicy: getRuntimeQualityPolicyJsonSchema(),
|
|
273
|
+
hostMaterialPolicy: getMaterialExecutionPolicyJsonSchema(),
|
|
274
|
+
},
|
|
275
|
+
nativeDefaults: createEmptySceneDocument(),
|
|
276
|
+
flowNodes: createBuiltInFlowNodeRegistry()
|
|
277
|
+
.list()
|
|
278
|
+
.map(({ type, category, inputs, outputs }) => structuredClone({ type, category, inputs, outputs })),
|
|
279
|
+
diagnostics: { ...DIAGNOSTIC_REPAIRS },
|
|
280
|
+
examples: getSceneBlueprintExamples(),
|
|
281
|
+
repairExamples: getSceneBlueprintRepairExamples(),
|
|
282
|
+
recipes: [...describeSceneRecipes(), ...describeSkenoraExampleRecipes()],
|
|
283
|
+
materialEffectParameters: describeMaterialEffectParameters(),
|
|
284
|
+
resourceMetadata: [
|
|
285
|
+
"logical id",
|
|
286
|
+
"kind",
|
|
287
|
+
"media type",
|
|
288
|
+
"safe label",
|
|
289
|
+
"optional integrity",
|
|
290
|
+
"host-supplied bounds and source material slots",
|
|
291
|
+
],
|
|
292
|
+
boundaries: [
|
|
293
|
+
"Descriptions are not availability. Supply observed capabilityAvailability to compilation when known; runtime checks the features used by the document again.",
|
|
294
|
+
"Only host resource bindings resolve bytes. Do not send private locators, tokens, credentials, arbitrary error messages or provider configuration to a model.",
|
|
295
|
+
"Defaults in JSON Schema are annotations. Skenora normalization/compilation applies the documented defaults.",
|
|
296
|
+
"The normalized Blueprint schema describes normalizeSceneBlueprint output, not the native document; compilation fills native setting and material defaults afterwards.",
|
|
297
|
+
"Material preparation checks resource decoding and a surface program variant, not every render pass or visual quality.",
|
|
298
|
+
"Flow port metadata is not a complete configuration schema. The built-in registry and semantic validator remain authoritative for behavior configuration.",
|
|
299
|
+
"SimulationProgram is a renderer-lab contract, not SceneDocument persistence. Its optional WebGPU runtime is imported from @skenora/runtime/simulation.",
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
export function getSceneBlueprintExamples() {
|
|
304
|
+
const examples = [
|
|
305
|
+
{
|
|
306
|
+
id: "shared-pbr",
|
|
307
|
+
note: "No external assets. One owned material, two entity-scoped bindings. Geometry and camera are explicit.",
|
|
308
|
+
blueprint: {
|
|
309
|
+
schema: SCENE_BLUEPRINT_SCHEMA,
|
|
310
|
+
version: 1,
|
|
311
|
+
scene: { id: "shared-pbr", name: "Shared PBR" },
|
|
312
|
+
entities: [
|
|
313
|
+
{ id: "composition", kind: "group" },
|
|
314
|
+
{
|
|
315
|
+
id: "sphere",
|
|
316
|
+
kind: "primitive",
|
|
317
|
+
primitive: "sphere",
|
|
318
|
+
parentId: "composition",
|
|
319
|
+
transform: { position: { x: -0.8, y: 0.5, z: 0 } },
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "box",
|
|
323
|
+
kind: "primitive",
|
|
324
|
+
primitive: "box",
|
|
325
|
+
parentId: "composition",
|
|
326
|
+
transform: { position: { x: 0.8, y: 0.5, z: 0 } },
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
materials: [
|
|
330
|
+
{
|
|
331
|
+
id: "ceramic",
|
|
332
|
+
creation: { version: 1, family: "pbr", sharing: "shared" },
|
|
333
|
+
baseColor: { r: 0.08, g: 0.32, b: 0.55 },
|
|
334
|
+
metallic: 0,
|
|
335
|
+
roughness: 0.35,
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
materialBindings: [
|
|
339
|
+
{ id: "sphere-ceramic", materialId: "ceramic", entityId: "sphere" },
|
|
340
|
+
{ id: "box-ceramic", materialId: "ceramic", entityId: "box" },
|
|
341
|
+
],
|
|
342
|
+
camera: {
|
|
343
|
+
mode: "orbit",
|
|
344
|
+
position: { x: 3, y: 2.4, z: -5 },
|
|
345
|
+
target: { x: 0, y: 0.5, z: 0 },
|
|
346
|
+
},
|
|
347
|
+
environment: {
|
|
348
|
+
enabled: false,
|
|
349
|
+
background: { mode: "color", color: "#202124" },
|
|
350
|
+
},
|
|
351
|
+
lights: [
|
|
352
|
+
{
|
|
353
|
+
id: "fill",
|
|
354
|
+
type: "hemispheric",
|
|
355
|
+
enabled: true,
|
|
356
|
+
intensity: 1,
|
|
357
|
+
color: { r: 1, g: 1, b: 1 },
|
|
358
|
+
rotation: { x: -Math.PI / 2, y: 0, z: 0 },
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
postProcess: {
|
|
362
|
+
enabled: true,
|
|
363
|
+
antialiasing: "fxaa",
|
|
364
|
+
toneMappingEnabled: true,
|
|
365
|
+
toneMappingType: "aces",
|
|
366
|
+
exposure: 1,
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
];
|
|
371
|
+
const shared = examples[0].blueprint;
|
|
372
|
+
examples.push({
|
|
373
|
+
id: "supplied-model-slot",
|
|
374
|
+
note: "Requires the original two-parts.gltf fixture under logical resource two-parts. Selectors use its declared extras IDs; runtime slot discovery remains authoritative after any asset replacement.",
|
|
375
|
+
blueprint: {
|
|
376
|
+
schema: SCENE_BLUEPRINT_SCHEMA,
|
|
377
|
+
version: 1,
|
|
378
|
+
scene: {
|
|
379
|
+
id: "supplied-model-slot",
|
|
380
|
+
name: "Explicit supplied model slot",
|
|
381
|
+
},
|
|
382
|
+
resources: [
|
|
383
|
+
{
|
|
384
|
+
id: "two-parts",
|
|
385
|
+
kind: "model",
|
|
386
|
+
mediaType: "model/gltf+json",
|
|
387
|
+
label: "Original two-part material fixture",
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
entities: [
|
|
391
|
+
{ id: "composition", kind: "group" },
|
|
392
|
+
{
|
|
393
|
+
id: "model",
|
|
394
|
+
kind: "model",
|
|
395
|
+
parentId: "composition",
|
|
396
|
+
assetRef: "two-parts",
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
materials: [
|
|
400
|
+
{
|
|
401
|
+
id: "replacement",
|
|
402
|
+
creation: { version: 1, family: "pbr" },
|
|
403
|
+
baseColor: { r: 0.1, g: 0.55, b: 0.3 },
|
|
404
|
+
roughness: 0.6,
|
|
405
|
+
doubleSided: true,
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
materialBindings: [
|
|
409
|
+
{
|
|
410
|
+
id: "left-replacement",
|
|
411
|
+
materialId: "replacement",
|
|
412
|
+
entityId: "model",
|
|
413
|
+
target: { nodeId: "left-part", slot: 0 },
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
camera: structuredClone(shared.camera),
|
|
417
|
+
// The legacy default points along +Z. Use upward fill so both sides of
|
|
418
|
+
// this planar fixture receive light without changing scene-v1 defaults.
|
|
419
|
+
lights: structuredClone(shared.lights),
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
examples.push({
|
|
423
|
+
id: "gradient-rim",
|
|
424
|
+
note: "Bounded gradient albedo and view-dependent rim emission on two shared instances. No screen-space glow, external shader code or hidden assets.",
|
|
425
|
+
blueprint: {
|
|
426
|
+
...structuredClone(shared),
|
|
427
|
+
scene: { id: "gradient-rim", name: "Gradient and rim emission" },
|
|
428
|
+
materials: shared.materials.map((material) => ({
|
|
429
|
+
...structuredClone(material),
|
|
430
|
+
effects: {
|
|
431
|
+
version: 1,
|
|
432
|
+
gradient: {
|
|
433
|
+
axis: "y",
|
|
434
|
+
space: "local",
|
|
435
|
+
start: -0.5,
|
|
436
|
+
end: 0.5,
|
|
437
|
+
colorStart: { r: 0.05, g: 0.1, b: 0.6 },
|
|
438
|
+
colorEnd: { r: 0.7, g: 0.1, b: 0.3 },
|
|
439
|
+
strength: 0.9,
|
|
440
|
+
},
|
|
441
|
+
rim: { color: { r: 0.1, g: 0.4, b: 1 }, intensity: 1.5, power: 3 },
|
|
442
|
+
},
|
|
443
|
+
})),
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
examples.push({
|
|
447
|
+
id: "independent-pbr",
|
|
448
|
+
note: "The same definition creates one instance per binding; material-ID operations still affect both. Use distinct definitions for independent authored values.",
|
|
449
|
+
blueprint: {
|
|
450
|
+
...structuredClone(shared),
|
|
451
|
+
scene: { id: "independent-pbr", name: "Independent PBR instances" },
|
|
452
|
+
materials: shared.materials.map((material) => ({
|
|
453
|
+
...structuredClone(material),
|
|
454
|
+
creation: {
|
|
455
|
+
version: 1,
|
|
456
|
+
family: "pbr",
|
|
457
|
+
sharing: "per-binding",
|
|
458
|
+
},
|
|
459
|
+
})),
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
const dissolve = {
|
|
463
|
+
...structuredClone(shared),
|
|
464
|
+
scene: { id: "animated-dissolve", name: "Bounded animated dissolve" },
|
|
465
|
+
materials: [
|
|
466
|
+
{
|
|
467
|
+
id: "ceramic",
|
|
468
|
+
creation: { version: 1, family: "pbr" },
|
|
469
|
+
roughness: 0.4,
|
|
470
|
+
effects: {
|
|
471
|
+
version: 1,
|
|
472
|
+
gradient: {
|
|
473
|
+
start: -0.5,
|
|
474
|
+
end: 0.5,
|
|
475
|
+
colorStart: { r: 0.05, g: 0.1, b: 0.6 },
|
|
476
|
+
colorEnd: { r: 0.7, g: 0.1, b: 0.3 },
|
|
477
|
+
},
|
|
478
|
+
rim: { color: { r: 0.1, g: 0.4, b: 1 }, intensity: 0.5 },
|
|
479
|
+
dissolve: {
|
|
480
|
+
progress: 0,
|
|
481
|
+
scale: 6,
|
|
482
|
+
seed: 7,
|
|
483
|
+
edgeWidth: 0.06,
|
|
484
|
+
edgeColor: { r: 1, g: 0.25, b: 0.03 },
|
|
485
|
+
edgeIntensity: 2,
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
animations: [
|
|
489
|
+
{
|
|
490
|
+
version: 1,
|
|
491
|
+
id: "reveal",
|
|
492
|
+
property: "dissolveProgress",
|
|
493
|
+
from: 1,
|
|
494
|
+
to: 0,
|
|
495
|
+
durationMs: 1800,
|
|
496
|
+
easing: "easeInOut",
|
|
497
|
+
autoStart: true,
|
|
498
|
+
},
|
|
499
|
+
],
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
// These are authored restrictions; the host does not silently disable them.
|
|
503
|
+
postProcess: {
|
|
504
|
+
...structuredClone(shared.postProcess),
|
|
505
|
+
glow: false,
|
|
506
|
+
depthOfField: false,
|
|
507
|
+
ssao: { enabled: false },
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
examples.push({
|
|
511
|
+
id: "animated-dissolve",
|
|
512
|
+
blueprint: dissolve,
|
|
513
|
+
note: "No external assets. Playback reveals both shared surfaces once on the session clock. Opaque cutout only; all shadow/depth-effect/glow passes are explicitly off.",
|
|
514
|
+
});
|
|
515
|
+
const flowAnimation = {
|
|
516
|
+
...structuredClone(dissolve),
|
|
517
|
+
scene: {
|
|
518
|
+
id: "flow-material-animation",
|
|
519
|
+
name: "Flow controlled material animation",
|
|
520
|
+
},
|
|
521
|
+
materials: [
|
|
522
|
+
{
|
|
523
|
+
...structuredClone(dissolve.materials[0]),
|
|
524
|
+
animations: [
|
|
525
|
+
{
|
|
526
|
+
version: 1,
|
|
527
|
+
id: "reveal",
|
|
528
|
+
property: "dissolveProgress",
|
|
529
|
+
from: 1,
|
|
530
|
+
to: 0,
|
|
531
|
+
durationMs: 1800,
|
|
532
|
+
autoStart: false,
|
|
533
|
+
},
|
|
534
|
+
],
|
|
535
|
+
},
|
|
536
|
+
],
|
|
537
|
+
flows: [
|
|
538
|
+
{
|
|
539
|
+
schema: "skenora.flow",
|
|
540
|
+
version: 1,
|
|
541
|
+
id: "reveal-on-ready",
|
|
542
|
+
name: "Reveal on ready",
|
|
543
|
+
enabled: true,
|
|
544
|
+
nodes: [
|
|
545
|
+
{ id: "ready", type: "trigger.scene.ready", config: {} },
|
|
546
|
+
{
|
|
547
|
+
id: "play",
|
|
548
|
+
type: "action.materialAnimation.play",
|
|
549
|
+
config: { materialId: "ceramic", animationId: "reveal" },
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
edges: [
|
|
553
|
+
{
|
|
554
|
+
id: "start-reveal",
|
|
555
|
+
kind: "control",
|
|
556
|
+
sourceNodeId: "ready",
|
|
557
|
+
sourcePort: "next",
|
|
558
|
+
targetNodeId: "play",
|
|
559
|
+
targetPort: "in",
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
},
|
|
563
|
+
],
|
|
564
|
+
};
|
|
565
|
+
examples.push({
|
|
566
|
+
id: "flow-material-animation",
|
|
567
|
+
blueprint: flowAnimation,
|
|
568
|
+
note: "The same declared animation starts through the existing Flow boundary. Requires host Flow enablement; graph cancellation cancels its awaited run.",
|
|
569
|
+
});
|
|
570
|
+
const textured = {
|
|
571
|
+
...structuredClone(shared),
|
|
572
|
+
scene: { id: "textured-pbr", name: "Explicit texture resource" },
|
|
573
|
+
resources: [
|
|
574
|
+
{
|
|
575
|
+
id: "checker",
|
|
576
|
+
kind: "texture",
|
|
577
|
+
mediaType: "image/png",
|
|
578
|
+
label: "Host-supplied color checker",
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
materials: shared.materials.map((material) => ({
|
|
582
|
+
...structuredClone(material),
|
|
583
|
+
textures: { baseColor: { assetId: "checker", gammaSpace: true } },
|
|
584
|
+
})),
|
|
585
|
+
};
|
|
586
|
+
examples.push({
|
|
587
|
+
id: "textured-pbr",
|
|
588
|
+
note: "Requires an authorized checker texture binding. No URL is assumed. The original PNG fixture in examples/materials is suitable; host owns acquisition.",
|
|
589
|
+
blueprint: textured,
|
|
590
|
+
});
|
|
591
|
+
const geometric = {
|
|
592
|
+
schema: SCENE_BLUEPRINT_SCHEMA,
|
|
593
|
+
version: 1,
|
|
594
|
+
scene: { id: "path-composition", name: "Paths and grouped surfaces" },
|
|
595
|
+
entities: [
|
|
596
|
+
{
|
|
597
|
+
id: "composition",
|
|
598
|
+
kind: "group",
|
|
599
|
+
transform: { rotationRadians: { x: 0, y: 0.35, z: 0 } },
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
id: "tube",
|
|
603
|
+
parentId: "composition",
|
|
604
|
+
kind: "shape",
|
|
605
|
+
shape: "tube",
|
|
606
|
+
tubeRadius: 0.04,
|
|
607
|
+
closed: false,
|
|
608
|
+
points: [
|
|
609
|
+
{ x: -1, y: 0.2, z: 0 },
|
|
610
|
+
{ x: 0, y: 1, z: 0 },
|
|
611
|
+
{ x: 1, y: 0.2, z: 0 },
|
|
612
|
+
],
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
id: "wall",
|
|
616
|
+
parentId: "composition",
|
|
617
|
+
kind: "shape",
|
|
618
|
+
shape: "wall",
|
|
619
|
+
wallHeight: 0.3,
|
|
620
|
+
closed: false,
|
|
621
|
+
points: [
|
|
622
|
+
{ x: -1, y: 0, z: 0.5 },
|
|
623
|
+
{ x: 1, y: 0, z: 0.5 },
|
|
624
|
+
],
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
id: "panel",
|
|
628
|
+
parentId: "composition",
|
|
629
|
+
kind: "shape",
|
|
630
|
+
shape: "polygon",
|
|
631
|
+
closed: true,
|
|
632
|
+
points: [
|
|
633
|
+
{ x: -0.8, y: 0, z: -0.4 },
|
|
634
|
+
{ x: 0.8, y: 0, z: -0.4 },
|
|
635
|
+
{ x: 0.8, y: 0, z: 0.3 },
|
|
636
|
+
{ x: -0.8, y: 0, z: 0.3 },
|
|
637
|
+
],
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
id: "guide",
|
|
641
|
+
parentId: "composition",
|
|
642
|
+
kind: "shape",
|
|
643
|
+
shape: "polyline",
|
|
644
|
+
closed: false,
|
|
645
|
+
points: [
|
|
646
|
+
{ x: -1, y: 1.2, z: 0 },
|
|
647
|
+
{ x: 0, y: 1.4, z: 0 },
|
|
648
|
+
{ x: 1, y: 1.2, z: 0 },
|
|
649
|
+
],
|
|
650
|
+
strokeColor: { r: 0.8, g: 0.6, b: 0.2 },
|
|
651
|
+
},
|
|
652
|
+
],
|
|
653
|
+
materials: [
|
|
654
|
+
{
|
|
655
|
+
id: "surface",
|
|
656
|
+
creation: { version: 1, family: "pbr" },
|
|
657
|
+
baseColor: { r: 0.1, g: 0.5, b: 0.3 },
|
|
658
|
+
roughness: 0.4,
|
|
659
|
+
doubleSided: true,
|
|
660
|
+
},
|
|
661
|
+
],
|
|
662
|
+
materialBindings: [
|
|
663
|
+
{ id: "tube-surface", materialId: "surface", entityId: "tube" },
|
|
664
|
+
{ id: "wall-surface", materialId: "surface", entityId: "wall" },
|
|
665
|
+
{
|
|
666
|
+
id: "panel-surface",
|
|
667
|
+
materialId: "surface",
|
|
668
|
+
entityId: "panel",
|
|
669
|
+
target: { nodeId: "panel-fill", slot: 0 },
|
|
670
|
+
},
|
|
671
|
+
],
|
|
672
|
+
};
|
|
673
|
+
examples.push({
|
|
674
|
+
id: "path-composition",
|
|
675
|
+
note: "Tube, wall, polygon fill and a separate polyline composed by one authored group. PBR selects panel-fill explicitly and leaves its line stroke untouched. No new extrusion/instancing engine is required.",
|
|
676
|
+
blueprint: applySceneRecipe(geometric, "studio-v1", {
|
|
677
|
+
bounds: {
|
|
678
|
+
minimum: { x: -1.5, y: 0, z: -0.5 },
|
|
679
|
+
maximum: { x: 1.5, y: 1.5, z: 1 },
|
|
680
|
+
},
|
|
681
|
+
}),
|
|
682
|
+
});
|
|
683
|
+
examples.push({
|
|
684
|
+
id: "texture-motion",
|
|
685
|
+
note: "The host supplies checker. Existing UV animation now follows the same paused render clock as parameter animation; it changes transient UV values, not texture pixels or document history.",
|
|
686
|
+
blueprint: {
|
|
687
|
+
...structuredClone(textured),
|
|
688
|
+
scene: { id: "texture-motion", name: "UV motion" },
|
|
689
|
+
textureAnimations: [
|
|
690
|
+
{
|
|
691
|
+
id: "scroll",
|
|
692
|
+
materialId: "ceramic",
|
|
693
|
+
slot: "baseColor",
|
|
694
|
+
property: "uOffset",
|
|
695
|
+
from: 0,
|
|
696
|
+
to: 1,
|
|
697
|
+
durationMs: 3000,
|
|
698
|
+
autoStart: true,
|
|
699
|
+
loop: true,
|
|
700
|
+
easing: "linear",
|
|
701
|
+
},
|
|
702
|
+
],
|
|
703
|
+
},
|
|
704
|
+
});
|
|
705
|
+
examples.push({
|
|
706
|
+
id: "presentation-atoms",
|
|
707
|
+
note: "Declared checker powers a small particle system. Annotation output contains projected coordinates for the host to present; it does not create a UI. The alternate camera and path are activated explicitly through public Renderer methods.",
|
|
708
|
+
blueprint: {
|
|
709
|
+
...structuredClone(shared),
|
|
710
|
+
scene: { id: "presentation-atoms", name: "Presentation atoms" },
|
|
711
|
+
resources: structuredClone(textured.resources),
|
|
712
|
+
entities: [
|
|
713
|
+
...structuredClone(shared.entities),
|
|
714
|
+
{
|
|
715
|
+
id: "label",
|
|
716
|
+
kind: "annotation",
|
|
717
|
+
parentId: "sphere",
|
|
718
|
+
transform: { position: { x: 0, y: 0.8, z: 0 } },
|
|
719
|
+
properties: { text: "Shared surface", color: "#ffffff", priority: 1 },
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
id: "alternate-camera",
|
|
723
|
+
kind: "camera",
|
|
724
|
+
parentId: "composition",
|
|
725
|
+
transform: { position: { x: 0, y: 0.5, z: -5 } },
|
|
726
|
+
properties: {
|
|
727
|
+
projection: "perspective",
|
|
728
|
+
fov: 0.8,
|
|
729
|
+
minZ: 0.01,
|
|
730
|
+
maxZ: 100,
|
|
731
|
+
},
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
id: "particles",
|
|
735
|
+
kind: "particle",
|
|
736
|
+
parentId: "composition",
|
|
737
|
+
textureAssetRef: "checker",
|
|
738
|
+
transform: { position: { x: 0, y: 0.2, z: 0 } },
|
|
739
|
+
properties: {
|
|
740
|
+
capacity: 128,
|
|
741
|
+
emitRate: 16,
|
|
742
|
+
minLifeTime: 0.4,
|
|
743
|
+
maxLifeTime: 1,
|
|
744
|
+
autoStart: true,
|
|
745
|
+
gravity: { x: 0, y: -0.2, z: 0 },
|
|
746
|
+
},
|
|
747
|
+
},
|
|
748
|
+
],
|
|
749
|
+
cameraPaths: [
|
|
750
|
+
{
|
|
751
|
+
id: "orbit-pass",
|
|
752
|
+
name: "Two-point camera pass",
|
|
753
|
+
durationMs: 3000,
|
|
754
|
+
loop: false,
|
|
755
|
+
interpolation: "linear",
|
|
756
|
+
restoreCameraOnStop: true,
|
|
757
|
+
keyframes: [
|
|
758
|
+
{
|
|
759
|
+
id: "start",
|
|
760
|
+
timeMs: 0,
|
|
761
|
+
position: { x: 3, y: 2.4, z: -5 },
|
|
762
|
+
target: { x: 0, y: 0.5, z: 0 },
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
id: "end",
|
|
766
|
+
timeMs: 3000,
|
|
767
|
+
position: { x: -3, y: 2.4, z: -5 },
|
|
768
|
+
target: { x: 0, y: 0.5, z: 0 },
|
|
769
|
+
easing: "easeInOut",
|
|
770
|
+
},
|
|
771
|
+
],
|
|
772
|
+
},
|
|
773
|
+
],
|
|
774
|
+
},
|
|
775
|
+
});
|
|
776
|
+
examples.push({
|
|
777
|
+
id: "procedural-ai",
|
|
778
|
+
note: "A bounded geometry graph and typed material graph. Both are plain JSON, compile without external resources, and contain no JavaScript or GLSL source.",
|
|
779
|
+
blueprint: {
|
|
780
|
+
schema: SCENE_BLUEPRINT_SCHEMA,
|
|
781
|
+
version: 1,
|
|
782
|
+
scene: { id: "procedural-ai", name: "Procedural AI" },
|
|
783
|
+
requires: [
|
|
784
|
+
{
|
|
785
|
+
id: "skenora.entity.procedural",
|
|
786
|
+
version: "1",
|
|
787
|
+
required: true,
|
|
788
|
+
},
|
|
789
|
+
{ id: "skenora.material.pbr", version: "1", required: true },
|
|
790
|
+
{ id: "skenora.material.program", version: "1", required: true },
|
|
791
|
+
],
|
|
792
|
+
entities: [
|
|
793
|
+
{
|
|
794
|
+
id: "procedural-stage",
|
|
795
|
+
kind: "procedural",
|
|
796
|
+
program: {
|
|
797
|
+
schema: "skenora.geometry.program",
|
|
798
|
+
version: 1,
|
|
799
|
+
output: "stage",
|
|
800
|
+
nodes: [
|
|
801
|
+
{
|
|
802
|
+
id: "platform",
|
|
803
|
+
type: "geometry.cylinder",
|
|
804
|
+
height: 0.25,
|
|
805
|
+
diameterTop: 3.2,
|
|
806
|
+
diameterBottom: 3.5,
|
|
807
|
+
segments: 48,
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
id: "post",
|
|
811
|
+
type: "geometry.box",
|
|
812
|
+
size: { x: 0.18, y: 0.8, z: 0.18 },
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
id: "posts",
|
|
816
|
+
type: "modifier.array-radial",
|
|
817
|
+
input: "post",
|
|
818
|
+
count: 12,
|
|
819
|
+
radius: 1.35,
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
id: "raise-posts",
|
|
823
|
+
type: "modifier.transform",
|
|
824
|
+
input: "posts",
|
|
825
|
+
translation: { x: 0, y: 0.45, z: 0 },
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
id: "stage",
|
|
829
|
+
type: "combine.merge",
|
|
830
|
+
inputs: ["platform", "raise-posts"],
|
|
831
|
+
},
|
|
832
|
+
],
|
|
833
|
+
},
|
|
834
|
+
},
|
|
835
|
+
],
|
|
836
|
+
materials: [
|
|
837
|
+
{
|
|
838
|
+
id: "procedural-surface",
|
|
839
|
+
creation: { version: 1, family: "pbr" },
|
|
840
|
+
roughness: 0.38,
|
|
841
|
+
program: {
|
|
842
|
+
schema: "skenora.material.program",
|
|
843
|
+
version: 1,
|
|
844
|
+
parameters: {
|
|
845
|
+
accent: {
|
|
846
|
+
type: "color",
|
|
847
|
+
value: { r: 0.05, g: 0.72, b: 1.4 },
|
|
848
|
+
},
|
|
849
|
+
glow: { type: "number", value: 0.12, minimum: 0, maximum: 2 },
|
|
850
|
+
},
|
|
851
|
+
nodes: [
|
|
852
|
+
{
|
|
853
|
+
id: "dark",
|
|
854
|
+
type: "value.color",
|
|
855
|
+
value: { r: 0.015, g: 0.025, b: 0.05 },
|
|
856
|
+
},
|
|
857
|
+
{ id: "accent", type: "parameter.color", parameter: "accent" },
|
|
858
|
+
{
|
|
859
|
+
id: "height",
|
|
860
|
+
type: "input.coordinate",
|
|
861
|
+
space: "local",
|
|
862
|
+
axis: "y",
|
|
863
|
+
},
|
|
864
|
+
{ id: "low", type: "value.number", value: -0.15 },
|
|
865
|
+
{ id: "high", type: "value.number", value: 0.9 },
|
|
866
|
+
{
|
|
867
|
+
id: "ramp",
|
|
868
|
+
type: "math.smoothstep",
|
|
869
|
+
edge0: "low",
|
|
870
|
+
edge1: "high",
|
|
871
|
+
input: "height",
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
id: "surface",
|
|
875
|
+
type: "color.mix",
|
|
876
|
+
a: "dark",
|
|
877
|
+
b: "accent",
|
|
878
|
+
factor: "ramp",
|
|
879
|
+
},
|
|
880
|
+
{ id: "glow", type: "parameter.number", parameter: "glow" },
|
|
881
|
+
{
|
|
882
|
+
id: "emission",
|
|
883
|
+
type: "color.scale",
|
|
884
|
+
color: "accent",
|
|
885
|
+
factor: "glow",
|
|
886
|
+
},
|
|
887
|
+
],
|
|
888
|
+
outputs: { baseColor: "surface", emissive: "emission" },
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
],
|
|
892
|
+
materialBindings: [
|
|
893
|
+
{
|
|
894
|
+
id: "procedural-stage-surface",
|
|
895
|
+
materialId: "procedural-surface",
|
|
896
|
+
entityId: "procedural-stage",
|
|
897
|
+
},
|
|
898
|
+
],
|
|
899
|
+
},
|
|
900
|
+
});
|
|
901
|
+
for (const recipe of ["neutral-v1", "studio-v1"]) {
|
|
902
|
+
const input = {
|
|
903
|
+
schema: SCENE_BLUEPRINT_SCHEMA,
|
|
904
|
+
version: 1,
|
|
905
|
+
scene: { id: recipe, name: recipe },
|
|
906
|
+
entities: structuredClone(shared.entities),
|
|
907
|
+
materials: structuredClone(shared.materials),
|
|
908
|
+
materialBindings: structuredClone(shared.materialBindings),
|
|
909
|
+
};
|
|
910
|
+
examples.push({
|
|
911
|
+
id: recipe,
|
|
912
|
+
note: "An expanded recipe using ordinary input, explicit supplied bounds and no environment assets. The host chooses execution quality; matching pixels across devices are not promised.",
|
|
913
|
+
blueprint: applySceneRecipe(input, recipe, {
|
|
914
|
+
bounds: {
|
|
915
|
+
minimum: { x: -1.3, y: 0, z: -0.5 },
|
|
916
|
+
maximum: { x: 1.3, y: 1, z: 0.5 },
|
|
917
|
+
},
|
|
918
|
+
aspectRatio: 1.5,
|
|
919
|
+
}),
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
return examples;
|
|
923
|
+
}
|
|
924
|
+
export function getSceneBlueprintRepairExamples() {
|
|
925
|
+
const base = getSceneBlueprintExamples()[0].blueprint;
|
|
926
|
+
return [
|
|
927
|
+
{
|
|
928
|
+
id: "unsupported-dissolve-depth-pass",
|
|
929
|
+
input: {
|
|
930
|
+
...structuredClone(getSceneBlueprintExamples().find((item) => item.id === "animated-dissolve").blueprint),
|
|
931
|
+
postProcess: { enabled: true, depthOfField: true },
|
|
932
|
+
},
|
|
933
|
+
expected: {
|
|
934
|
+
stage: "compile",
|
|
935
|
+
code: "material-effect-pass-unsupported",
|
|
936
|
+
path: ["postProcess", "depthOfField"],
|
|
937
|
+
},
|
|
938
|
+
repair: "Disable depthOfField explicitly or remove dissolve. Version 1 never renders an inconsistent auxiliary depth mask as a fallback.",
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
id: "unknown-material-field",
|
|
942
|
+
input: {
|
|
943
|
+
...structuredClone(base),
|
|
944
|
+
materials: base.materials.map((material) => ({
|
|
945
|
+
...structuredClone(material),
|
|
946
|
+
shaderSource: "unsupported",
|
|
947
|
+
})),
|
|
948
|
+
},
|
|
949
|
+
expected: {
|
|
950
|
+
stage: "validate",
|
|
951
|
+
code: "unknown-field",
|
|
952
|
+
path: ["materials", 0, "shaderSource"],
|
|
953
|
+
},
|
|
954
|
+
repair: "Remove shaderSource. Use the declared PBR/effect fields; raw shader text is not executable input.",
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
id: "missing-texture-binding",
|
|
958
|
+
input: getSceneBlueprintExamples().find((item) => item.id === "textured-pbr").blueprint,
|
|
959
|
+
expected: {
|
|
960
|
+
stage: "validate",
|
|
961
|
+
code: "missing-resource-binding",
|
|
962
|
+
path: ["resources", 0, "id"],
|
|
963
|
+
},
|
|
964
|
+
repair: "Ask the host for the checker resource binding and compile with that explicit context.",
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
id: "missing-runtime-slot",
|
|
968
|
+
input: {
|
|
969
|
+
...structuredClone(base),
|
|
970
|
+
materialBindings: base.materialBindings.map((binding) => ({
|
|
971
|
+
...binding,
|
|
972
|
+
target: { nodeId: "missing-source-node", slot: 0 },
|
|
973
|
+
})),
|
|
974
|
+
},
|
|
975
|
+
expected: { stage: "runtime", code: "missing-material-slot" },
|
|
976
|
+
repair: "Use describeMaterialSlots(entityId) on the current loaded asset or the host's matching asset metadata.",
|
|
977
|
+
},
|
|
978
|
+
];
|
|
979
|
+
}
|
|
980
|
+
//# sourceMappingURL=information.js.map
|