@forgeax/engine-preview 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.
Files changed (59) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +75 -0
  3. package/assets/canonical-kit/cook-receipt.json +40 -0
  4. package/assets/canonical-kit/sky.hdr +0 -0
  5. package/assets/canonical-kit/sky.hdr.meta.json +19 -0
  6. package/dist/.tsbuildinfo +1 -0
  7. package/dist/__tests__/canonical-rig-owner.test-d.d.ts +1 -0
  8. package/dist/__tests__/consumer.smoke.test.d.ts +1 -0
  9. package/dist/__tests__/contract.test.d.ts +1 -0
  10. package/dist/__tests__/default-runtime.test.d.ts +1 -0
  11. package/dist/__tests__/host.test.d.ts +1 -0
  12. package/dist/__tests__/kit-receipt.test.d.ts +1 -0
  13. package/dist/__tests__/manifest.test.d.ts +1 -0
  14. package/dist/__tests__/material-oracle.test.d.ts +1 -0
  15. package/dist/__tests__/material.test.d.ts +1 -0
  16. package/dist/__tests__/mesh-oracle.test.d.ts +1 -0
  17. package/dist/__tests__/mesh-subject.test.d.ts +1 -0
  18. package/dist/__tests__/mesh.test.d.ts +1 -0
  19. package/dist/__tests__/oracle-status-owner.test-d.d.ts +1 -0
  20. package/dist/__tests__/owner-gate.test.d.ts +1 -0
  21. package/dist/__tests__/preview-contract.test.d.ts +1 -0
  22. package/dist/__tests__/texture-oracle.test.d.ts +1 -0
  23. package/dist/__tests__/texture-payload-class-owner.test-d.d.ts +1 -0
  24. package/dist/__tests__/texture.test.d.ts +1 -0
  25. package/dist/__tests__/vfx-oracle.test.d.ts +1 -0
  26. package/dist/__tests__/vfx-subject.test.d.ts +1 -0
  27. package/dist/__tests__/vfx.test.d.ts +1 -0
  28. package/dist/domains/material.d.ts +50 -0
  29. package/dist/domains/mesh.d.ts +53 -0
  30. package/dist/domains/subject.d.ts +28 -0
  31. package/dist/domains/texture.d.ts +63 -0
  32. package/dist/domains/vfx.d.ts +58 -0
  33. package/dist/evidence/errors.d.ts +14 -0
  34. package/dist/evidence/manifest.d.ts +10 -0
  35. package/dist/evidence/oracle.d.ts +104 -0
  36. package/dist/evidence/report.d.ts +15 -0
  37. package/dist/host/preview-host.d.ts +52 -0
  38. package/dist/host.d.ts +88 -0
  39. package/dist/index.d.ts +15 -0
  40. package/dist/index.mjs +1826 -0
  41. package/dist/index.mjs.map +1 -0
  42. package/dist/kit/canonical.d.ts +16 -0
  43. package/dist/kit/presentation.d.ts +33 -0
  44. package/dist/kit/receipt.d.ts +56 -0
  45. package/dist/kit/receipt.test-fixtures.d.ts +41 -0
  46. package/dist/material.d.ts +45 -0
  47. package/dist/material.mjs +469 -0
  48. package/dist/material.mjs.map +1 -0
  49. package/dist/mesh.d.ts +57 -0
  50. package/dist/mesh.mjs +529 -0
  51. package/dist/mesh.mjs.map +1 -0
  52. package/dist/primitive.d.ts +46 -0
  53. package/dist/texture.d.ts +57 -0
  54. package/dist/texture.mjs +526 -0
  55. package/dist/texture.mjs.map +1 -0
  56. package/dist/vfx.d.ts +55 -0
  57. package/dist/vfx.mjs +630 -0
  58. package/dist/vfx.mjs.map +1 -0
  59. package/package.json +83 -0
@@ -0,0 +1,526 @@
1
+ import { defineToolCapability, defineTool } from '@forgeax/engine-tool-runtime';
2
+ import { defineToolPlugin } from '@forgeax/engine-plugin';
3
+ import { builtinAssetKind } from '@forgeax/engine-runtime';
4
+
5
+ // src/texture.ts
6
+ var previewHostCapability = defineToolCapability("preview.host");
7
+
8
+ // src/domains/subject.ts
9
+ var RESOURCE_PREVIEW_DEFAULT_SIZE = 512;
10
+ var RESOURCE_PREVIEW_MIN_SIZE = 64;
11
+ var RESOURCE_PREVIEW_MAX_SIZE = 4096;
12
+ function isResourcePreviewSize(value) {
13
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= RESOURCE_PREVIEW_MIN_SIZE && value <= RESOURCE_PREVIEW_MAX_SIZE && (value & value - 1) === 0;
14
+ }
15
+ function isRecord(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
18
+ function isString(value) {
19
+ return typeof value === "string" && value.length > 0;
20
+ }
21
+ function isResourcePreviewResult(value) {
22
+ if (!isRecord(value)) return false;
23
+ const subject = value.subject;
24
+ if (!isRecord(subject) || !isString(subject.kind) || !isString(subject.guid) || !isString(subject.digest))
25
+ return false;
26
+ if (!isRecord(value.presentation) || !isString(value.presentation.kind)) return false;
27
+ if (!isRecord(value.recipe) || !isString(value.recipe.schemaVersion) || !isString(value.recipe.recipeDigest))
28
+ return false;
29
+ if (!isRecord(value.oracle) || !isString(value.oracle.status)) return false;
30
+ return Array.isArray(value.artifacts);
31
+ }
32
+ var resourcePreviewResultSchema = {
33
+ parse(value) {
34
+ return isResourcePreviewResult(value) ? { ok: true, value } : {
35
+ ok: false,
36
+ error: "resource preview result must include subject, presentation, recipe, oracle, and artifacts"
37
+ };
38
+ },
39
+ describe: '{"type":"object","required":["subject","presentation","recipe","oracle","artifacts"]}'
40
+ };
41
+ var resourcePreviewArgsSchema = {
42
+ parse(value) {
43
+ if (value === null || typeof value !== "object" || typeof Reflect.get(value, "guid") !== "string") {
44
+ return { ok: false, error: "expected { guid: string }" };
45
+ }
46
+ const guid = Reflect.get(value, "guid");
47
+ if (guid.length === 0) return { ok: false, error: "guid must not be empty" };
48
+ const size = Reflect.get(value, "size");
49
+ if (size === void 0) return { ok: true, value: { guid } };
50
+ return isResourcePreviewSize(size) ? { ok: true, value: { guid, size } } : {
51
+ ok: false,
52
+ error: `size must be a power of two between ${RESOURCE_PREVIEW_MIN_SIZE} and ${RESOURCE_PREVIEW_MAX_SIZE}`
53
+ };
54
+ },
55
+ describe: `{"type":"object","required":["guid"],"properties":{"guid":{"type":"string"},"size":{"type":"integer","minimum":${RESOURCE_PREVIEW_MIN_SIZE},"maximum":${RESOURCE_PREVIEW_MAX_SIZE},"description":"square power-of-two screenshot edge; defaults to ${RESOURCE_PREVIEW_DEFAULT_SIZE}"}}}`
56
+ };
57
+ function subjectDescriptor(kind) {
58
+ return {
59
+ id: `${kind}.preview`,
60
+ title: `Preview ${kind}`,
61
+ summary: `Preview one ${kind} asset in the Engine-owned resource host.`,
62
+ realm: "host",
63
+ argsSchema: resourcePreviewArgsSchema,
64
+ resultSchema: resourcePreviewResultSchema,
65
+ evidence: ["rhi-tape", "profile-capture", "png"]
66
+ };
67
+ }
68
+ function subjectFailure(code, expected, detail) {
69
+ return {
70
+ ok: false,
71
+ error: {
72
+ code,
73
+ expected,
74
+ hint: "Load the requested GUID through AssetRegistry.load(guid, kind) and repair the owner facts before retrying.",
75
+ detail
76
+ }
77
+ };
78
+ }
79
+ function assetLoadFailure(expected, runId, error) {
80
+ const owner = isRecord(error) ? error : void 0;
81
+ const ownerCode = typeof owner?.code === "string" ? owner.code : void 0;
82
+ const ownerExpected = typeof owner?.expected === "string" ? owner.expected : void 0;
83
+ const ownerHint = typeof owner?.hint === "string" ? owner.hint : void 0;
84
+ const ownerDetail = owner?.detail;
85
+ return subjectFailure("resource-preview-subject-invalid", expected, {
86
+ phase: "asset-load",
87
+ runId,
88
+ ...ownerCode === void 0 ? {} : { ownerCode },
89
+ ...ownerExpected === void 0 ? {} : { ownerExpected },
90
+ ...ownerHint === void 0 ? {} : { ownerHint },
91
+ ...ownerDetail === void 0 ? {} : { ownerDetail: toJsonValue(ownerDetail) }
92
+ });
93
+ }
94
+ function toJsonValue(value) {
95
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
96
+ if (typeof value === "number" && Number.isFinite(value)) return value;
97
+ if (Array.isArray(value)) return value.map((entry) => toJsonValue(entry));
98
+ if (isRecord(value)) {
99
+ return Object.fromEntries(
100
+ Object.entries(value).map(([key, entry]) => [key, toJsonValue(entry)])
101
+ );
102
+ }
103
+ return String(value);
104
+ }
105
+ function nativePreviewPlugin(kind, contribution) {
106
+ return defineToolPlugin({ name: `forgeax-preview-${kind}`, apply() {
107
+ } }, [
108
+ contribution
109
+ ]);
110
+ }
111
+
112
+ // src/evidence/oracle.ts
113
+ function passedPreviewOracle(input) {
114
+ return {
115
+ status: "passed",
116
+ subjectBound: true,
117
+ drawCalls: input.drawCalls,
118
+ dispatches: input.dispatches ?? 0,
119
+ rendererHealthy: true,
120
+ detail: { ...input.detail }
121
+ };
122
+ }
123
+ function failedPreviewOracle(input) {
124
+ return {
125
+ status: "failed",
126
+ subjectBound: false,
127
+ drawCalls: input.drawCalls ?? 0,
128
+ dispatches: input.dispatches ?? 0,
129
+ rendererHealthy: input.rendererHealthy ?? false,
130
+ detail: { ...input.detail }
131
+ };
132
+ }
133
+ function firstMismatch(requested, observed) {
134
+ for (const key of Object.keys(requested)) {
135
+ const requestedValue = requested[key];
136
+ const observedValue = observed[key];
137
+ if (Array.isArray(requestedValue) || Array.isArray(observedValue) || typeof requestedValue === "object" || typeof observedValue === "object") {
138
+ if (JSON.stringify(requestedValue) !== JSON.stringify(observedValue)) return key;
139
+ } else if (requestedValue !== observedValue) {
140
+ return key;
141
+ }
142
+ }
143
+ return void 0;
144
+ }
145
+ function identityRecord(input) {
146
+ return Object.fromEntries(
147
+ Object.entries(input).map(([key, value]) => [
148
+ key,
149
+ Array.isArray(value) ? JSON.stringify(value) : String(value)
150
+ ])
151
+ );
152
+ }
153
+ function evaluateIdentityOracle(kind, requestedInput, observedInput, healthy, drawCalls, dispatches, detailOverrides) {
154
+ const requested = identityRecord(requestedInput);
155
+ const observed = identityRecord(observedInput);
156
+ const mismatch = firstMismatch(requested, observed);
157
+ const detail = {
158
+ kind,
159
+ mismatch: mismatch ?? "none",
160
+ requestedSubjectDigest: requested.subjectDigest ?? "unknown",
161
+ observedSubjectDigest: observed.subjectDigest ?? "unknown",
162
+ requestedOwnerDigest: JSON.stringify(requested),
163
+ observedOwnerDigest: JSON.stringify(observed),
164
+ ...detailOverrides
165
+ };
166
+ if (mismatch !== void 0 || !healthy) {
167
+ return failedPreviewOracle({ drawCalls, dispatches, rendererHealthy: healthy, detail });
168
+ }
169
+ return passedPreviewOracle({ drawCalls, dispatches, detail });
170
+ }
171
+ function evaluateTextureOracle(input) {
172
+ const observed = input.observed;
173
+ return evaluateIdentityOracle(
174
+ "texture",
175
+ input.requested,
176
+ input.observed,
177
+ observed.rendererHealthy && observed.drawCalls > 0,
178
+ observed.drawCalls,
179
+ 0,
180
+ {
181
+ rendererHealthy: observed.rendererHealthy,
182
+ payloadClass: observed.payloadClass,
183
+ nonBlackPixels: observed.nonBlackPixels
184
+ }
185
+ );
186
+ }
187
+
188
+ // src/kit/canonical.ts
189
+ function createCanonicalPreviewRecipe(kind) {
190
+ const texture = kind === "texture";
191
+ return {
192
+ schemaVersion: "1.0.0",
193
+ rig: kind === "mesh" ? "asset-mesh" : texture ? "asset-quad" : "handle-sphere",
194
+ environment: texture ? "black" : "engine-canonical",
195
+ skybox: texture ? "none" : "engine-canonical",
196
+ skylight: texture ? "none" : "engine-canonical",
197
+ directionalLight: texture ? "none" : "engine-canonical",
198
+ stage: texture ? "texture-unlit-black" : "neutral-material-checker-unlit",
199
+ camera: texture ? "texture-orthographic" : "bounds-derived",
200
+ recipeDigest: `canonical:${kind}:${"texture-orthographic" }`
201
+ };
202
+ }
203
+ function canonicalPresentation(kind) {
204
+ {
205
+ return {
206
+ kind: "texture-unlit",
207
+ geometry: "aspect-quad",
208
+ checkerboard: "linear-alpha"
209
+ };
210
+ }
211
+ }
212
+
213
+ // src/domains/texture.ts
214
+ function isRecord2(value) {
215
+ return typeof value === "object" && value !== null;
216
+ }
217
+ function requiredString(asset, field) {
218
+ const value = asset[field];
219
+ return typeof value === "string" && value.length > 0 ? value : void 0;
220
+ }
221
+ function requiredNumber(asset, field) {
222
+ const value = asset[field];
223
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
224
+ }
225
+ function isTexturePayloadClass(value) {
226
+ return value === "black" || value === "transparent" || value === "single-channel" || value === "color";
227
+ }
228
+ function classifyPayload(data, format) {
229
+ const bytes = data instanceof Uint8Array || data instanceof Uint8ClampedArray ? [...data] : Array.isArray(data) && data.every((value) => typeof value === "number") ? data : void 0;
230
+ if (bytes === void 0 || bytes.length === 0) return void 0;
231
+ const channels = format?.startsWith("r") && !format.startsWith("rg") ? 1 : 4;
232
+ if (bytes.every((value) => value === 0)) return "black";
233
+ if (channels === 1) return "single-channel";
234
+ const alpha = bytes.filter((_, index) => (index + 1) % channels === 0);
235
+ if (alpha.length > 0 && alpha.every((value) => value === 0)) return "transparent";
236
+ return "color";
237
+ }
238
+ function observationString(observation, field) {
239
+ const value = observation[field];
240
+ return typeof value === "string" && value.length > 0 ? value : void 0;
241
+ }
242
+ function observationDimensions(observation) {
243
+ const value = observation.dimensions;
244
+ if (!Array.isArray(value) || value.length !== 2 || value.some((entry) => typeof entry !== "number" || !Number.isInteger(entry) || entry <= 0))
245
+ return void 0;
246
+ return [value[0], value[1]];
247
+ }
248
+ function observationPayloadClass(observation) {
249
+ const value = observation.payloadClass;
250
+ return isTexturePayloadClass(value) ? value : void 0;
251
+ }
252
+ function inspectTextureSubject(input) {
253
+ if (!isRecord2(input.asset) || input.asset.kind !== "texture") {
254
+ return subjectFailure("resource-preview-kind-mismatch", "a TextureAsset with kind texture", {
255
+ phase: "subject",
256
+ guid: input.guid,
257
+ actualKind: isRecord2(input.asset) && typeof input.asset.kind === "string" ? input.asset.kind : "unknown"
258
+ });
259
+ }
260
+ const asset = input.asset;
261
+ const inlineOwner = asset.ownerFacts;
262
+ const ownerFacts = isRecord2(inlineOwner) ? inlineOwner : input.ownerFacts;
263
+ const readString = (field) => requiredString(asset, field) ?? requiredString(ownerFacts ?? {}, field);
264
+ const subjectDigest = readString("digest") ?? input.digest ?? readString("subjectDigest");
265
+ const boundDigest = readString("boundDigest");
266
+ const uvDigest = readString("uvDigest");
267
+ const bindingDigest = readString("bindingDigest");
268
+ const format = readString("format");
269
+ const colorSpace = readString("colorSpace");
270
+ const filter = readString("filter");
271
+ const dimensionsValue = asset.dimensions;
272
+ const dimensions = Array.isArray(dimensionsValue) ? dimensionsValue : [requiredNumber(asset, "width"), requiredNumber(asset, "height")];
273
+ const mipCount = requiredNumber(asset, "mipCount") ?? requiredNumber(asset, "mipLevelCount") ?? 1;
274
+ const payloadClass = readString("payloadClass") ?? classifyPayload(asset.data, format) ?? void 0;
275
+ if (subjectDigest === void 0 || boundDigest === void 0 || uvDigest === void 0 || bindingDigest === void 0 || format === void 0 || colorSpace === void 0 || filter === void 0 || dimensions.length !== 2 || dimensions.some(
276
+ (value) => typeof value !== "number" || !Number.isInteger(value) || value <= 0
277
+ ) || !Number.isInteger(mipCount) || mipCount <= 0 || !isTexturePayloadClass(payloadClass)) {
278
+ return subjectFailure(
279
+ "resource-preview-subject-invalid",
280
+ "TextureAsset dimensions and owner binding facts",
281
+ {
282
+ phase: "subject",
283
+ guid: input.guid,
284
+ field: "digest/dimensions/format/colorSpace/mipCount/uv/filter/binding"
285
+ }
286
+ );
287
+ }
288
+ const [width = 0, height = 0] = dimensions;
289
+ return {
290
+ ok: true,
291
+ value: {
292
+ subjectDigest,
293
+ boundDigest,
294
+ dimensions: [width, height],
295
+ format,
296
+ colorSpace,
297
+ mipCount,
298
+ uvDigest,
299
+ filter,
300
+ bindingDigest,
301
+ payloadClass
302
+ }
303
+ };
304
+ }
305
+ async function executeTexturePreview(args, input) {
306
+ if (input.assets === void 0)
307
+ return subjectFailure(
308
+ "resource-preview-subject-invalid",
309
+ "the existing AssetRegistry capability",
310
+ {
311
+ phase: "asset-registry",
312
+ runId: input.runId
313
+ }
314
+ );
315
+ const loaded = await input.assets.load(args.guid, builtinAssetKind("texture"));
316
+ if (!loaded.ok)
317
+ return assetLoadFailure(
318
+ "AssetRegistry.load(guid, textureAssetKind) to resolve the texture",
319
+ input.runId,
320
+ loaded.error
321
+ );
322
+ const inspected = inspectTextureSubject({
323
+ guid: args.guid,
324
+ asset: loaded.value,
325
+ ...loaded.digest === void 0 ? {} : { digest: loaded.digest },
326
+ ...loaded.ownerFacts === void 0 ? {} : { ownerFacts: loaded.ownerFacts }
327
+ });
328
+ if (!inspected.ok) return inspected;
329
+ const renderer = input.renderer;
330
+ const drawCalls = renderer?.texture?.drawCalls ?? renderer?.drawCalls ?? 0;
331
+ if (renderer?.rendererReady !== true || renderer.worldReady !== true || drawCalls <= 0 || renderer.observation === void 0) {
332
+ return subjectFailure(
333
+ "resource-preview-oracle-failed",
334
+ "shared World and Renderer texture stage to produce an observed output",
335
+ {
336
+ phase: "renderer",
337
+ runId: input.runId
338
+ }
339
+ );
340
+ }
341
+ const observation = renderer.observation;
342
+ const observedPayloadClass = observationPayloadClass(observation);
343
+ if (observedPayloadClass === void 0) {
344
+ return subjectFailure(
345
+ "resource-preview-oracle-failed",
346
+ "the rendered texture observation to classify the actual payload",
347
+ { phase: "renderer-observation", runId: input.runId }
348
+ );
349
+ }
350
+ const observed = {
351
+ subjectDigest: observationString(observation, "subjectDigest") ?? "",
352
+ boundDigest: observationString(observation, "boundDigest") ?? "",
353
+ dimensions: observationDimensions(observation) ?? [0, 0],
354
+ format: observationString(observation, "format") ?? "",
355
+ colorSpace: observationString(observation, "colorSpace") ?? "",
356
+ mipCount: typeof observation.mipCount === "number" ? observation.mipCount : 0,
357
+ uvDigest: observationString(observation, "uvDigest") ?? "",
358
+ filter: observationString(observation, "filter") ?? "",
359
+ bindingDigest: observationString(observation, "bindingDigest") ?? "",
360
+ rendererHealthy: renderer.rendererReady && renderer.worldReady,
361
+ drawCalls,
362
+ nonBlackPixels: renderer.nonBlackPixels,
363
+ payloadClass: observedPayloadClass
364
+ };
365
+ const oracle = evaluateTextureOracle({ requested: inspected.value, observed });
366
+ if (oracle.status !== "passed") {
367
+ return subjectFailure(
368
+ "resource-preview-oracle-failed",
369
+ "the rendered texture binding observation to match the loaded owner facts",
370
+ { phase: "oracle", runId: input.runId, ...oracle.detail }
371
+ );
372
+ }
373
+ return {
374
+ ok: true,
375
+ value: {
376
+ subject: { kind: "texture", guid: args.guid, digest: inspected.value.subjectDigest },
377
+ presentation: canonicalPresentation(),
378
+ recipe: createCanonicalPreviewRecipe("texture"),
379
+ quad: { projection: "orthographic", aspect: "preserving", stage: "unlit-checker" },
380
+ binding: { filter: inspected.value.filter, digest: inspected.value.bindingDigest },
381
+ oracle,
382
+ artifacts: input.artifacts ?? []
383
+ },
384
+ artifacts: input.artifacts ?? []
385
+ };
386
+ }
387
+
388
+ // src/host.ts
389
+ var REQUIRED_PREVIEW_EVIDENCE = [
390
+ "rhi-tape",
391
+ "png",
392
+ "profile-capture"
393
+ ];
394
+ function domainFailure(code, expected, hint, detail) {
395
+ return { ok: false, error: { code, expected, hint, detail } };
396
+ }
397
+ function previewRuntimeUnavailable(subject, snapshot, domain) {
398
+ return domainFailure(
399
+ "preview-runtime-unavailable",
400
+ "a Project GUID cold-load and an active PreviewHost/Browser/Dawn runner",
401
+ "Install the Project preview provider and retry; no synthetic artifact is published.",
402
+ {
403
+ domain,
404
+ subjectKind: subject.kind,
405
+ subjectGuid: subject.guid,
406
+ snapshotRevision: snapshot.revision,
407
+ snapshotDigest: snapshot.digest,
408
+ requiredEvidence: REQUIRED_PREVIEW_EVIDENCE
409
+ }
410
+ );
411
+ }
412
+ function validateDomainValue(value, request) {
413
+ if (value.subject.kind !== request.subject.kind || value.subject.guid !== request.subject.guid || value.snapshot.revision !== request.snapshot.revision || value.snapshot.digest !== request.snapshot.digest) {
414
+ return domainFailure(
415
+ "preview-subject-identity-mismatch",
416
+ "the terminal subject and snapshot to match the request",
417
+ "Repair the domain producer so evidence remains bound to the requested subject.",
418
+ {
419
+ requestGuid: request.subject.guid,
420
+ resultGuid: value.subject.guid,
421
+ requestSnapshot: request.snapshot.digest,
422
+ resultSnapshot: value.snapshot.digest
423
+ }
424
+ );
425
+ }
426
+ const kinds = new Set(value.artifacts.map((artifact) => artifact.kind));
427
+ const missing = REQUIRED_PREVIEW_EVIDENCE.filter((kind) => !kinds.has(kind));
428
+ if (missing.length > 0) {
429
+ return domainFailure(
430
+ "preview-evidence-incomplete",
431
+ "one subject-bound artifact for each required evidence kind",
432
+ "Repair the RHI, PNG, and profile producers before retrying.",
433
+ { missing }
434
+ );
435
+ }
436
+ return { ok: true, value };
437
+ }
438
+
439
+ // src/texture.ts
440
+ var texturePreviewArgsSchema = {
441
+ parse(value) {
442
+ if (value === null || typeof value !== "object")
443
+ return { ok: false, error: "expected a texture preview request object" };
444
+ const request = value;
445
+ const binding = request.binding;
446
+ if (request.subject?.kind !== "TextureAsset" || typeof request.subject.guid !== "string")
447
+ return { ok: false, error: "expected a TextureAsset subject" };
448
+ if (request.snapshot === void 0 || !Number.isSafeInteger(request.snapshot.revision) || typeof request.snapshot.digest !== "string")
449
+ return { ok: false, error: "expected a revisioned snapshot" };
450
+ if (binding === void 0 || binding.guid !== request.subject.guid || !Number.isSafeInteger(binding.width) || binding.width < 1 || !Number.isSafeInteger(binding.height) || binding.height < 1 || binding.format.length === 0 || !["linear", "srgb", "hdr"].includes(binding.colorSpace) || !Number.isSafeInteger(binding.mipLevels) || binding.mipLevels < 1 || !Number.isSafeInteger(binding.channels) || binding.channels < 1 || binding.channels > 4)
451
+ return {
452
+ ok: false,
453
+ error: "expected complete texture dimensions, format, and color-space facts"
454
+ };
455
+ return { ok: true, value: request };
456
+ },
457
+ describe: '{"type":"object","required":["subject","snapshot","binding"],"properties":{"subject":{"type":"object","required":["kind","guid"],"properties":{"kind":{"const":"TextureAsset"},"guid":{"type":"string"}}},"snapshot":{"type":"object","required":["revision","digest"]},"binding":{"type":"object","required":["guid","width","height","format","colorSpace","alpha","mipLevels","channels"]}}}'
458
+ };
459
+ var texturePreviewResultSchema = {
460
+ parse: (value) => ({ ok: true, value }),
461
+ describe: '{"type":"object","required":["subject","snapshot","report","artifacts"]}'
462
+ };
463
+ var texturePreviewDescriptor = {
464
+ id: "texture.preview",
465
+ title: "Preview texture asset",
466
+ summary: "Presents one texture on an aspect-correct quad with alpha checker evidence.",
467
+ realm: "engine",
468
+ argsSchema: texturePreviewArgsSchema,
469
+ resultSchema: texturePreviewResultSchema,
470
+ evidence: ["rhi-tape", "png", "profile-capture"],
471
+ preview: {
472
+ realm: "engine",
473
+ subject: { kind: "TextureAsset", guid: "<request>" },
474
+ snapshot: { revision: 0, digest: "<request>" },
475
+ requiredEvidence: ["rhi-tape", "png", "profile-capture"]
476
+ }
477
+ };
478
+ function defaultTextureRunner(request) {
479
+ return Promise.resolve(previewRuntimeUnavailable(request.subject, request.snapshot, "texture"));
480
+ }
481
+ function createTexturePreviewContribution(runner = async (request) => defaultTextureRunner(request)) {
482
+ return defineTool(texturePreviewDescriptor, async (request, context) => {
483
+ const result = await runner(request, context);
484
+ if (!result.ok) return result;
485
+ const report = result.value.report;
486
+ const binding = request.binding;
487
+ if (report.bindingGuid !== request.subject.guid || report.width !== binding.width || report.height !== binding.height || report.format !== binding.format || report.colorSpace !== binding.colorSpace || report.alpha !== binding.alpha || report.mipLevels !== binding.mipLevels || report.channels !== binding.channels || report.aspectRatio !== binding.width / binding.height || report.checkerPixels <= 0 || report.subjectNonBlackPixels <= 0) {
488
+ return domainFailure(
489
+ "preview-subject-falsified",
490
+ "the texture binding facts and aspect-correct checker presentation to agree",
491
+ "Reject texture replacement, color-space drift, and background-only captures.",
492
+ {
493
+ requestGuid: request.subject.guid,
494
+ reportBindingGuid: report.bindingGuid,
495
+ expectedFormat: binding.format,
496
+ actualFormat: report.format,
497
+ expectedColorSpace: binding.colorSpace,
498
+ actualColorSpace: report.colorSpace,
499
+ expectedAspectRatio: binding.width / binding.height,
500
+ actualAspectRatio: report.aspectRatio
501
+ }
502
+ );
503
+ }
504
+ const validated = validateDomainValue(result.value, request);
505
+ if (!validated.ok) return validated;
506
+ return { ok: true, value: validated.value };
507
+ });
508
+ }
509
+ var texturePreview = defineTool(subjectDescriptor("texture"), (args, context) => {
510
+ const host = context.require(previewHostCapability);
511
+ if (!host.ok) return host;
512
+ return host.value.withSession(
513
+ async (mechanisms) => executeTexturePreview(args, {
514
+ ...mechanisms.assets === void 0 ? {} : { assets: mechanisms.assets },
515
+ ...mechanisms.renderer === void 0 ? {} : { renderer: mechanisms.renderer },
516
+ runId: mechanisms.runId,
517
+ ...mechanisms.artifacts === void 0 ? {} : { artifacts: mechanisms.artifacts }
518
+ })
519
+ );
520
+ });
521
+ var texturePreviewPlugin = nativePreviewPlugin("texture", texturePreview);
522
+ var texture_default = texturePreviewPlugin;
523
+
524
+ export { createTexturePreviewContribution, texture_default as default, texturePreview, texturePreviewDescriptor, texturePreviewPlugin };
525
+ //# sourceMappingURL=texture.mjs.map
526
+ //# sourceMappingURL=texture.mjs.map