@instruktlabs/kiln 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +86 -1
- package/README.md +18 -0
- package/dist/agent-run.mjs +6392 -1477
- package/dist/build.json +23 -23
- package/dist/cli.mjs +8453 -3407
- package/dist/evaluator-worker.mjs +16608 -12387
- package/dist/mcp-engine.mjs +6746 -1725
- package/dist/mcp-server.mjs +4 -4
- package/dist/viewer/app.js +203 -199
- package/dist/viewer/chat.html +209 -205
- package/dist/viewer/style.css +10 -0
- package/docs/atlas-provenance.md +23 -0
- package/docs/community-exporter.md +27 -0
- package/docs/export-profiles.md +20 -0
- package/docs/install.md +7 -6
- package/docs/material-library.md +9 -5
- package/docs/migration.md +16 -0
- package/docs/procedural-surfaces-v3.md +276 -0
- package/docs/projects-and-live-review.md +1 -1
- package/docs/rendering.md +41 -0
- package/docs/runtime.md +3 -3
- package/docs/sdk.md +55 -2
- package/docs/tools-operations.md +2 -2
- package/docs/tools.md +3 -3
- package/lib/asset-materials-node.d.ts +3 -3
- package/lib/asset-materials-node.js +29 -33
- package/lib/assets-node.d.ts +1 -1
- package/lib/assets-node.js +11 -4
- package/lib/assets.d.ts +4 -2
- package/lib/assets.js +61 -16
- package/lib/atlas-provenance.d.ts +86 -0
- package/lib/atlas-provenance.js +448 -0
- package/lib/character.d.ts +11 -2
- package/lib/character.js +90 -2
- package/lib/community-exporter.d.ts +3 -2
- package/lib/community-exporter.js +139 -3
- package/lib/discovery/helper-contracts.js +161 -7
- package/lib/discovery/helper-specs.js +49 -4
- package/lib/discovery/shapes.js +20 -7
- package/lib/engine-identity.d.ts +1 -1
- package/lib/engine-identity.js +1 -1
- package/lib/evaluator/authoring-diagnostic.d.ts +1 -1
- package/lib/evaluator/authoring-diagnostic.js +12 -0
- package/lib/evaluator/protocol.d.ts +2 -2
- package/lib/evaluator/protocol.js +16 -6
- package/lib/evaluator/subprocess.js +2 -2
- package/lib/exporter-node.js +9 -1
- package/lib/geometry-budget.d.ts +7 -0
- package/lib/geometry-budget.js +8 -0
- package/lib/gltf-texture-binding.d.ts +6 -0
- package/lib/gltf-texture-binding.js +54 -0
- package/lib/material-authoring-node.d.ts +6 -0
- package/lib/material-authoring-node.js +49 -0
- package/lib/material-authoring-schema.d.ts +871 -0
- package/lib/material-authoring-schema.js +310 -0
- package/lib/material-export.d.ts +5 -0
- package/lib/material-export.js +21 -0
- package/lib/material-library-node.d.ts +34 -8
- package/lib/material-library-node.js +691 -11
- package/lib/material-library.d.ts +3704 -4
- package/lib/material-library.js +272 -0
- package/lib/mcp-core.d.ts +1 -1
- package/lib/pinned-material-map.d.ts +33 -0
- package/lib/pinned-material-map.js +1 -0
- package/lib/portable-material-runtime.d.ts +5 -0
- package/lib/portable-material-runtime.js +160 -0
- package/lib/portable-sampler-runtime.d.ts +4 -0
- package/lib/portable-sampler-runtime.js +26 -0
- package/lib/portable-sampler.d.ts +11 -0
- package/lib/portable-sampler.js +42 -0
- package/lib/primitives.d.ts +15 -3
- package/lib/primitives.js +24 -4
- package/lib/procedural-material-v2.d.ts +2 -0
- package/lib/procedural-material-v2.js +2 -1
- package/lib/procedural-material-v3.d.ts +72 -0
- package/lib/procedural-material-v3.js +162 -0
- package/lib/procedural-texture-v2-compiler.d.ts +31 -0
- package/lib/procedural-texture-v2-compiler.js +172 -0
- package/lib/procedural-texture-v3-contract.d.ts +125 -0
- package/lib/procedural-texture-v3-contract.js +333 -0
- package/lib/procedural-texture-v3.d.ts +12 -0
- package/lib/procedural-texture-v3.js +165 -0
- package/lib/procedural-texture.d.ts +9 -31
- package/lib/procedural-texture.js +43 -170
- package/lib/project-bundle-node.d.ts +1 -1
- package/lib/project-bundle-node.js +70 -13
- package/lib/project-bundle.d.ts +91 -3
- package/lib/project-bundle.js +48 -7
- package/lib/qa/atlas.d.ts +21 -0
- package/lib/qa/atlas.js +474 -0
- package/lib/qa/index.d.ts +1 -0
- package/lib/qa/index.js +1 -0
- package/lib/qa/material.js +2 -0
- package/lib/qa/skin-integrity.d.ts +18 -0
- package/lib/qa/skin-integrity.js +221 -0
- package/lib/render-service-host.js +4 -7
- package/lib/render.d.ts +3 -3
- package/lib/render.js +108 -16
- package/lib/skin-binding.d.ts +20 -0
- package/lib/skin-binding.js +144 -0
- package/lib/texture-atlas-contract.d.ts +97 -0
- package/lib/texture-atlas-contract.js +414 -0
- package/lib/texture-atlas-pixels.d.ts +73 -0
- package/lib/texture-atlas-pixels.js +290 -0
- package/lib/texture-atlas.d.ts +10 -0
- package/lib/texture-atlas.js +143 -0
- package/lib/texture-bake.d.ts +3 -0
- package/lib/texture-bake.js +66 -11
- package/lib/texture-resolver.d.ts +22 -0
- package/lib/texture-resolver.js +24 -0
- package/lib/tools/materials.js +8 -7
- package/lib/tools/registry.js +5 -16
- package/lib/tools/review.js +1 -1
- package/lib/uv-data.d.ts +32 -0
- package/lib/uv-data.js +172 -0
- package/lib/uv-shapes.js +3 -0
- package/lib/uv.d.ts +9 -3
- package/lib/uv.js +53 -82
- package/lib/views/camera.d.ts +3 -3
- package/lib/views/camera.js +32 -28
- package/lib/views/face-policy.d.ts +9 -0
- package/lib/views/face-policy.js +16 -0
- package/lib/views/glb.js +218 -24
- package/lib/views/pose.d.ts +7 -1
- package/lib/views/pose.js +63 -12
- package/lib/views/raster.d.ts +6 -2
- package/lib/views/raster.js +49 -37
- package/lib/views/scene-copy.d.ts +6 -0
- package/lib/views/scene-copy.js +73 -0
- package/lib/views/scene-raster.js +2 -1
- package/lib/views/triangle-coverage.d.ts +8 -0
- package/lib/views/triangle-coverage.js +58 -0
- package/lib/workspace-node.js +7 -3
- package/lib/workspace.d.ts +2 -2
- package/package.json +3 -1
- package/plugin.json +1 -1
- package/plugins/kiln-engine/.claude-plugin/plugin.json +1 -1
- package/plugins/kiln-engine/README.md +3 -3
- package/plugins/kiln-engine/package-provenance.json +6 -6
- package/plugins/kiln-engine/plugin.json +1 -1
- package/plugins/kiln-engine/runtime.json +1 -1
- package/skills/kiln-author-asset/SKILL.md +1 -1
- package/skills/kiln-author-asset/references/camera-cli.md +13 -0
- package/skills/kiln-author-asset/references/geometry-recipes.md +134 -0
- package/skills/kiln-author-asset/references/projects-and-materials.md +32 -2
- package/skills/kiln-compose-scene/references/runtime-scenes.md +19 -0
- package/skills/kiln-qa-asset/references/integration-checks.md +37 -2
- package/src/asset-materials-node.ts +38 -45
- package/src/assets-node.ts +22 -5
- package/src/assets.ts +61 -20
- package/src/atlas-provenance.ts +522 -0
- package/src/character.ts +107 -2
- package/src/community-exporter.ts +143 -3
- package/src/discovery/helper-contracts.ts +188 -7
- package/src/discovery/helper-specs.ts +69 -4
- package/src/discovery/shapes.ts +25 -9
- package/src/engine-identity.ts +1 -1
- package/src/evaluator/authoring-diagnostic.ts +19 -1
- package/src/evaluator/protocol.ts +19 -9
- package/src/evaluator/subprocess.ts +2 -2
- package/src/exporter-node.ts +12 -1
- package/src/generated/mcp-manifest.json +3 -3
- package/src/geometry-budget.ts +8 -0
- package/src/gltf-texture-binding.ts +60 -0
- package/src/material-authoring-node.ts +68 -0
- package/src/material-authoring-schema.ts +343 -0
- package/src/material-export.ts +25 -0
- package/src/material-library-node.ts +840 -16
- package/src/material-library.ts +348 -5
- package/src/pinned-material-map.ts +43 -0
- package/src/portable-material-runtime.ts +200 -0
- package/src/portable-sampler-runtime.ts +40 -0
- package/src/portable-sampler.ts +54 -0
- package/src/primitives.ts +98 -4
- package/src/procedural-material-v2.ts +2 -1
- package/src/procedural-material-v3.ts +224 -0
- package/src/procedural-texture-v2-compiler.ts +234 -0
- package/src/procedural-texture-v3-contract.ts +437 -0
- package/src/procedural-texture-v3.ts +220 -0
- package/src/procedural-texture.ts +61 -229
- package/src/project-bundle-node.ts +106 -14
- package/src/project-bundle.ts +61 -18
- package/src/project-cli.ts +29 -17
- package/src/qa/atlas.ts +597 -0
- package/src/qa/index.ts +1 -0
- package/src/qa/material.ts +2 -0
- package/src/qa/skin-integrity.ts +291 -0
- package/src/render-service-host.ts +4 -6
- package/src/render.ts +133 -15
- package/src/skin-binding.ts +225 -0
- package/src/texture-atlas-contract.ts +558 -0
- package/src/texture-atlas-pixels.ts +456 -0
- package/src/texture-atlas.ts +185 -0
- package/src/texture-bake.ts +88 -10
- package/src/texture-resolver.ts +55 -0
- package/src/tools/materials.ts +15 -11
- package/src/tools/registry.ts +5 -15
- package/src/tools/review.ts +1 -1
- package/src/uv-data.ts +238 -0
- package/src/uv-shapes.ts +3 -0
- package/src/uv.ts +73 -128
- package/src/viewer/app.ts +37 -0
- package/src/viewer/live-panel.ts +57 -1
- package/src/viewer/material-preview-panel.ts +140 -0
- package/src/viewer/material-preview.ts +138 -0
- package/src/viewer/materials-panel.ts +34 -5
- package/src/viewer/navigation.ts +2 -0
- package/src/views/camera.ts +37 -40
- package/src/views/face-policy.ts +20 -0
- package/src/views/glb.ts +294 -17
- package/src/views/pose.ts +71 -13
- package/src/views/raster.ts +73 -46
- package/src/views/scene-copy.ts +86 -0
- package/src/views/scene-raster.ts +2 -1
- package/src/views/triangle-coverage.ts +78 -0
- package/src/workspace-node.ts +11 -3
- package/src/workspace.ts +2 -2
package/docs/sdk.md
CHANGED
|
@@ -22,7 +22,7 @@ const result = await renderGLB(code);
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
The complete authoring and host APIs remain available through documented subpaths.
|
|
25
|
-
The original 55 export paths are retained; version 1.1
|
|
25
|
+
The original 55 export paths are retained; version 1.1 added four
|
|
26
26
|
host entrypoints below. The root exposes rendering, validation,
|
|
27
27
|
Discovery and engine identity; it does not re-export every name from every module.
|
|
28
28
|
CommonJS is not part of this release's contract.
|
|
@@ -53,7 +53,7 @@ entrypoint list. Files inside `lib/`, including declaration dependencies under
|
|
|
53
53
|
|
|
54
54
|
## Stability in 1.x
|
|
55
55
|
|
|
56
|
-
The table classifies all 59 public entrypoints
|
|
56
|
+
The table classifies all 59 public entrypoints. The root and every listed subpath
|
|
57
57
|
except `implicit` are stable entrypoints for v1: their declared public signatures,
|
|
58
58
|
record shapes and documented behavior receive normal semantic-versioning
|
|
59
59
|
compatibility. Optional dependencies do not make the two agent APIs experimental.
|
|
@@ -80,6 +80,59 @@ and source/revision receipts for reproducible replay. Compatibility of the SDK
|
|
|
80
80
|
does not imply identical generated GLB bytes or renderer output across releases.
|
|
81
81
|
Persisted data and rebuild restrictions follow the [migration contract](migration.md).
|
|
82
82
|
|
|
83
|
+
## Procedural RGBA surfaces
|
|
84
|
+
|
|
85
|
+
The `primitives` entrypoint and authored `proceduralTexture` helper accept an
|
|
86
|
+
explicit V3 recipe for bounded rectangular RGBA images, shape coverage, alpha
|
|
87
|
+
masks, palettes, dithering and seeded stamps. Pure V3 compilation returns pixels,
|
|
88
|
+
canonical JSON and a compiler-qualified recipe hash without creating a mesh.
|
|
89
|
+
Read the [V3 contract](procedural-surfaces-v3.md) for the exact limits and rounding
|
|
90
|
+
rules. V1/V2 remain supported with their existing pixels, hashes and sampler
|
|
91
|
+
defaults. The same entrypoint exposes `textureAtlas` and `remapUVToAtlas` for
|
|
92
|
+
named regions. Portable V3 materials add explicit per-map sampling; opt-in V2
|
|
93
|
+
material records persist recipes, compiled maps and exact dependency pins.
|
|
94
|
+
See the [standalone material and atlas workflow](procedural-surfaces-v3.md#standalone-saved-materials-and-atlases).
|
|
95
|
+
|
|
96
|
+
## Explicit character skinning
|
|
97
|
+
|
|
98
|
+
The `character` and `primitives` entrypoints expose opt-in `createBoneChain` and
|
|
99
|
+
`createSkinnedPart`; both are also sandbox helpers. `createJointChain` continues
|
|
100
|
+
to return ordinary rigid `Object3D` pivots. Bone chains use the same versioned
|
|
101
|
+
joint descriptors, infer an attached declared parent role and reject ambiguous
|
|
102
|
+
roles/names before attaching nodes. They do not choose anatomy or a body plan.
|
|
103
|
+
|
|
104
|
+
`createSkinnedPart(name, geometry, material, { parent, bones, indices, weights })`
|
|
105
|
+
clones unchanged geometry and binds it to the assembled hierarchy's current rest
|
|
106
|
+
pose. Supply exactly four indices and weights per vertex, in the explicit bone
|
|
107
|
+
array order. Indices must be valid even in zero-weight lanes. By default weights
|
|
108
|
+
must be in 0..1 and sum to one within 1e-5; `normalizeWeights: true` explicitly
|
|
109
|
+
normalizes positive finite sums. Zero sums are rejected. Optional local position,
|
|
110
|
+
Euler rotation (degrees), scale and semantic metadata match `createPart`'s units.
|
|
111
|
+
|
|
112
|
+
The helper owns geometry, influence arrays and inverse bind matrices; bones and
|
|
113
|
+
materials remain shared. Limits are 1024 bones, 262144 vertices and 128 MiB of
|
|
114
|
+
cloned attributes plus new influence storage. These are helper limits, not new
|
|
115
|
+
restrictions on direct Three.js authoring. Existing skin attributes and singular
|
|
116
|
+
bind transforms are rejected. Set up rest transforms before binding; later rest
|
|
117
|
+
edits/reparenting need deliberate rebinding. Animated bounds remain a consumer
|
|
118
|
+
responsibility. The selected Three exporter is required to preserve skins;
|
|
119
|
+
calling these helpers does not change the legacy default or select a converter.
|
|
120
|
+
|
|
121
|
+
`inspectSkinIntegrity(root)` from `@instruktlabs/kiln/qa` reads source bindings
|
|
122
|
+
without updating matrices, normalizing weights or repairing the scene. It returns
|
|
123
|
+
bounded findings, checked counts and current posed world bounds (including position
|
|
124
|
+
morphs). It is not an animation envelope, deformation-quality score or proof of
|
|
125
|
+
collision clearance. Rigid meshes do not acquire skin or body-plan requirements.
|
|
126
|
+
|
|
127
|
+
Export runs this check before bounds, texture baking or conversion. Invalid source
|
|
128
|
+
bindings produce an `AssetQaBlockedError` at the scene stage with mesh/joint or
|
|
129
|
+
vertex-range context and repair advice. This is mandatory input integrity, like
|
|
130
|
+
invalid geometry buffers; QA warning mode and derivative serialization do not
|
|
131
|
+
bypass it. The partial failure report does not claim the remaining QA rules ran.
|
|
132
|
+
The independent final glTF validation remains in place. Source Three skeletons
|
|
133
|
+
need their inverse-bind arrays populated; this does not change glTF's valid
|
|
134
|
+
identity-matrix default when the file omits `inverseBindMatrices`.
|
|
135
|
+
|
|
83
136
|
## Execution and dependencies
|
|
84
137
|
|
|
85
138
|
The root SDK is Node-oriented. `/node` adapters use filesystem or server services;
|
package/docs/tools-operations.md
CHANGED
|
@@ -1123,7 +1123,7 @@ Create and retain one immutable material from a preset or procedural definition.
|
|
|
1123
1123
|
"type": "object",
|
|
1124
1124
|
"properties": {
|
|
1125
1125
|
"draft": {
|
|
1126
|
-
"description": "create-procedural:
|
|
1126
|
+
"description": "create-procedural: legacy layered draft, or schemaVersion 2 with explicit samplers and RGBA/atlas recipes. Exact atlas sources must exist in this library. Shape: kiln_discover({ ids: ['shape:material-draft'] }).",
|
|
1127
1127
|
"type": "object",
|
|
1128
1128
|
"propertyNames": {
|
|
1129
1129
|
"type": "string"
|
|
@@ -1166,7 +1166,7 @@ Import complete normalized material records and embedded PNGs. Does not fetch UR
|
|
|
1166
1166
|
"type": "object",
|
|
1167
1167
|
"properties": {
|
|
1168
1168
|
"payload": {
|
|
1169
|
-
"description": "import: schemaVersion 1
|
|
1169
|
+
"description": "import: schemaVersion 1 or 2, complete normalized records and their exact dependencies with embedded PNG bytes. Shape: kiln_discover({ ids: ['shape:material-import'] }).",
|
|
1170
1170
|
"type": "object",
|
|
1171
1171
|
"propertyNames": {
|
|
1172
1172
|
"type": "string"
|
package/docs/tools.md
CHANGED
|
@@ -1328,7 +1328,7 @@ Manage optional shared workspace projects: list discovers IDs; get reads the cur
|
|
|
1328
1328
|
|
|
1329
1329
|
## kiln_material
|
|
1330
1330
|
|
|
1331
|
-
Manage optional immutable material resources in this workspace. presets
|
|
1331
|
+
Manage optional immutable material resources in this workspace. presets lists shipped recipes; create-preset uses explicit seed, creator and license; list returns revision summaries; get returns provenance, hashes, sampling and a portable material spec. create-procedural accepts legacy layer drafts or schemaVersion 2 drafts with explicit samplers and bounded RGBA or named atlas recipes. Atlas resource sources require exact saved revisions; import requires complete normalized records and dependencies with embedded PNG bytes. Read shape:material-draft or shape:material-import through kiln_discover for full fields. No action downloads URLs or executes source. Pin the returned materialId/revisionId through per-invocation materialDependencies or project dependencies before evaluation; no project or mesh is required.
|
|
1332
1332
|
|
|
1333
1333
|
<details>
|
|
1334
1334
|
<summary>Input JSON Schema</summary>
|
|
@@ -1431,7 +1431,7 @@ Manage optional immutable material resources in this workspace. presets discover
|
|
|
1431
1431
|
"pattern": "^sha256:[a-f0-9]{64}$"
|
|
1432
1432
|
},
|
|
1433
1433
|
"draft": {
|
|
1434
|
-
"description": "create-procedural:
|
|
1434
|
+
"description": "create-procedural: legacy layered draft, or schemaVersion 2 with explicit samplers and RGBA/atlas recipes. Exact atlas sources must exist in this library. Shape: kiln_discover({ ids: ['shape:material-draft'] }).",
|
|
1435
1435
|
"type": "object",
|
|
1436
1436
|
"propertyNames": {
|
|
1437
1437
|
"type": "string"
|
|
@@ -1439,7 +1439,7 @@ Manage optional immutable material resources in this workspace. presets discover
|
|
|
1439
1439
|
"additionalProperties": {}
|
|
1440
1440
|
},
|
|
1441
1441
|
"payload": {
|
|
1442
|
-
"description": "import: schemaVersion 1
|
|
1442
|
+
"description": "import: schemaVersion 1 or 2, complete normalized records and their exact dependencies with embedded PNG bytes. Shape: kiln_discover({ ids: ['shape:material-import'] }).",
|
|
1443
1443
|
"type": "object",
|
|
1444
1444
|
"propertyNames": {
|
|
1445
1445
|
"type": "string"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Exact saved material closure, shared by standalone asset and project delivery. */
|
|
2
2
|
import type { AssetRecord } from "./assets.js";
|
|
3
|
-
import { type MaterialLibrary, type
|
|
4
|
-
export declare function dependencyManifests(asset: AssetRecord):
|
|
5
|
-
export declare function resolveSavedAssetMaterials(asset: AssetRecord, library?: MaterialLibrary): Promise<
|
|
3
|
+
import { type MaterialLibrary, type MaterialManifest, type MaterialRecord } from "./material-library.js";
|
|
4
|
+
export declare function dependencyManifests(asset: AssetRecord): MaterialManifest[];
|
|
5
|
+
export declare function resolveSavedAssetMaterials(asset: AssetRecord, library?: MaterialLibrary): Promise<MaterialRecord[]>;
|
|
@@ -1,30 +1,13 @@
|
|
|
1
|
-
import { validateAssetMaterialClosure } from "./assets.js";
|
|
2
|
-
import {
|
|
3
|
-
import { decodeMaterialLibraryPayload,
|
|
1
|
+
import { assetMaterialRootManifests, validateAssetMaterialClosure } from "./assets.js";
|
|
2
|
+
import { MATERIAL_LIBRARY_LIMITS, } from "./material-library.js";
|
|
3
|
+
import { decodeMaterialLibraryPayload, validateMaterialManifestIdentityAny, resolveMaterialRecordClosure, } from "./material-library-node.js";
|
|
4
4
|
export function dependencyManifests(asset) {
|
|
5
|
-
const result =
|
|
6
|
-
|
|
7
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
8
|
-
throw new Error('Invalid saved asset dependency');
|
|
9
|
-
const dependency = raw;
|
|
10
|
-
if (dependency.kind === 'kiln.material.v1') {
|
|
11
|
-
if (dependency.delivery !== 'runtime')
|
|
12
|
-
throw new Error('Invalid material dependency delivery');
|
|
13
|
-
const manifest = validateMaterialManifestIdentity(dependency.manifest);
|
|
14
|
-
result.set(`${manifest.materialId}/${manifest.revisionId}`, manifest);
|
|
15
|
-
if (result.size > MATERIAL_LIBRARY_LIMITS.maxPayloadRecords)
|
|
16
|
-
throw new Error('Saved asset material dependency count exceeds limit');
|
|
17
|
-
}
|
|
18
|
-
else if (dependency.delivery === 'runtime')
|
|
19
|
-
throw new Error(`Unsupported runtime dependency: ${String(dependency.resourceId ?? 'unknown')}`);
|
|
20
|
-
else if (dependency.delivery !== 'embedded')
|
|
21
|
-
throw new Error('Unsupported saved asset dependency');
|
|
22
|
-
}
|
|
23
|
-
if (asset.manifest.build?.rebuild === 'external-dependencies-required' && !result.size)
|
|
5
|
+
const result = assetMaterialRootManifests(asset).map(validateMaterialManifestIdentityAny);
|
|
6
|
+
if (asset.manifest.build?.rebuild === 'external-dependencies-required' && !result.length)
|
|
24
7
|
throw new Error('Saved asset has unrecorded external dependencies');
|
|
25
8
|
let bytes = 0;
|
|
26
9
|
let pixels = 0;
|
|
27
|
-
for (const manifest of result
|
|
10
|
+
for (const manifest of result)
|
|
28
11
|
for (const map of manifest.maps) {
|
|
29
12
|
bytes += map.bytes;
|
|
30
13
|
pixels += map.width * map.height;
|
|
@@ -32,7 +15,7 @@ export function dependencyManifests(asset) {
|
|
|
32
15
|
if (bytes > MATERIAL_LIBRARY_LIMITS.maxPayloadBytes ||
|
|
33
16
|
pixels > MATERIAL_LIBRARY_LIMITS.maxPayloadPixels)
|
|
34
17
|
throw new Error('Saved asset material allocation exceeds limit');
|
|
35
|
-
return
|
|
18
|
+
return result;
|
|
36
19
|
}
|
|
37
20
|
export async function resolveSavedAssetMaterials(asset, library) {
|
|
38
21
|
const dependencies = dependencyManifests(asset);
|
|
@@ -40,17 +23,30 @@ export async function resolveSavedAssetMaterials(asset, library) {
|
|
|
40
23
|
validateAssetMaterialClosure(asset, true);
|
|
41
24
|
return decodeMaterialLibraryPayload(asset.materialResources);
|
|
42
25
|
}
|
|
43
|
-
|
|
26
|
+
if (!dependencies.length)
|
|
27
|
+
return [];
|
|
28
|
+
if (!library)
|
|
29
|
+
throw new Error('Locked asset material unavailable: No material library configured');
|
|
30
|
+
const roots = new Map(dependencies.map((m) => [`${m.materialId}/${m.revisionId}`, m]));
|
|
31
|
+
const wrap = async (id, revision, load) => {
|
|
44
32
|
try {
|
|
45
|
-
|
|
46
|
-
throw new Error('No material library configured');
|
|
47
|
-
const record = await library.read(manifest.materialId, manifest.revisionId);
|
|
48
|
-
if (canonicalMaterialJson(record.manifest) !== canonicalMaterialJson(manifest))
|
|
49
|
-
throw new Error('Saved dependency manifest mismatch');
|
|
50
|
-
return record;
|
|
33
|
+
return await load();
|
|
51
34
|
}
|
|
52
35
|
catch (error) {
|
|
53
|
-
throw new Error(`Locked asset material unavailable: ${
|
|
36
|
+
throw new Error(`Locked asset material unavailable: ${id} at ${revision}`, { cause: error });
|
|
54
37
|
}
|
|
55
|
-
}
|
|
38
|
+
};
|
|
39
|
+
return resolveMaterialRecordClosure(dependencies, {
|
|
40
|
+
readManifest: async (id, revision) => wrap(id, revision, async () => {
|
|
41
|
+
const root = roots.get(`${id}/${revision}`);
|
|
42
|
+
if (root)
|
|
43
|
+
return root;
|
|
44
|
+
if (!library.readManifest)
|
|
45
|
+
throw new Error('Material library requires manifest-first dependency capability');
|
|
46
|
+
return library.readManifest(id, revision);
|
|
47
|
+
}),
|
|
48
|
+
read: async (id, revision) => wrap(id, revision, () => library.readRecordFiles
|
|
49
|
+
? library.readRecordFiles(id, revision)
|
|
50
|
+
: library.read(id, revision)),
|
|
51
|
+
});
|
|
56
52
|
}
|
package/lib/assets-node.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AssetDraft, type AssetLibrary, type AssetManifest, type AssetRecord } from "./assets.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type MaterialLibrary } from "./material-library.js";
|
|
3
3
|
export { resolveSavedAssetMaterials } from "./asset-materials-node.js";
|
|
4
4
|
export declare function verifyAssetRecord(record: AssetRecord): Promise<void>;
|
|
5
5
|
export declare class FileAssetLibrary implements AssetLibrary {
|
package/lib/assets-node.js
CHANGED
|
@@ -6,6 +6,7 @@ import { homedir, platform } from 'node:os';
|
|
|
6
6
|
import { dirname, join, relative, resolve, sep, win32 } from 'node:path';
|
|
7
7
|
import { localWorkspaceRoot } from "./workspace-location.js";
|
|
8
8
|
import { assetIdSchema, assetManifestSchema, ASSET_LIMIT, ASSET_MATERIAL_BYTES, ASSET_MATERIAL_FILE, encodeAssetBundle, validateRecordShape, } from "./assets.js";
|
|
9
|
+
import { canonicalMaterialJson, } from "./material-library.js";
|
|
9
10
|
import { FileMaterialLibrary, createMaterialLibraryPayload } from "./material-library-node.js";
|
|
10
11
|
import { dependencyManifests, resolveSavedAssetMaterials } from "./asset-materials-node.js";
|
|
11
12
|
import { renameDirectoryAtomically } from "./atomic-directory.js";
|
|
@@ -201,7 +202,7 @@ export class FileAssetLibrary {
|
|
|
201
202
|
assertMaterialAllocation(records.filter((record) => record.materialResources ||
|
|
202
203
|
record.manifest.build?.dependencies?.some((raw) => raw &&
|
|
203
204
|
typeof raw === 'object' &&
|
|
204
|
-
|
|
205
|
+
['kiln.material.v1', 'kiln.material.v2'].includes(String(raw.kind)))));
|
|
205
206
|
let resourceBytes = 0;
|
|
206
207
|
for (const record of records) {
|
|
207
208
|
validateRecordShape(record);
|
|
@@ -222,10 +223,16 @@ export class FileAssetLibrary {
|
|
|
222
223
|
(this.materials &&
|
|
223
224
|
record.manifest.build?.dependencies?.some((raw) => raw &&
|
|
224
225
|
typeof raw === 'object' &&
|
|
225
|
-
|
|
226
|
+
['kiln.material.v1', 'kiln.material.v2'].includes(String(raw.kind))))) {
|
|
226
227
|
const materials = await resolveSavedAssetMaterials(record, this.materials);
|
|
227
|
-
for (const material of materials)
|
|
228
|
-
|
|
228
|
+
for (const material of materials) {
|
|
229
|
+
const key = `${material.manifest.materialId}/${material.manifest.revisionId}`;
|
|
230
|
+
const previous = resources.get(key);
|
|
231
|
+
if (previous &&
|
|
232
|
+
canonicalMaterialJson(previous.manifest) !== canonicalMaterialJson(material.manifest))
|
|
233
|
+
throw new Error('Conflicting asset material dependencies');
|
|
234
|
+
resources.set(key, material);
|
|
235
|
+
}
|
|
229
236
|
prepared.push({
|
|
230
237
|
...record,
|
|
231
238
|
materialResources: await createMaterialLibraryPayload(materials),
|
package/lib/assets.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Portable asset records. No filesystem, renderer, or server dependencies. */
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { type
|
|
3
|
+
import { type MaterialLibraryPayload, type MaterialManifest } from "./material-library.js";
|
|
4
4
|
export declare const ASSET_LIMIT: number;
|
|
5
5
|
export declare const assetIdSchema: z.ZodString;
|
|
6
6
|
export declare const assetManifestSchema: z.ZodObject<{
|
|
@@ -50,7 +50,7 @@ export interface AssetRecord {
|
|
|
50
50
|
manifest: AssetManifest;
|
|
51
51
|
files: Record<string, Uint8Array>;
|
|
52
52
|
/** Editable delivery closure; not part of the canonical asset manifest or revision identity. */
|
|
53
|
-
materialResources?:
|
|
53
|
+
materialResources?: MaterialLibraryPayload;
|
|
54
54
|
}
|
|
55
55
|
export interface AssetDraft {
|
|
56
56
|
name: string;
|
|
@@ -82,6 +82,8 @@ export interface AssetLibrary {
|
|
|
82
82
|
}
|
|
83
83
|
export declare const ASSET_MATERIAL_FILE = "materials.kiln.json";
|
|
84
84
|
export declare const ASSET_MATERIAL_BYTES: number;
|
|
85
|
+
/** Pure declaration boundary shared by portable delivery and host hash verification. */
|
|
86
|
+
export declare function assetMaterialRootManifests(record: AssetRecord): MaterialManifest[];
|
|
85
87
|
/** Shape and closure checks are portable; hosts verify hashes and PNG/recipe bytes before import. */
|
|
86
88
|
export declare function validateAssetMaterialClosure(record: AssetRecord, required?: boolean): void;
|
|
87
89
|
export declare function validateRecordShape(record: AssetRecord): void;
|
package/lib/assets.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { BACKDROP_IDS } from "./views/background.js";
|
|
4
4
|
import { zipSync, unzipSync } from 'three/addons/libs/fflate.module.js';
|
|
5
|
-
import { assertMaterialJson, canonicalMaterialJson,
|
|
5
|
+
import { assertMaterialJson, canonicalMaterialJson, materialLibraryPayloadAnySchema, materialRecordDependencyPins, validateMaterialManifestAny, MATERIAL_LIBRARY_LIMITS, } from "./material-library.js";
|
|
6
6
|
export const ASSET_LIMIT = 64 * 1024 * 1024;
|
|
7
7
|
export const assetIdSchema = z.string().regex(/^[a-z][a-z0-9_-]{0,79}$/);
|
|
8
8
|
const hash = z.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
@@ -47,32 +47,59 @@ export const assetManifestSchema = z.object({
|
|
|
47
47
|
});
|
|
48
48
|
export const ASSET_MATERIAL_FILE = 'materials.kiln.json';
|
|
49
49
|
export const ASSET_MATERIAL_BYTES = 24 * 1024 * 1024;
|
|
50
|
-
/**
|
|
51
|
-
export function
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
raw
|
|
55
|
-
|
|
50
|
+
/** Pure declaration boundary shared by portable delivery and host hash verification. */
|
|
51
|
+
export function assetMaterialRootManifests(record) {
|
|
52
|
+
const roots = new Map();
|
|
53
|
+
for (const raw of record.manifest.build?.dependencies ?? []) {
|
|
54
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
55
|
+
throw new Error('Invalid saved asset dependency');
|
|
56
|
+
const dependency = raw;
|
|
57
|
+
if (dependency.kind !== 'kiln.material.v1' && dependency.kind !== 'kiln.material.v2') {
|
|
58
|
+
if (dependency.delivery === 'runtime')
|
|
59
|
+
throw new Error('Unsupported runtime dependency');
|
|
60
|
+
if (dependency.delivery !== 'embedded')
|
|
61
|
+
throw new Error('Unsupported saved asset dependency');
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
56
64
|
if (dependency.delivery !== 'runtime')
|
|
57
65
|
throw new Error('Invalid saved material dependency');
|
|
58
|
-
const manifest =
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
const manifest = validateMaterialManifestAny(dependency.manifest);
|
|
67
|
+
if (dependency.kind !== `kiln.material.v${manifest.schemaVersion}`)
|
|
68
|
+
throw new Error('Saved material dependency kind/version mismatch');
|
|
69
|
+
const key = `${manifest.materialId}/${manifest.revisionId}`;
|
|
70
|
+
const existing = roots.get(key);
|
|
71
|
+
if (existing && canonicalMaterialJson(existing) !== canonicalMaterialJson(manifest))
|
|
72
|
+
throw new Error('Conflicting saved material dependency manifests');
|
|
73
|
+
roots.set(key, manifest);
|
|
74
|
+
if (roots.size > MATERIAL_LIBRARY_LIMITS.maxPayloadRecords)
|
|
75
|
+
throw new Error('Saved asset material dependency count exceeds limit');
|
|
76
|
+
}
|
|
77
|
+
return [...roots.values()];
|
|
78
|
+
}
|
|
79
|
+
/** Shape and closure checks are portable; hosts verify hashes and PNG/recipe bytes before import. */
|
|
80
|
+
export function validateAssetMaterialClosure(record, required = false) {
|
|
81
|
+
const dependencies = assetMaterialRootManifests(record);
|
|
82
|
+
const expected = new Map(dependencies.map((manifest) => [
|
|
83
|
+
`${manifest.materialId}/${manifest.revisionId}`,
|
|
84
|
+
canonicalMaterialJson(manifest),
|
|
85
|
+
]));
|
|
61
86
|
if (!record.materialResources) {
|
|
62
87
|
if (required && expected.size)
|
|
63
88
|
throw new Error('Editable asset material closure unavailable');
|
|
64
89
|
return;
|
|
65
90
|
}
|
|
66
91
|
assertMaterialJson(record.materialResources);
|
|
67
|
-
const payload =
|
|
92
|
+
const payload = materialLibraryPayloadAnySchema.parse(record.materialResources);
|
|
68
93
|
let bytes = 0;
|
|
69
94
|
let pixels = 0;
|
|
70
|
-
const
|
|
95
|
+
const available = new Map();
|
|
71
96
|
for (const wire of payload.records) {
|
|
97
|
+
const manifest = validateMaterialManifestAny(wire.manifest);
|
|
72
98
|
const key = `${wire.manifest.materialId}/${wire.manifest.revisionId}`;
|
|
73
|
-
if (
|
|
99
|
+
if (available.has(key) ||
|
|
100
|
+
(expected.has(key) && expected.get(key) !== canonicalMaterialJson(manifest)))
|
|
74
101
|
throw new Error('Asset material closure mismatch');
|
|
75
|
-
|
|
102
|
+
available.set(key, manifest);
|
|
76
103
|
if (Object.keys(wire.files).length !== wire.manifest.maps.length)
|
|
77
104
|
throw new Error('Asset material map inventory mismatch');
|
|
78
105
|
for (const map of wire.manifest.maps) {
|
|
@@ -82,11 +109,29 @@ export function validateAssetMaterialClosure(record, required = false) {
|
|
|
82
109
|
pixels += map.width * map.height;
|
|
83
110
|
}
|
|
84
111
|
}
|
|
85
|
-
if (seen.size !== expected.size)
|
|
86
|
-
throw new Error('Asset material closure incomplete');
|
|
87
112
|
if (bytes > MATERIAL_LIBRARY_LIMITS.maxPayloadBytes ||
|
|
88
113
|
pixels > MATERIAL_LIBRARY_LIMITS.maxPayloadPixels)
|
|
89
114
|
throw new Error('Asset material closure exceeds allocation limits');
|
|
115
|
+
const visited = new Set();
|
|
116
|
+
const active = new Set();
|
|
117
|
+
const visit = (key) => {
|
|
118
|
+
if (active.has(key))
|
|
119
|
+
throw new Error('Asset material closure contains a cycle');
|
|
120
|
+
if (visited.has(key))
|
|
121
|
+
return;
|
|
122
|
+
const manifest = available.get(key);
|
|
123
|
+
if (!manifest)
|
|
124
|
+
throw new Error('Asset material closure incomplete');
|
|
125
|
+
active.add(key);
|
|
126
|
+
for (const pin of materialRecordDependencyPins(manifest))
|
|
127
|
+
visit(`${pin.resourceId}/${pin.revisionId}`);
|
|
128
|
+
active.delete(key);
|
|
129
|
+
visited.add(key);
|
|
130
|
+
};
|
|
131
|
+
for (const key of expected.keys())
|
|
132
|
+
visit(key);
|
|
133
|
+
if (visited.size !== available.size)
|
|
134
|
+
throw new Error('Asset material closure has unrelated records');
|
|
90
135
|
}
|
|
91
136
|
const allowedFiles = new Set(['asset.glb', 'source.kiln.js', 'preview.png']);
|
|
92
137
|
export function validateRecordShape(record) {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Internal atlas metadata owner. TextureInfo, rather than an image, owns each sampler use. */
|
|
2
|
+
import type { Document, Texture as GltfTexture, TextureInfo } from '@gltf-transform/core';
|
|
3
|
+
import * as THREE from 'three';
|
|
4
|
+
import { type CanonicalTextureAtlasSpecV1 } from "./texture-atlas-contract.js";
|
|
5
|
+
import type { TextureAtlasManifestV1 } from "./texture-atlas-pixels.js";
|
|
6
|
+
export declare const ATLAS_PROVENANCE_VERSION: 'kiln.atlas-provenance.v1';
|
|
7
|
+
export declare const ATLAS_PROVENANCE_LIMITS: Readonly<{
|
|
8
|
+
atlases: 128;
|
|
9
|
+
bytes: 4194304;
|
|
10
|
+
bindings: 1024;
|
|
11
|
+
entryBytes: 262144;
|
|
12
|
+
encodedBytes: 16777216;
|
|
13
|
+
aggregateEncodedBytes: 67108864;
|
|
14
|
+
bakeDecodedBytes: 67108864;
|
|
15
|
+
}>;
|
|
16
|
+
export interface AtlasProvenanceV1 {
|
|
17
|
+
spec: CanonicalTextureAtlasSpecV1;
|
|
18
|
+
manifest: TextureAtlasManifestV1;
|
|
19
|
+
}
|
|
20
|
+
export interface AtlasProvenanceTableV1 {
|
|
21
|
+
version: typeof ATLAS_PROVENANCE_VERSION;
|
|
22
|
+
atlases: (AtlasProvenanceV1 & {
|
|
23
|
+
id: string;
|
|
24
|
+
})[];
|
|
25
|
+
}
|
|
26
|
+
export interface AtlasReferenceV1 {
|
|
27
|
+
version: typeof ATLAS_PROVENANCE_VERSION;
|
|
28
|
+
id: string;
|
|
29
|
+
effectiveSampler?: {
|
|
30
|
+
minFilter: number | null;
|
|
31
|
+
magFilter: number | null;
|
|
32
|
+
wrapS: number;
|
|
33
|
+
wrapT: number;
|
|
34
|
+
};
|
|
35
|
+
finalMime?: string;
|
|
36
|
+
finalImageSha256?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface AtlasBindingV1 {
|
|
39
|
+
version: typeof ATLAS_PROVENANCE_VERSION;
|
|
40
|
+
atlasId: string;
|
|
41
|
+
region: string;
|
|
42
|
+
inputOrigin: 'top-left' | 'bottom-left';
|
|
43
|
+
mode: 'content-boundary' | 'texel-center';
|
|
44
|
+
}
|
|
45
|
+
/** Reads only engine metadata data descriptors; author metadata and encoded arrays are not serialized. */
|
|
46
|
+
export declare function readAtlasTextureProvenance(texture: THREE.Texture): AtlasProvenanceV1 | undefined;
|
|
47
|
+
export declare function validateAtlasProvenanceTable(input: unknown): AtlasProvenanceTableV1;
|
|
48
|
+
export declare function expandAtlasBinding(input: unknown, table: AtlasProvenanceTableV1): {
|
|
49
|
+
manifestHash: `sha256:${string}`;
|
|
50
|
+
specHash: `sha256:${string}`;
|
|
51
|
+
layoutHash: `sha256:${string}`;
|
|
52
|
+
region: string;
|
|
53
|
+
content: import("./texture-atlas-contract.js").AtlasRectangleV1;
|
|
54
|
+
allocation: import("./texture-atlas-contract.js").AtlasRectangleV1;
|
|
55
|
+
inputOrigin: unknown;
|
|
56
|
+
mode: unknown;
|
|
57
|
+
scale: number[];
|
|
58
|
+
offset: number[];
|
|
59
|
+
};
|
|
60
|
+
/** A geometry-wide stamp can be associated only with the exact primary atlas supplied here. */
|
|
61
|
+
export declare function readAtlasGeometryBinding(geometry: THREE.BufferGeometry, provenance: AtlasProvenanceV1): AtlasBindingV1 | undefined;
|
|
62
|
+
export interface AtlasProvenanceCollection {
|
|
63
|
+
table?: AtlasProvenanceTableV1;
|
|
64
|
+
texture(texture: THREE.Texture): AtlasReferenceV1 | undefined;
|
|
65
|
+
binding(geometry: THREE.BufferGeometry): AtlasBindingV1 | undefined;
|
|
66
|
+
}
|
|
67
|
+
export declare function collectAtlasProvenance(root: THREE.Object3D): AtlasProvenanceCollection;
|
|
68
|
+
export declare function atlasDocumentTable(doc: Document): AtlasProvenanceTableV1 | undefined;
|
|
69
|
+
export declare function atlasMaterialUses(doc: Document): {
|
|
70
|
+
texture: GltfTexture;
|
|
71
|
+
info: TextureInfo;
|
|
72
|
+
}[];
|
|
73
|
+
/** Export/optimization only: bind source identity to the final encoded bytes and actual per-use sampler. */
|
|
74
|
+
export declare function refreshAtlasDocumentProvenance(doc: Document): Promise<void>;
|
|
75
|
+
/** Import is validation, never a repair or identity refresh. */
|
|
76
|
+
export declare function validateAtlasDocumentProvenance(doc: Document): Promise<void>;
|
|
77
|
+
/** Host-only owned identity established by the validated GLB loader; no serialized skip flag. */
|
|
78
|
+
export declare function readValidatedAtlasDelivery(texture: THREE.Texture): Readonly<AtlasReferenceV1> | undefined;
|
|
79
|
+
export declare function readAtlasEncodedTexture(texture: THREE.Texture): {
|
|
80
|
+
mime: 'image/png';
|
|
81
|
+
bytes: Uint8Array;
|
|
82
|
+
} | undefined;
|
|
83
|
+
/** Called only after validateAtlasDocumentProvenance; each sampler use gets owned Three state. */
|
|
84
|
+
export declare function restoreAtlasTextureProvenance(texture: THREE.Texture, info: TextureInfo, table: AtlasProvenanceTableV1): void;
|
|
85
|
+
/** Compose before mergeDocuments copies image/geometry buffers or deletes the source scene. */
|
|
86
|
+
export declare function mergeAtlasDocumentProvenance(target: Document, source: Document): Promise<void>;
|