@forgeax/engine-pack 0.1.32 → 0.1.34
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 +1 -1
- package/dist/build.mjs +26 -11
- package/dist/build.mjs.map +1 -1
- package/dist/cli-asset.mjs +4 -4
- package/dist/cli-asset.mjs.map +1 -1
- package/dist/evidence/material-cook.d.ts +2 -1
- package/dist/evidence/material-cook.d.ts.map +1 -1
- package/dist/index.mjs +11 -6
- package/dist/index.mjs.map +1 -1
- package/dist/material-cook.mjs +7 -2
- package/dist/material-cook.mjs.map +1 -1
- package/dist/pack-authoring-node.d.ts.map +1 -1
- package/dist/pack-authoring-node.mjs +19 -9
- package/dist/pack-authoring-node.mjs.map +1 -1
- package/dist/pack-authoring.d.ts.map +1 -1
- package/dist/pack-authoring.mjs +4 -4
- package/dist/pack-authoring.mjs.map +1 -1
- package/dist/scanner.mjs +4 -4
- package/dist/scanner.mjs.map +1 -1
- package/dist/scriptable-pack-node.mjs +4 -4
- package/dist/scriptable-pack-node.mjs.map +1 -1
- package/dist/scriptable-pack.mjs +4 -4
- package/dist/scriptable-pack.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/ai-recovery.unit.test.ts +4 -1
- package/src/__tests__/material-cook-schema.unit.test.ts +26 -0
- package/src/__tests__/pack-authoring-gateway.unit.test.ts +71 -1
- package/src/__tests__/pack-authoring.unit.test.ts +26 -0
- package/src/evidence/material-cook.ts +9 -1
- package/src/pack-authoring-node.ts +20 -4
- package/src/pack-authoring.ts +8 -4
- package/src/schema/material-cook.schema.json +74 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
1
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
@@ -296,3 +296,73 @@ describe('Pack authoring gateway', () => {
|
|
|
296
296
|
expect(verified).toMatchObject({ ok: true, value: { summary: { assetCount: 1 } } });
|
|
297
297
|
});
|
|
298
298
|
});
|
|
299
|
+
|
|
300
|
+
it('inspects the exact source revision used by rebuild conflict checks', async () => {
|
|
301
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-inspect-revision-'));
|
|
302
|
+
roots.push(root);
|
|
303
|
+
const outputs = [
|
|
304
|
+
{
|
|
305
|
+
packageId: SOURCE_PACKAGE,
|
|
306
|
+
sourceKey: 'mesh/main',
|
|
307
|
+
guid: AssetGuid.format(AssetGuid.derive(packageId(SOURCE_PACKAGE), 'mesh/main')),
|
|
308
|
+
kind: 'mesh',
|
|
309
|
+
sourcePath: 'assets/source.pack.ts',
|
|
310
|
+
ready: true,
|
|
311
|
+
},
|
|
312
|
+
];
|
|
313
|
+
let published = false;
|
|
314
|
+
const gateway = createFileSystemPackAuthoringGateway({
|
|
315
|
+
gameRoot: root,
|
|
316
|
+
materialized: () => (published ? outputs : []),
|
|
317
|
+
rebuild: async () => ({ ok: true, value: undefined }),
|
|
318
|
+
});
|
|
319
|
+
const created = await gateway.execute({
|
|
320
|
+
operation: 'asset-source.create',
|
|
321
|
+
requestId: 'create',
|
|
322
|
+
targetPath: 'assets/source.pack.ts',
|
|
323
|
+
format: 'pack.ts',
|
|
324
|
+
packageId: SOURCE_PACKAGE,
|
|
325
|
+
});
|
|
326
|
+
expect(created.ok).toBe(true);
|
|
327
|
+
if (!created.ok) return;
|
|
328
|
+
published = true;
|
|
329
|
+
const inspected = await gateway.execute({
|
|
330
|
+
operation: 'asset.inspect',
|
|
331
|
+
subject: 'assets/source.pack.ts',
|
|
332
|
+
requestId: 'inspect',
|
|
333
|
+
});
|
|
334
|
+
expect(inspected).toMatchObject({
|
|
335
|
+
ok: true,
|
|
336
|
+
value: {
|
|
337
|
+
revision: created.value.revision,
|
|
338
|
+
sourcePath: 'assets/source.pack.ts',
|
|
339
|
+
assets: outputs,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
const rebuilt = await gateway.execute({
|
|
343
|
+
operation: 'asset-source.rebuild',
|
|
344
|
+
sourcePath: 'assets/source.pack.ts',
|
|
345
|
+
expectedRevision: created.value.revision,
|
|
346
|
+
requestId: 'rebuild',
|
|
347
|
+
});
|
|
348
|
+
expect(rebuilt).toMatchObject({
|
|
349
|
+
ok: true,
|
|
350
|
+
value: { assets: outputs, revision: created.value.revision },
|
|
351
|
+
});
|
|
352
|
+
const path = join(root, 'assets/source.pack.ts');
|
|
353
|
+
await writeFile(path, `${await readFile(path, 'utf8')}\n// source edit\n`);
|
|
354
|
+
const changed = await gateway.execute({
|
|
355
|
+
operation: 'asset.inspect',
|
|
356
|
+
subject: 'assets/source.pack.ts',
|
|
357
|
+
requestId: 'inspect-edited',
|
|
358
|
+
});
|
|
359
|
+
expect(changed.ok).toBe(true);
|
|
360
|
+
if (changed.ok) expect(changed.value.revision).not.toBe(created.value.revision);
|
|
361
|
+
const stale = await gateway.execute({
|
|
362
|
+
operation: 'asset-source.rebuild',
|
|
363
|
+
sourcePath: 'assets/source.pack.ts',
|
|
364
|
+
expectedRevision: created.value.revision,
|
|
365
|
+
requestId: 'stale',
|
|
366
|
+
});
|
|
367
|
+
expect(stale).toMatchObject({ ok: false, error: { code: 'pack-source-revision-conflict' } });
|
|
368
|
+
});
|
|
@@ -21,6 +21,32 @@ function packageId(value: string) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
describe('ScriptablePack and Pack authoring', () => {
|
|
24
|
+
it('accepts boolean and string defaults and overrides without coercion', () => {
|
|
25
|
+
const definition = definePack({
|
|
26
|
+
schemaVersion: '2.0.0',
|
|
27
|
+
packageId: definePackageId('01900000-0000-7000-8000-000000000031'),
|
|
28
|
+
parameters: [
|
|
29
|
+
{ name: 'animated', type: 'bool', default: true },
|
|
30
|
+
{ name: 'label', type: 'string', default: 'smoke' },
|
|
31
|
+
],
|
|
32
|
+
build: () => ok({ 'scene/main': { kind: 'scene', entities: {} } }),
|
|
33
|
+
});
|
|
34
|
+
expect(resolvePackParameterValues(definition, {})).toMatchObject({
|
|
35
|
+
ok: true,
|
|
36
|
+
value: { animated: true, label: 'smoke' },
|
|
37
|
+
});
|
|
38
|
+
expect(resolvePackParameterValues(definition, { animated: false, label: '' })).toMatchObject({
|
|
39
|
+
ok: true,
|
|
40
|
+
value: { animated: false, label: '' },
|
|
41
|
+
});
|
|
42
|
+
for (const values of [{ animated: 'false' }, { animated: 0 }, { label: false }, { label: 0 }]) {
|
|
43
|
+
expect(resolvePackParameterValues(definition, values)).toMatchObject({
|
|
44
|
+
ok: false,
|
|
45
|
+
error: { code: 'pack-parameter-invalid' },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
24
50
|
it('derives the RFC 4122 UUIDv5 vector and keeps PackageId branded separately', () => {
|
|
25
51
|
const namespace = packageId(NAMESPACE);
|
|
26
52
|
expect(AssetGuid.format(AssetGuid.derive(namespace, 'www.widgets.com'))).toBe(
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
MaterialPass,
|
|
5
5
|
MaterialProgramAbi,
|
|
6
6
|
MaterialProgramAddress,
|
|
7
|
+
MaterialSurfaceDeclaration,
|
|
7
8
|
MaterialTextureReference,
|
|
8
9
|
MaterialTextureValue,
|
|
9
10
|
MaterialValue,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
deriveStandardLayerPlan,
|
|
14
15
|
err,
|
|
15
16
|
isMaterialProgramAbi,
|
|
17
|
+
isMaterialSurfaceDeclaration,
|
|
16
18
|
MATERIAL_TEXTURE_SLOTS,
|
|
17
19
|
ok,
|
|
18
20
|
} from '@forgeax/engine-types';
|
|
@@ -120,6 +122,7 @@ export interface CookedMaterialRecord {
|
|
|
120
122
|
readonly passes: readonly MaterialPass[];
|
|
121
123
|
readonly parameters: readonly MaterialParameter[];
|
|
122
124
|
readonly values: Readonly<Record<string, MaterialValue | null>>;
|
|
125
|
+
readonly surface?: MaterialSurfaceDeclaration;
|
|
123
126
|
};
|
|
124
127
|
readonly refs: MaterialCookRefs;
|
|
125
128
|
readonly receipt: MaterialCookReceipt;
|
|
@@ -231,7 +234,10 @@ export function collectMaterialCookRefs(material: Partial<MaterialAsset>): Mater
|
|
|
231
234
|
return guid === undefined ? [] : [guid];
|
|
232
235
|
}),
|
|
233
236
|
),
|
|
234
|
-
modules: unique(
|
|
237
|
+
modules: unique([
|
|
238
|
+
...(material.passes ?? []).map((pass) => pass.program.module),
|
|
239
|
+
...(material.surface === undefined ? [] : [material.surface.module]),
|
|
240
|
+
]),
|
|
235
241
|
};
|
|
236
242
|
}
|
|
237
243
|
|
|
@@ -476,6 +482,8 @@ export function validateCookedMaterialRecord(
|
|
|
476
482
|
Array.isArray(resolved.values)
|
|
477
483
|
)
|
|
478
484
|
return invalid('resolved.values', resolved.values);
|
|
485
|
+
if (resolved.surface !== undefined && !isMaterialSurfaceDeclaration(resolved.surface))
|
|
486
|
+
return invalid('resolved.surface', resolved.surface);
|
|
479
487
|
const passes = resolved.passes as MaterialPass[];
|
|
480
488
|
const passNames = new Set<string>();
|
|
481
489
|
for (const [index, pass] of passes.entries()) {
|
|
@@ -817,6 +817,19 @@ async function inspectAsset(
|
|
|
817
817
|
const selected = subjectFor(gameRoot, snapshot, operation);
|
|
818
818
|
if (!selected.ok) return selected;
|
|
819
819
|
const subject = selected.value;
|
|
820
|
+
const source = await readConfined({ gameRoot }, operation, subject.relativePath);
|
|
821
|
+
if (!source.ok) return source;
|
|
822
|
+
const inspectedSource = {
|
|
823
|
+
...sourceResult(subject, operation),
|
|
824
|
+
revision: source.value.revision,
|
|
825
|
+
...(subject.format === 'source'
|
|
826
|
+
? {
|
|
827
|
+
assets: snapshot.materialized
|
|
828
|
+
.filter((asset) => asset.packageId === packageKey(subject.packageId))
|
|
829
|
+
.map((asset) => ({ ...asset })),
|
|
830
|
+
}
|
|
831
|
+
: {}),
|
|
832
|
+
};
|
|
820
833
|
if (operation.sourceKey !== undefined) {
|
|
821
834
|
if (!isValidPackSourceKey(operation.sourceKey)) {
|
|
822
835
|
return err(
|
|
@@ -851,7 +864,7 @@ async function inspectAsset(
|
|
|
851
864
|
candidate.sourceKey === operation.sourceKey,
|
|
852
865
|
);
|
|
853
866
|
return ok({
|
|
854
|
-
...
|
|
867
|
+
...inspectedSource,
|
|
855
868
|
sourceKey: asset.sourceKey,
|
|
856
869
|
guid: asset.guid,
|
|
857
870
|
kind: asset.kind,
|
|
@@ -871,7 +884,7 @@ async function inspectAsset(
|
|
|
871
884
|
candidate.sourceKey === operation.sourceKey,
|
|
872
885
|
);
|
|
873
886
|
return ok({
|
|
874
|
-
...
|
|
887
|
+
...inspectedSource,
|
|
875
888
|
sourceKey: operation.sourceKey,
|
|
876
889
|
guid,
|
|
877
890
|
...(materialized === undefined
|
|
@@ -897,7 +910,7 @@ async function inspectAsset(
|
|
|
897
910
|
const resolved = await resolveInstance(snapshot, subject);
|
|
898
911
|
if (!resolved.ok) return resolved;
|
|
899
912
|
return ok({
|
|
900
|
-
...
|
|
913
|
+
...inspectedSource,
|
|
901
914
|
parameters: resolved.value.parameters.map((parameter) => ({
|
|
902
915
|
name: parameter.name,
|
|
903
916
|
type: parameter.type,
|
|
@@ -911,7 +924,7 @@ async function inspectAsset(
|
|
|
911
924
|
parentChain: resolved.value.parentChain,
|
|
912
925
|
});
|
|
913
926
|
}
|
|
914
|
-
return ok(
|
|
927
|
+
return ok(inspectedSource);
|
|
915
928
|
}
|
|
916
929
|
|
|
917
930
|
function listAssets(
|
|
@@ -2188,6 +2201,9 @@ async function executeRaw(
|
|
|
2188
2201
|
selected.value.packageId,
|
|
2189
2202
|
{
|
|
2190
2203
|
format: refreshed.value.relative.endsWith('.pack.json') ? 'pack.json' : 'pack.ts',
|
|
2204
|
+
assets: refreshedSnapshot.value.materialized
|
|
2205
|
+
.filter((asset) => asset.packageId === packageKey(selected.value.packageId))
|
|
2206
|
+
.map((asset) => ({ ...asset })),
|
|
2191
2207
|
},
|
|
2192
2208
|
);
|
|
2193
2209
|
}
|
package/src/pack-authoring.ts
CHANGED
|
@@ -256,8 +256,10 @@ function parameterValueError(
|
|
|
256
256
|
propertyPath: string,
|
|
257
257
|
): PackAuthoringError | undefined {
|
|
258
258
|
const { type } = parameter;
|
|
259
|
-
if (type === 'bool'
|
|
260
|
-
return
|
|
259
|
+
if (type === 'bool') {
|
|
260
|
+
return typeof value === 'boolean'
|
|
261
|
+
? undefined
|
|
262
|
+
: parameterFailure(propertyPath, 'a boolean', value, 'bool value has the wrong type');
|
|
261
263
|
}
|
|
262
264
|
if (numericType(type)) {
|
|
263
265
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
@@ -318,8 +320,10 @@ function parameterValueError(
|
|
|
318
320
|
}
|
|
319
321
|
return undefined;
|
|
320
322
|
}
|
|
321
|
-
if (type === 'string'
|
|
322
|
-
return
|
|
323
|
+
if (type === 'string') {
|
|
324
|
+
return typeof value === 'string'
|
|
325
|
+
? undefined
|
|
326
|
+
: parameterFailure(propertyPath, 'a string', value, 'string parameter has the wrong type');
|
|
323
327
|
}
|
|
324
328
|
if (type === 'enum') {
|
|
325
329
|
if (typeof value !== 'string') {
|
|
@@ -52,7 +52,80 @@
|
|
|
52
52
|
},
|
|
53
53
|
"resolved": {
|
|
54
54
|
"type": "object",
|
|
55
|
-
"required": ["passes", "parameters", "values"]
|
|
55
|
+
"required": ["passes", "parameters", "values"],
|
|
56
|
+
"properties": {
|
|
57
|
+
"surface": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"required": ["model", "module"],
|
|
60
|
+
"properties": {
|
|
61
|
+
"model": {
|
|
62
|
+
"enum": ["standard", "single-layer-medium"]
|
|
63
|
+
},
|
|
64
|
+
"module": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"minLength": 1
|
|
67
|
+
},
|
|
68
|
+
"dynamicInput": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"required": [
|
|
71
|
+
"name",
|
|
72
|
+
"fields",
|
|
73
|
+
"maxRecords",
|
|
74
|
+
"maxDomains",
|
|
75
|
+
"maxPageBytes",
|
|
76
|
+
"maxBindings",
|
|
77
|
+
"maxEventsPerSample"
|
|
78
|
+
],
|
|
79
|
+
"properties": {
|
|
80
|
+
"name": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
83
|
+
},
|
|
84
|
+
"fields": {
|
|
85
|
+
"type": "array",
|
|
86
|
+
"minItems": 1,
|
|
87
|
+
"items": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"required": ["name", "type"],
|
|
90
|
+
"properties": {
|
|
91
|
+
"name": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
94
|
+
},
|
|
95
|
+
"type": {
|
|
96
|
+
"enum": ["f32", "u32", "vec2<f32>", "vec3<f32>", "vec4<f32>"]
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"additionalProperties": false
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"maxRecords": {
|
|
103
|
+
"type": "integer",
|
|
104
|
+
"minimum": 1
|
|
105
|
+
},
|
|
106
|
+
"maxDomains": {
|
|
107
|
+
"type": "integer",
|
|
108
|
+
"minimum": 1
|
|
109
|
+
},
|
|
110
|
+
"maxPageBytes": {
|
|
111
|
+
"type": "integer",
|
|
112
|
+
"minimum": 1
|
|
113
|
+
},
|
|
114
|
+
"maxBindings": {
|
|
115
|
+
"type": "integer",
|
|
116
|
+
"minimum": 1
|
|
117
|
+
},
|
|
118
|
+
"maxEventsPerSample": {
|
|
119
|
+
"type": "integer",
|
|
120
|
+
"minimum": 1
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"additionalProperties": false
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"additionalProperties": false
|
|
127
|
+
}
|
|
128
|
+
}
|
|
56
129
|
},
|
|
57
130
|
"refs": {
|
|
58
131
|
"type": "object",
|