@forgeax/engine-assets-runtime 0.1.7 → 0.1.20
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/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/texture-pack-loader.unit.test.d.ts +2 -0
- package/dist/__tests__/texture-pack-loader.unit.test.d.ts.map +1 -0
- package/dist/decode-image-bytes.d.ts +2 -2
- package/dist/decode-image-bytes.d.ts.map +1 -1
- package/dist/errors/asset.d.ts +20 -0
- package/dist/errors/asset.d.ts.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +264 -198
- package/dist/index.mjs.map +1 -1
- package/dist/loaders/pack-artifact.d.ts +25 -1
- package/dist/loaders/pack-artifact.d.ts.map +1 -1
- package/dist/mipmap-generator.d.ts +3 -1
- package/dist/mipmap-generator.d.ts.map +1 -1
- package/dist/registry/instantiate.d.ts +2 -0
- package/dist/registry/instantiate.d.ts.map +1 -1
- package/dist/registry/validate-material.d.ts +26 -0
- package/dist/registry/validate-material.d.ts.map +1 -1
- package/package.json +14 -14
- package/src/__tests__/catalog-replica.unit.test.ts +36 -0
- package/src/__tests__/decode-image-bytes.browser.test.ts +5 -8
- package/src/__tests__/inline-pack-loaders.unit.test.ts +37 -0
- package/src/__tests__/instantiate-publication-fence.unit.test.ts +59 -1
- package/src/__tests__/material-ready.integration.test.ts +65 -0
- package/src/__tests__/pack-basis-load.integration.test.ts +66 -7
- package/src/__tests__/texture-pack-loader.unit.test.ts +145 -0
- package/src/__tests__/validate-material.unit.test.ts +45 -0
- package/src/decode-image-bytes.ts +6 -11
- package/src/errors/asset.ts +33 -0
- package/src/index.ts +7 -2
- package/src/loaders/pack-artifact.ts +218 -17
- package/src/mipmap-generator.ts +43 -1
- package/src/registry/instantiate.ts +14 -4
- package/src/registry/validate-material.ts +148 -0
|
@@ -78,16 +78,26 @@ function catalogEntriesForFence(registry: AssetRegistry): readonly CatalogEntry[
|
|
|
78
78
|
if (record.sourcePath === undefined) continue;
|
|
79
79
|
const candidate = { ...record, guid, sourcePath: record.sourcePath } as CatalogEntry;
|
|
80
80
|
const current = byGuid.get(guid.toLowerCase());
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
if (current === undefined || candidate.publication !== undefined) {
|
|
82
|
+
// A persisted Scene fence is captured from the complete pack-index
|
|
83
|
+
// publication at save time. If the CatalogReplica is still carrying its
|
|
84
|
+
// previous tuple during the watcher handoff, keep the same pack-index
|
|
85
|
+
// authority here or the freshly reopened scene will reject its own
|
|
86
|
+
// mount with asset-generation-fence-mismatch.
|
|
85
87
|
byGuid.set(guid.toLowerCase(), candidate);
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
return [...byGuid.values()];
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
/** Derive a publication fence from the registry's unified live projections. */
|
|
94
|
+
export function scenePublicationFenceFromRegistry(
|
|
95
|
+
registry: AssetRegistry,
|
|
96
|
+
outputGuid: string,
|
|
97
|
+
): Result<ScenePublicationFence, ScenePublicationFenceError> {
|
|
98
|
+
return scenePublicationFenceFromCatalog(catalogEntriesForFence(registry), outputGuid);
|
|
99
|
+
}
|
|
100
|
+
|
|
91
101
|
/**
|
|
92
102
|
* Post-spawn hook contract (D-1). A hook runs after instantiate spawns the
|
|
93
103
|
* scene subtree; the shipped implementation is runtime's `postSpawnResolveJoints`
|
|
@@ -10,6 +10,60 @@ import {
|
|
|
10
10
|
} from '@forgeax/engine-types';
|
|
11
11
|
import type { AssetRegistry } from '../asset-registry';
|
|
12
12
|
|
|
13
|
+
export interface TexturePublicationStateSnapshot {
|
|
14
|
+
readonly guid: string;
|
|
15
|
+
readonly sourceKey: string;
|
|
16
|
+
readonly acceptedGeneration: number;
|
|
17
|
+
readonly acceptedDigest: string;
|
|
18
|
+
readonly candidateGeneration?: number;
|
|
19
|
+
readonly candidateFailure?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TexturePublicationState {
|
|
23
|
+
reject(candidate: {
|
|
24
|
+
readonly generation: number;
|
|
25
|
+
readonly reason: string;
|
|
26
|
+
}): TexturePublicationStateSnapshot;
|
|
27
|
+
accept(candidate: {
|
|
28
|
+
readonly generation: number;
|
|
29
|
+
readonly digest: string;
|
|
30
|
+
}): TexturePublicationStateSnapshot;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Candidate/accepted state that keeps the last known good publication honest. */
|
|
34
|
+
export function createTexturePublicationState(input: {
|
|
35
|
+
readonly guid: string;
|
|
36
|
+
readonly sourceKey: string;
|
|
37
|
+
readonly generation: number;
|
|
38
|
+
readonly digest: string;
|
|
39
|
+
}): TexturePublicationState {
|
|
40
|
+
let accepted = {
|
|
41
|
+
generation: input.generation,
|
|
42
|
+
digest: input.digest,
|
|
43
|
+
};
|
|
44
|
+
let candidate: { readonly generation: number; readonly reason: string } | undefined;
|
|
45
|
+
const snapshot = (): TexturePublicationStateSnapshot => ({
|
|
46
|
+
guid: input.guid,
|
|
47
|
+
sourceKey: input.sourceKey,
|
|
48
|
+
acceptedGeneration: accepted.generation,
|
|
49
|
+
acceptedDigest: accepted.digest,
|
|
50
|
+
...(candidate === undefined
|
|
51
|
+
? {}
|
|
52
|
+
: { candidateGeneration: candidate.generation, candidateFailure: candidate.reason }),
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
reject(next) {
|
|
56
|
+
candidate = next;
|
|
57
|
+
return snapshot();
|
|
58
|
+
},
|
|
59
|
+
accept(next) {
|
|
60
|
+
accepted = next;
|
|
61
|
+
candidate = undefined;
|
|
62
|
+
return snapshot();
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
13
67
|
/**
|
|
14
68
|
* Validate a MaterialAsset's passes[] against the ShaderRegistry's
|
|
15
69
|
* paramSchema (union semantics: all declared params across all passes
|
|
@@ -112,6 +166,100 @@ export function validateMaterialPasses(
|
|
|
112
166
|
return null;
|
|
113
167
|
}
|
|
114
168
|
|
|
169
|
+
type TransmissionParameter =
|
|
170
|
+
| 'transmission'
|
|
171
|
+
| 'ior'
|
|
172
|
+
| 'thickness'
|
|
173
|
+
| 'attenuationColor'
|
|
174
|
+
| 'attenuationDistance'
|
|
175
|
+
| 'transmissionTexture'
|
|
176
|
+
| 'thicknessTexture';
|
|
177
|
+
|
|
178
|
+
function transmissionError(
|
|
179
|
+
material: string,
|
|
180
|
+
parameter: TransmissionParameter,
|
|
181
|
+
reason: 'non-finite' | 'range' | 'shape' | 'blend' | 'depth-write',
|
|
182
|
+
actual?: unknown,
|
|
183
|
+
): MaterialError {
|
|
184
|
+
return createMaterialError('material-transmission-contract-invalid', {
|
|
185
|
+
code: 'material-transmission-contract-invalid',
|
|
186
|
+
material,
|
|
187
|
+
parameter,
|
|
188
|
+
reason,
|
|
189
|
+
...(actual === undefined ? {} : { actual }),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function validateTransmissionNumber(
|
|
194
|
+
material: string,
|
|
195
|
+
values: Record<string, unknown>,
|
|
196
|
+
parameter: 'transmission' | 'ior' | 'thickness' | 'attenuationDistance',
|
|
197
|
+
minimum: number,
|
|
198
|
+
maximum?: number,
|
|
199
|
+
): MaterialError | null {
|
|
200
|
+
const value = values[parameter];
|
|
201
|
+
if (value === undefined) return null;
|
|
202
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
203
|
+
return transmissionError(material, parameter, 'non-finite', value);
|
|
204
|
+
}
|
|
205
|
+
if (value < minimum || (maximum !== undefined && value > maximum)) {
|
|
206
|
+
return transmissionError(material, parameter, 'range', value);
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function validateMaterialTransmissionContract(asset: MaterialAsset): MaterialError | null {
|
|
212
|
+
const material = (asset.values?.materialName as string | undefined) ?? '<unnamed>';
|
|
213
|
+
const values = (asset.values ?? {}) as Record<string, unknown>;
|
|
214
|
+
const numericChecks: ReadonlyArray<
|
|
215
|
+
['transmission' | 'ior' | 'thickness' | 'attenuationDistance', number, number | undefined]
|
|
216
|
+
> = [
|
|
217
|
+
['transmission', 0, 1],
|
|
218
|
+
['ior', 1, undefined],
|
|
219
|
+
['thickness', 0, undefined],
|
|
220
|
+
['attenuationDistance', Number.MIN_VALUE, undefined],
|
|
221
|
+
];
|
|
222
|
+
for (const [parameter, minimum, maximum] of numericChecks) {
|
|
223
|
+
const error = validateTransmissionNumber(material, values, parameter, minimum, maximum);
|
|
224
|
+
if (error !== null) return error;
|
|
225
|
+
}
|
|
226
|
+
const attenuationColor = values.attenuationColor;
|
|
227
|
+
if (attenuationColor !== undefined) {
|
|
228
|
+
if (
|
|
229
|
+
!Array.isArray(attenuationColor) ||
|
|
230
|
+
attenuationColor.length !== 3 ||
|
|
231
|
+
attenuationColor.some(
|
|
232
|
+
(value) => typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1,
|
|
233
|
+
)
|
|
234
|
+
) {
|
|
235
|
+
return transmissionError(material, 'attenuationColor', 'shape', attenuationColor);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const parameter of ['transmissionTexture', 'thicknessTexture'] as const) {
|
|
239
|
+
const value = values[parameter];
|
|
240
|
+
if (
|
|
241
|
+
value !== undefined &&
|
|
242
|
+
typeof value !== 'string' &&
|
|
243
|
+
!(typeof value === 'number' && Number.isInteger(value) && value >= 0) &&
|
|
244
|
+
(typeof value !== 'object' || value === null || Array.isArray(value))
|
|
245
|
+
) {
|
|
246
|
+
return transmissionError(material, parameter, 'shape', value);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const transmission = values.transmission;
|
|
250
|
+
if (typeof transmission === 'number' && transmission > 0) {
|
|
251
|
+
for (const pass of asset.passes ?? []) {
|
|
252
|
+
if (pass.renderState?.blend !== undefined) {
|
|
253
|
+
return transmissionError(material, 'transmission', 'blend');
|
|
254
|
+
}
|
|
255
|
+
if (pass.renderState?.depthWriteEnabled === true) {
|
|
256
|
+
return transmissionError(material, 'transmission', 'depth-write');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
115
263
|
export interface MaterialEffectiveRootIdentity {
|
|
116
264
|
readonly guid: string;
|
|
117
265
|
readonly layoutIdentity: string;
|