@forgeax/engine-pack 0.1.27 → 0.1.29

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 (52) hide show
  1. package/README.md +30 -17
  2. package/dist/build.mjs +35 -52
  3. package/dist/build.mjs.map +1 -1
  4. package/dist/cli-asset.d.ts +1 -1
  5. package/dist/cli-asset.d.ts.map +1 -1
  6. package/dist/cli-asset.mjs +40 -33
  7. package/dist/cli-asset.mjs.map +1 -1
  8. package/dist/index.mjs +1 -1
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/inventory/declaration.d.ts +1 -1
  11. package/dist/inventory/declaration.d.ts.map +1 -1
  12. package/dist/inventory/sync.d.ts.map +1 -1
  13. package/dist/native-cooker-registry.d.ts +1 -0
  14. package/dist/native-cooker-registry.d.ts.map +1 -1
  15. package/dist/native-cooker.mjs.map +1 -1
  16. package/dist/pack-authoring-node.mjs +22 -13
  17. package/dist/pack-authoring-node.mjs.map +1 -1
  18. package/dist/runtime.mjs +1 -1
  19. package/dist/runtime.mjs.map +1 -1
  20. package/dist/scanner.mjs +22 -13
  21. package/dist/scanner.mjs.map +1 -1
  22. package/dist/schema-compiled.d.ts.map +1 -1
  23. package/dist/schema.mjs +41 -63
  24. package/dist/schema.mjs.map +1 -1
  25. package/dist/scriptable-pack-node.d.ts.map +1 -1
  26. package/dist/scriptable-pack-node.mjs +11 -3
  27. package/dist/scriptable-pack-node.mjs.map +1 -1
  28. package/dist/scriptable-pack-worker.mjs +5 -1
  29. package/dist/scriptable-pack-worker.mjs.map +1 -1
  30. package/package.json +3 -6
  31. package/schema/meta.schema.json +1 -1
  32. package/src/__tests__/atlas-cli-region-mismatch.test.ts +1 -1
  33. package/src/__tests__/cli.unit.test.ts +1 -1
  34. package/src/__tests__/inventory-schema.test.ts +10 -10
  35. package/src/__tests__/pack-authoring.unit.test.ts +2 -4
  36. package/src/__tests__/pack.unit.test.ts +89 -127
  37. package/src/__tests__/scanner-instance-cycle.test.ts +92 -0
  38. package/src/__tests__/scanner-inventory.contract.test.ts +2 -2
  39. package/src/__tests__/scriptable-pack-diagnostic.integration.test.ts +45 -0
  40. package/src/atlas/run-atlas.ts +3 -3
  41. package/src/atlas/shelf-pack.ts +1 -1
  42. package/src/atlas/upng-ambient.d.ts +1 -1
  43. package/src/cli-asset.ts +22 -23
  44. package/src/inventory/binding.ts +3 -3
  45. package/src/inventory/declaration.ts +10 -48
  46. package/src/inventory/sync.ts +3 -2
  47. package/src/native-cooker-registry.ts +1 -0
  48. package/src/scanner.ts +29 -29
  49. package/src/schema-compiled.ts +40 -79
  50. package/src/scriptable-pack-node.ts +11 -3
  51. package/src/scriptable-pack-worker.ts +8 -1
  52. package/src/__tests__/scanner-mount-cycle.test.ts +0 -232
package/README.md CHANGED
@@ -37,11 +37,22 @@ export default definePack({
37
37
  build: ({ packageId: subjectId }) =>
38
38
  ok({
39
39
  'mesh/main': { kind: 'mesh', materialSlots: [] },
40
+ 'scene/hero': {
41
+ kind: 'scene',
42
+ entities: { body: { components: {} } },
43
+ },
40
44
  'scene/main': {
41
45
  kind: 'scene',
42
- entities: [],
43
- mounts: [],
44
- mesh: AssetGuid.format(AssetGuid.derive(subjectId, 'mesh/main')),
46
+ entities: {
47
+ root: { components: {} },
48
+ hero: {
49
+ components: {},
50
+ instance: {
51
+ source: AssetGuid.format(AssetGuid.derive(subjectId, 'scene/hero')),
52
+ overrides: [{ target: ['body'], components: {} }],
53
+ },
54
+ },
55
+ },
45
56
  },
46
57
  }),
47
58
  });
@@ -147,8 +158,8 @@ source.
147
158
 
148
159
  | Need | Entry | Safe action |
149
160
  |:--|:--|:--|
150
- | Inspect one asset | `forgeax-engine-remote-asset lookup --guid ... --project ... --catalog ... --json` | Read structured evidence and diagnostics |
151
- | Verify a result | `forgeax-engine-remote-asset verify --guid ... --project ... --catalog ... --json` | Repair the producer or package, then retry |
161
+ | Inspect one asset | `forgeax asset inspect --subject <guid> --root <project> --json` | Read structured evidence and diagnostics |
162
+ | Verify a result | `forgeax asset verify --root <project> --json` | Repair the producer or package, then retry |
152
163
  | Rebuild a cooked result | Pack/import runner and the declared producer | Cold cook after discarding invalid DDC |
153
164
  | Preview old output | Catalog evidence with explicit last-known-good state | Preview only; never publish it as current |
154
165
 
@@ -191,7 +202,7 @@ sampler、强度或非默认坐标等附加元数据才使用结构化纹理对
191
202
  > package moves and DDC relocation. Paths and array positions are locators,
192
203
  > not identities.
193
204
 
194
- Disk schema, GUID tools (`AssetGuid` brand + UUIDv7/v5), mesh-binary wire facts, and scanner fail-fast chain (13-member `PackErrorCode`) for the forgeax engine asset package system. The three CLI surfaces -- `scan`, `lookup`, `verify` -- are shipped as the standalone plugin bin `forgeax-engine-remote-asset` (resolved via PATH-prefix discovery for `forgeax-engine-remote-`; filesystem-mode; offline; no WS connection required).
205
+ Disk schema, GUID tools (`AssetGuid` brand + UUIDv7/v5), mesh-binary wire facts, and scanner fail-fast chain (13-member `PackErrorCode`) for the ForgeaX asset package system. The unified `asset list`, `asset inspect`, and `asset verify` commands call this owner in filesystem mode; they are offline and require no WS connection.
195
206
 
196
207
  > Package name vs directory: this package is published as `@forgeax/engine-pack` but lives at `packages/pack` on disk. The `@forgeax/engine-` prefix is the IDE-autocomplete entrypoint AI users discover the package family by; the directory drops the prefix to keep tree depth flat (mirrors the `packages/runtime` / `@forgeax/engine-runtime` pair). All other packages in the engine family follow the same convention.
197
208
 
@@ -201,9 +212,10 @@ Pack owns the offline half of the GUID evidence chain: `source inventory -> cata
201
212
 
202
213
  The catalog is a locator, not proof. `lookup/verify --guid --project --catalog --json` joins the source meta or authored pack, the catalog row, the receipt, and the package descriptors. Both commands emit one JSON record on stdout or one structured `{code, expected, hint, detail}` record on stderr; there is no runtime or WebSocket dependency.
203
214
 
204
- ## VFX Pack v2 migration
215
+ ## VFX Program v3 in Pack v2
205
216
 
206
- VFX source v2 is cooked in one atomic producer step. The executable contract is
217
+ VFX source v3 is cooked in one atomic producer step and stored in the ordinary
218
+ Pack v2 envelope. The executable contract is
207
219
  `scripts/asset-cook-contract.mjs`; a package-local scripts path is not valid.
208
220
  The cook publishes the Pack payload and
209
221
  `particle-effect/program.json` with the same source fingerprint. Runtime loads
@@ -266,7 +278,7 @@ vectors, colors, and asset-GUID constraints. The isolated Pack worker passes
266
278
  only serializable authoring data to the build and never exposes filesystem or
267
279
  runtime state.
268
280
 
269
- `forgeax-engine-remote-asset meta <source.pack.ts> --json` executes module initialization, validates the default export, and projects canonical Meta without calling `build`. `@forgeax/engine-pack/source-node` accepts a host executor with `load` and optional `dispose`; `timeoutMs` bounds module initialization and `buildTimeoutMs` bounds one `build(context)` call. A build timeout returns one structured `pack-source-load-failed` Result with `detail.phase: 'build'`, the configured `timeoutMs`, and deterministic cleanup of the isolated worker and compile root.
281
+ `forgeax asset inspect --subject <source.pack.ts> --root <project> --json` executes module initialization, validates the default export, and projects canonical Meta without calling `build`. `@forgeax/engine-pack/source-node` accepts a host executor with `load` and optional `dispose`; `timeoutMs` bounds module initialization and `buildTimeoutMs` bounds one `build(context)` call. A build timeout returns one structured `pack-source-load-failed` Result with `detail.phase: 'build'`, the configured `timeoutMs`, and deterministic cleanup of the isolated worker and compile root.
270
282
 
271
283
  The default worker executes the complete relative TypeScript module closure on the supported Node floor, including Node 22 hosts that do not load `.ts` files directly. It transpiles that closure into a disposable ESM directory, resolves bare imports through the source project's nearest `node_modules`, and removes the directory when the worker is disposed. Bulk producers use the internal `createScriptablePackModuleExecutorPool()` with two recyclable workers; a pooled lease is released after metadata projection or one build, so a generation never retains one live Worker-backed definition per source.
272
284
 
@@ -460,21 +472,22 @@ Exhaustive `switch (err.code)` without `default` -- TS guards completeness.
460
472
 
461
473
  Access `err.detail.<field>` directly after narrowing via `switch (err.code)` -- full IDE autocomplete.
462
474
 
463
- ## CLI plugin -- `forgeax-engine-remote-asset`
475
+ ## Unified asset commands
464
476
 
465
- The CLI subcommands ship as a standalone plugin bin `forgeax-engine-remote-asset` (entry `dist/cli-asset.mjs`) declared in this package's `package.json#bin`, discovered via PATH-prefix scan for `forgeax-engine-remote-`.
477
+ DevKit loads the Pack producer behind the unified `forgeax asset` command;
478
+ this package does not publish an independent executable.
466
479
 
467
480
  | Subcommand | Description | Exit code |
468
481
  |:--|:--|:--|
469
- | `forgeax-engine-remote-asset scan [--roots <dir>...]` | Print JSON array of all discovered `PackEntry` objects to stdout | 0 always |
470
- | `forgeax-engine-remote-asset lookup <guid>` | Print matching `PackEntry` as JSON to stdout (cwd as scan root) | 0 found / 1 not found |
471
- | `forgeax-engine-remote-asset verify [--strict]` | Run fail-fast 7-step scanner; print `PackError` JSON to stderr on first failure; prints `material-validated: <N>` count at end | 0 clean / 1 error |
482
+ | `forgeax asset list --root <project> --json` | Print discovered `PackEntry` objects | 0 on valid roots |
483
+ | `forgeax asset inspect --subject <guid> --root <project> --json` | Print matching asset evidence | 0 found / 1 not found |
484
+ | `forgeax asset verify --root <project> --json` | Run the fail-fast scanner and return the first structured `PackError` | 0 clean / 1 error |
472
485
 
473
486
  ```bash
474
487
  # Direct invocation (after pnpm -F @forgeax/engine-pack build)
475
- forgeax-engine-remote-asset scan --roots apps/hello/room/assets
476
- forgeax-engine-remote-asset lookup 01935f3b-aaaa-7000-8000-000000000001
477
- forgeax-engine-remote-asset verify --strict
488
+ forgeax asset list --root ./game --json
489
+ forgeax asset inspect --subject 01935f3b-aaaa-7000-8000-000000000001 --root ./game --json
490
+ forgeax asset verify --root ./game --json
478
491
  ```
479
492
 
480
493
  ## Scanner 7-step validation chain
package/dist/build.mjs CHANGED
@@ -8450,7 +8450,7 @@ var meta_schema_default = {
8450
8450
  properties: {
8451
8451
  schemaVersion: {
8452
8452
  oneOf: [{ type: "string" }, { type: "integer" }],
8453
- description: "Schema version. Strings (e.g. '1.0.0') for hand-authored .meta.json; integers (1, 2, ...) for importer-emitted .meta.json (forgeax-engine-remote-asset import writes schemaVersion: 1 as the wire format SSOT, GltfMetaJson literal type)."
8453
+ description: "Schema version. Strings (e.g. '1.0.0') for hand-authored .meta.json; integers (1, 2, ...) for importer-emitted .meta.json (forgeax asset import writes schemaVersion: 1 as the wire format SSOT, GltfMetaJson literal type)."
8454
8454
  },
8455
8455
  kind: {
8456
8456
  type: "string",
@@ -9061,9 +9061,17 @@ var WorkerScriptablePackExecutor = class {
9061
9061
  }
9062
9062
  };
9063
9063
  worker.on("message", onMessage);
9064
- worker.once("error", rejectLoad);
9064
+ const fail = (error) => {
9065
+ rejectLoad(error);
9066
+ const active = this.build;
9067
+ this.build = void 0;
9068
+ active?.reject(error);
9069
+ void this.dispose("failure");
9070
+ };
9071
+ worker.once("error", fail);
9065
9072
  worker.once("exit", (code) => {
9066
- if (code !== 0) rejectLoad(new Error(`ScriptablePack worker exited with code ${code}`));
9073
+ if (this.disposal !== void 0) return;
9074
+ fail(new Error(`ScriptablePack worker exited with code ${code}`));
9067
9075
  });
9068
9076
  });
9069
9077
  }
@@ -9298,16 +9306,17 @@ function makePackError(code, detail) {
9298
9306
  detail
9299
9307
  });
9300
9308
  }
9301
- function* extractMountSourceGuids(asset) {
9309
+ function* extractInstanceSourceGuids(asset) {
9302
9310
  if (asset.kind !== "scene") return;
9303
9311
  const payload = asset.payload;
9304
- if (!payload || !Array.isArray(payload.mounts)) return;
9305
- for (const rawMount of payload.mounts) {
9306
- const mount = rawMount;
9307
- const idx = mount.source;
9308
- if (typeof idx !== "number" || !Number.isInteger(idx)) continue;
9309
- if (idx < 0 || idx >= asset.refs.length) continue;
9310
- const resolved = asset.refs[idx];
9312
+ if (!payload || payload.entities === null || typeof payload.entities !== "object" || Array.isArray(payload.entities))
9313
+ return;
9314
+ for (const rawEntity of Object.values(payload.entities)) {
9315
+ if (rawEntity === null || typeof rawEntity !== "object" || Array.isArray(rawEntity)) continue;
9316
+ const instance = rawEntity.instance;
9317
+ if (instance === null || typeof instance !== "object" || Array.isArray(instance)) continue;
9318
+ const source = instance.source;
9319
+ const resolved = typeof source === "number" && Number.isInteger(source) ? asset.refs[source] : typeof source === "string" ? source : void 0;
9311
9320
  if (typeof resolved !== "string") continue;
9312
9321
  yield resolved.toLowerCase();
9313
9322
  }
@@ -9425,7 +9434,7 @@ async function scanValidated(roots, opts = {}, capture) {
9425
9434
  guidToPath.set(normalizedGuid, packPath);
9426
9435
  packRefs.set(normalizedGuid, [
9427
9436
  ...asset.refs.map((ref) => ref.toLowerCase()),
9428
- ...extractMountSourceGuids(asset)
9437
+ ...extractInstanceSourceGuids(asset)
9429
9438
  ]);
9430
9439
  }
9431
9440
  }
@@ -9529,7 +9538,7 @@ async function scanValidated(roots, opts = {}, capture) {
9529
9538
  guidToPath.set(normalizedGuid, packPath);
9530
9539
  packRefs.set(normalizedGuid, [
9531
9540
  ...asset.refs.map((ref) => ref.toLowerCase()),
9532
- ...extractMountSourceGuids(asset)
9541
+ ...extractInstanceSourceGuids(asset)
9533
9542
  ]);
9534
9543
  }
9535
9544
  capture?.declarations.set(packPath, {
@@ -10714,9 +10723,9 @@ function projectAssetRefs(inventory) {
10714
10723
  }
10715
10724
  function projectSceneEntityRefs(inventory) {
10716
10725
  return inventory.declarations.flatMap(
10717
- (row) => row.sceneBindings === void 0 ? [] : row.sceneBindings.map((bindingKey) => ({
10726
+ (row) => row.sceneEntityKeys === void 0 ? [] : row.sceneEntityKeys.map((address) => ({
10718
10727
  sceneSourceKey: row.sourceKey,
10719
- bindingKey
10728
+ address
10720
10729
  }))
10721
10730
  );
10722
10731
  }
@@ -10776,49 +10785,23 @@ function validateAuthorInventory(value) {
10776
10785
  { guid, sourceKey: sourceKey2 }
10777
10786
  );
10778
10787
  }
10779
- if (row.kind === "scene") {
10780
- const payload = row.payload;
10781
- const payloadRecord = typeof payload === "object" && payload !== null ? payload : void 0;
10782
- const entities = payloadRecord !== void 0 && Array.isArray(payloadRecord.entities) ? payloadRecord.entities : [];
10783
- const bindings = /* @__PURE__ */ new Set();
10784
- for (const entity of entities) {
10785
- const bindingKey = typeof entity === "object" && entity !== null && typeof entity.bindingKey === "string" ? entity.bindingKey : void 0;
10786
- if (bindingKey !== void 0 && bindingKey.length === 0) {
10787
- return failure2(
10788
- "inventory-scene-binding-missing",
10789
- "each declared scene bindingKey is non-empty",
10790
- "remove the empty bindingKey or declare a stable key in the scene producer",
10791
- { sourceKey: sourceKey2 }
10792
- );
10793
- }
10794
- if (bindingKey === void 0) continue;
10795
- if (bindings.has(bindingKey)) {
10796
- return failure2(
10797
- "inventory-scene-binding-duplicate",
10798
- "bindingKey values are unique within one scene",
10799
- "rename the duplicate bindingKey in the scene producer",
10800
- { sourceKey: sourceKey2 }
10801
- );
10802
- }
10803
- bindings.add(bindingKey);
10804
- }
10805
- }
10806
10788
  guids.add(normalizedGuid);
10807
10789
  sourceKeys.add(sourceKey2);
10808
- const sceneBindings = row.kind === "scene" && typeof row.payload === "object" && row.payload !== null ? row.payload.entities?.flatMap(
10809
- (entity) => {
10810
- if (typeof entity !== "object" || entity === null) return [];
10811
- const key = entity.bindingKey;
10812
- return typeof key === "string" && key.length > 0 ? [key] : [];
10813
- }
10814
- ) : void 0;
10790
+ const sceneEntityKeys = (() => {
10791
+ if (row.kind !== "scene" || typeof row.payload !== "object" || row.payload === null)
10792
+ return void 0;
10793
+ const entities = row.payload.entities;
10794
+ if (entities === null || typeof entities !== "object" || Array.isArray(entities))
10795
+ return void 0;
10796
+ return Object.keys(entities);
10797
+ })();
10815
10798
  declarations.push({
10816
10799
  guid,
10817
10800
  sourceKey: sourceKey2,
10818
10801
  kind: typeof row.kind === "string" ? row.kind : "unknown",
10819
10802
  payload: typeof row.payload === "object" && row.payload !== null ? row.payload : {},
10820
10803
  refs: Array.isArray(row.refs) ? row.refs.filter((ref) => typeof ref === "string") : [],
10821
- ...sceneBindings === void 0 ? {} : { sceneBindings }
10804
+ ...sceneEntityKeys === void 0 ? {} : { sceneEntityKeys }
10822
10805
  });
10823
10806
  }
10824
10807
  return ok({ declarations });
@@ -10827,7 +10810,7 @@ function validateAuthorInventory(value) {
10827
10810
  // src/inventory/sync.ts
10828
10811
  function inventoryDigest(inventory) {
10829
10812
  return inventory.declarations.map(
10830
- (row) => `${row.guid}\0${row.sourceKey}\0${row.kind}\0${row.sceneBindings?.join("\0") ?? ""}`
10813
+ (row) => `${row.guid}\0${row.sourceKey}\0${row.kind}\0${row.sceneEntityKeys?.join("\0") ?? ""}`
10831
10814
  ).sort().join("\n");
10832
10815
  }
10833
10816
  function syncAuthorInventory(inventory) {
@@ -10835,7 +10818,7 @@ function syncAuthorInventory(inventory) {
10835
10818
  declarations: inventory.declarations.map((row) => ({
10836
10819
  ...row,
10837
10820
  refs: [...row.refs],
10838
- ...row.sceneBindings === void 0 ? {} : { sceneBindings: [...row.sceneBindings] }
10821
+ ...row.sceneEntityKeys === void 0 ? {} : { sceneEntityKeys: [...row.sceneEntityKeys] }
10839
10822
  }))
10840
10823
  };
10841
10824
  }