@forgeax/engine-pack 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 (228) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +370 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/ai-recovery.unit.test.d.ts +2 -0
  5. package/dist/__tests__/ai-recovery.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/artifact-path.test.d.ts +2 -0
  7. package/dist/__tests__/artifact-path.test.d.ts.map +1 -0
  8. package/dist/__tests__/asset-evidence-error-hints.test.d.ts +2 -0
  9. package/dist/__tests__/asset-evidence-error-hints.test.d.ts.map +1 -0
  10. package/dist/__tests__/asset-evidence-lifecycle.test.d.ts +2 -0
  11. package/dist/__tests__/asset-evidence-lifecycle.test.d.ts.map +1 -0
  12. package/dist/__tests__/asset-evidence-schema.test.d.ts +2 -0
  13. package/dist/__tests__/asset-evidence-schema.test.d.ts.map +1 -0
  14. package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts +2 -0
  15. package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts.map +1 -0
  16. package/dist/__tests__/atlas-error-code-owner.test-d.d.ts +2 -0
  17. package/dist/__tests__/atlas-error-code-owner.test-d.d.ts.map +1 -0
  18. package/dist/__tests__/cli-asset-evidence.integration.test.d.ts +2 -0
  19. package/dist/__tests__/cli-asset-evidence.integration.test.d.ts.map +1 -0
  20. package/dist/__tests__/cli.unit.test.d.ts +2 -0
  21. package/dist/__tests__/cli.unit.test.d.ts.map +1 -0
  22. package/dist/__tests__/evidence-source-inventory.unit.test.d.ts +2 -0
  23. package/dist/__tests__/evidence-source-inventory.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/guid-collision.unit.test.d.ts +2 -0
  25. package/dist/__tests__/guid-collision.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/guid-validator-surface.unit.test.d.ts +2 -0
  27. package/dist/__tests__/guid-validator-surface.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/load-asset-config.test.d.ts +2 -0
  29. package/dist/__tests__/load-asset-config.test.d.ts.map +1 -0
  30. package/dist/__tests__/material-cook-dependencies.unit.test.d.ts +2 -0
  31. package/dist/__tests__/material-cook-dependencies.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/material-cook-receipt.unit.test.d.ts +2 -0
  33. package/dist/__tests__/material-cook-receipt.unit.test.d.ts.map +1 -0
  34. package/dist/__tests__/material-cook-schema.unit.test.d.ts +2 -0
  35. package/dist/__tests__/material-cook-schema.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts +2 -0
  37. package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/mesh-bin-contract.unit.test.d.ts +2 -0
  39. package/dist/__tests__/mesh-bin-contract.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/meta-schema-optional.test.d.ts +2 -0
  41. package/dist/__tests__/meta-schema-optional.test.d.ts.map +1 -0
  42. package/dist/__tests__/meta-source-overrides.unit.test.d.ts +2 -0
  43. package/dist/__tests__/meta-source-overrides.unit.test.d.ts.map +1 -0
  44. package/dist/__tests__/native-cooker-registry.test.d.ts +2 -0
  45. package/dist/__tests__/native-cooker-registry.test.d.ts.map +1 -0
  46. package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts +2 -0
  47. package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts.map +1 -0
  48. package/dist/__tests__/offline-evidence.integration.test.d.ts +2 -0
  49. package/dist/__tests__/offline-evidence.integration.test.d.ts.map +1 -0
  50. package/dist/__tests__/pack-v2-fixture-migration.test.d.ts +2 -0
  51. package/dist/__tests__/pack-v2-fixture-migration.test.d.ts.map +1 -0
  52. package/dist/__tests__/pack-v2-schema.test.d.ts +2 -0
  53. package/dist/__tests__/pack-v2-schema.test.d.ts.map +1 -0
  54. package/dist/__tests__/pack.unit.test.d.ts +2 -0
  55. package/dist/__tests__/pack.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/producer-contract-schema.unit.test.d.ts +2 -0
  57. package/dist/__tests__/producer-contract-schema.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/resolve-asset-source.test.d.ts +2 -0
  59. package/dist/__tests__/resolve-asset-source.test.d.ts.map +1 -0
  60. package/dist/__tests__/runtime-projection.unit.test.d.ts +2 -0
  61. package/dist/__tests__/runtime-projection.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/scanner-inventory.test.d.ts +2 -0
  63. package/dist/__tests__/scanner-inventory.test.d.ts.map +1 -0
  64. package/dist/__tests__/scanner-mount-cycle.test.d.ts +2 -0
  65. package/dist/__tests__/scanner-mount-cycle.test.d.ts.map +1 -0
  66. package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts +2 -0
  67. package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts.map +1 -0
  68. package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts +2 -0
  69. package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts.map +1 -0
  70. package/dist/__tests__/scriptable-pack.test-d.d.ts +2 -0
  71. package/dist/__tests__/scriptable-pack.test-d.d.ts.map +1 -0
  72. package/dist/__tests__/scriptable-pack.unit.test.d.ts +2 -0
  73. package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/topology-actions.unit.test.d.ts +2 -0
  75. package/dist/__tests__/topology-actions.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/topology.unit.test.d.ts +2 -0
  77. package/dist/__tests__/topology.unit.test.d.ts.map +1 -0
  78. package/dist/artifact-path.d.ts +9 -0
  79. package/dist/artifact-path.d.ts.map +1 -0
  80. package/dist/atlas/run-atlas.d.ts +8 -0
  81. package/dist/atlas/run-atlas.d.ts.map +1 -0
  82. package/dist/atlas/shelf-pack.d.ts +55 -0
  83. package/dist/atlas/shelf-pack.d.ts.map +1 -0
  84. package/dist/bridge.d.ts +2 -0
  85. package/dist/bridge.d.ts.map +1 -0
  86. package/dist/bridge.mjs +3 -0
  87. package/dist/bridge.mjs.map +1 -0
  88. package/dist/builtin.d.ts +24 -0
  89. package/dist/builtin.d.ts.map +1 -0
  90. package/dist/cli-asset.d.ts +22 -0
  91. package/dist/cli-asset.d.ts.map +1 -0
  92. package/dist/cli-asset.mjs +9950 -0
  93. package/dist/cli-asset.mjs.map +1 -0
  94. package/dist/config.d.ts +6 -0
  95. package/dist/config.d.ts.map +1 -0
  96. package/dist/config.mjs +29 -0
  97. package/dist/config.mjs.map +1 -0
  98. package/dist/deriveAssetName.d.ts +15 -0
  99. package/dist/deriveAssetName.d.ts.map +1 -0
  100. package/dist/errors.d.ts +30 -0
  101. package/dist/errors.d.ts.map +1 -0
  102. package/dist/errors.mjs +19 -0
  103. package/dist/errors.mjs.map +1 -0
  104. package/dist/evidence/material-cook.d.ts +70 -0
  105. package/dist/evidence/material-cook.d.ts.map +1 -0
  106. package/dist/evidence/offline-evidence.d.ts +25 -0
  107. package/dist/evidence/offline-evidence.d.ts.map +1 -0
  108. package/dist/evidence/source-inventory.d.ts +29 -0
  109. package/dist/evidence/source-inventory.d.ts.map +1 -0
  110. package/dist/guid.d.ts +45 -0
  111. package/dist/guid.d.ts.map +1 -0
  112. package/dist/guid.mjs +94 -0
  113. package/dist/guid.mjs.map +1 -0
  114. package/dist/index.d.ts +15 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.mjs +8475 -0
  117. package/dist/index.mjs.map +1 -0
  118. package/dist/material/artifact-writer.d.ts +14 -0
  119. package/dist/material/artifact-writer.d.ts.map +1 -0
  120. package/dist/mesh-bin-contract.d.ts +36 -0
  121. package/dist/mesh-bin-contract.d.ts.map +1 -0
  122. package/dist/name.mjs +22 -0
  123. package/dist/name.mjs.map +1 -0
  124. package/dist/native-cooker-registry.d.ts +28 -0
  125. package/dist/native-cooker-registry.d.ts.map +1 -0
  126. package/dist/native-cooker.d.ts +3 -0
  127. package/dist/native-cooker.d.ts.map +1 -0
  128. package/dist/native-cooker.mjs +105 -0
  129. package/dist/native-cooker.mjs.map +1 -0
  130. package/dist/producer-contract.d.ts +19 -0
  131. package/dist/producer-contract.d.ts.map +1 -0
  132. package/dist/resolve-asset-source.d.ts +4 -0
  133. package/dist/resolve-asset-source.d.ts.map +1 -0
  134. package/dist/resolve-asset-source.mjs +99 -0
  135. package/dist/resolve-asset-source.mjs.map +1 -0
  136. package/dist/runtime-projection.d.ts +14 -0
  137. package/dist/runtime-projection.d.ts.map +1 -0
  138. package/dist/scanner.d.ts +80 -0
  139. package/dist/scanner.d.ts.map +1 -0
  140. package/dist/scanner.mjs +9015 -0
  141. package/dist/scanner.mjs.map +1 -0
  142. package/dist/schema-compiled.d.ts +12 -0
  143. package/dist/schema-compiled.d.ts.map +1 -0
  144. package/dist/schema.mjs +7369 -0
  145. package/dist/schema.mjs.map +1 -0
  146. package/dist/scriptable-pack-node.d.ts +67 -0
  147. package/dist/scriptable-pack-node.d.ts.map +1 -0
  148. package/dist/scriptable-pack-node.mjs +1590 -0
  149. package/dist/scriptable-pack-node.mjs.map +1 -0
  150. package/dist/scriptable-pack-worker.d.ts +2 -0
  151. package/dist/scriptable-pack-worker.d.ts.map +1 -0
  152. package/dist/scriptable-pack-worker.mjs +223 -0
  153. package/dist/scriptable-pack-worker.mjs.map +1 -0
  154. package/dist/scriptable-pack.d.ts +525 -0
  155. package/dist/scriptable-pack.d.ts.map +1 -0
  156. package/dist/scriptable-pack.mjs +583 -0
  157. package/dist/scriptable-pack.mjs.map +1 -0
  158. package/dist/topology.d.ts +10 -0
  159. package/dist/topology.d.ts.map +1 -0
  160. package/package.json +129 -0
  161. package/schema/meta.schema.json +173 -0
  162. package/schema/pack.schema.json +232 -0
  163. package/src/__tests__/ai-recovery.unit.test.ts +114 -0
  164. package/src/__tests__/artifact-path.test.ts +41 -0
  165. package/src/__tests__/asset-evidence-error-hints.test.ts +46 -0
  166. package/src/__tests__/asset-evidence-lifecycle.test.ts +104 -0
  167. package/src/__tests__/asset-evidence-schema.test.ts +86 -0
  168. package/src/__tests__/atlas-cli-region-mismatch.test.ts +134 -0
  169. package/src/__tests__/atlas-error-code-owner.test-d.ts +23 -0
  170. package/src/__tests__/cli-asset-evidence.integration.test.ts +172 -0
  171. package/src/__tests__/cli.unit.test.ts +448 -0
  172. package/src/__tests__/evidence-source-inventory.unit.test.ts +37 -0
  173. package/src/__tests__/guid-collision.unit.test.ts +112 -0
  174. package/src/__tests__/guid-validator-surface.unit.test.ts +20 -0
  175. package/src/__tests__/load-asset-config.test.ts +121 -0
  176. package/src/__tests__/material-cook-dependencies.unit.test.ts +45 -0
  177. package/src/__tests__/material-cook-receipt.unit.test.ts +91 -0
  178. package/src/__tests__/material-cook-schema.unit.test.ts +98 -0
  179. package/src/__tests__/mesh-bin-consumer-surface.unit.test.ts +21 -0
  180. package/src/__tests__/mesh-bin-contract.unit.test.ts +32 -0
  181. package/src/__tests__/meta-schema-optional.test.ts +116 -0
  182. package/src/__tests__/meta-source-overrides.unit.test.ts +71 -0
  183. package/src/__tests__/native-cooker-registry.test.ts +64 -0
  184. package/src/__tests__/offline-evidence-artifact-verification-owner.test-d.ts +64 -0
  185. package/src/__tests__/offline-evidence.integration.test.ts +106 -0
  186. package/src/__tests__/pack-v2-fixture-migration.test.ts +64 -0
  187. package/src/__tests__/pack-v2-schema.test.ts +142 -0
  188. package/src/__tests__/pack.unit.test.ts +2218 -0
  189. package/src/__tests__/producer-contract-schema.unit.test.ts +92 -0
  190. package/src/__tests__/resolve-asset-source.test.ts +121 -0
  191. package/src/__tests__/runtime-projection.unit.test.ts +87 -0
  192. package/src/__tests__/scanner-inventory.test.ts +72 -0
  193. package/src/__tests__/scanner-mount-cycle.test.ts +232 -0
  194. package/src/__tests__/scriptable-pack-cli.integration.test.ts +47 -0
  195. package/src/__tests__/scriptable-pack-ddc-key.unit.test.ts +63 -0
  196. package/src/__tests__/scriptable-pack.test-d.ts +131 -0
  197. package/src/__tests__/scriptable-pack.unit.test.ts +747 -0
  198. package/src/__tests__/topology-actions.unit.test.ts +94 -0
  199. package/src/__tests__/topology.unit.test.ts +85 -0
  200. package/src/artifact-path.ts +69 -0
  201. package/src/atlas/run-atlas.ts +358 -0
  202. package/src/atlas/shelf-pack.ts +176 -0
  203. package/src/atlas/upng-ambient.d.ts +13 -0
  204. package/src/bridge.ts +2 -0
  205. package/src/builtin.ts +79 -0
  206. package/src/cli-asset.ts +590 -0
  207. package/src/config.ts +36 -0
  208. package/src/deriveAssetName.ts +42 -0
  209. package/src/errors.ts +47 -0
  210. package/src/evidence/material-cook.ts +332 -0
  211. package/src/evidence/offline-evidence.ts +90 -0
  212. package/src/evidence/source-inventory.ts +193 -0
  213. package/src/guid.ts +99 -0
  214. package/src/index.ts +104 -0
  215. package/src/material/artifact-writer.ts +26 -0
  216. package/src/mesh-bin-contract.ts +150 -0
  217. package/src/native-cooker-registry.ts +163 -0
  218. package/src/native-cooker.ts +2 -0
  219. package/src/producer-contract.ts +277 -0
  220. package/src/resolve-asset-source.ts +93 -0
  221. package/src/runtime-projection.ts +100 -0
  222. package/src/scanner.ts +716 -0
  223. package/src/schema/material-cook.schema.json +79 -0
  224. package/src/schema-compiled.ts +238 -0
  225. package/src/scriptable-pack-node.ts +1390 -0
  226. package/src/scriptable-pack-worker.ts +306 -0
  227. package/src/scriptable-pack.ts +884 -0
  228. package/src/topology.ts +156 -0
@@ -0,0 +1,448 @@
1
+ // Consolidated by feat-20260609-test-pool-startup-reduction-merge-tiny-test-files
2
+ // biome-ignore-all lint/complexity/noUselessLoneBlockStatements: scope isolation between merged source files
3
+ //
4
+ // Source files (N=4 — NOT 5, atlas-cli-region-mismatch.test.ts excluded: its
5
+ // vi.mock('../atlas/shelf-pack.js') is hoisted to file-top and conflicts with
6
+ // atlas-cli-happy-path.test.ts which needs the real shelf-pack):
7
+ // - packages/pack/src/__tests__/atlas-cli-empty-input.test.ts
8
+ // - packages/pack/src/__tests__/atlas-cli-happy-path.test.ts
9
+ // - packages/pack/src/__tests__/atlas-cli-size-exceeded.test.ts
10
+ // - packages/pack/src/__tests__/cli-asset.test.ts
11
+ //
12
+ // Paradigm: each block-scoped describe('<source-filename>.test.ts', ...) preserves
13
+ // source as ancestorTitles[0]. Top-level imports merged + deduped.
14
+
15
+ import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
16
+ import { tmpdir } from 'node:os';
17
+ import { join } from 'node:path';
18
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
19
+ import { runCliAsset } from '../cli-asset.js';
20
+
21
+ interface CapturedIO {
22
+ stdout: string[];
23
+ stderr: string[];
24
+ }
25
+
26
+ function makeIO(): CapturedIO {
27
+ return { stdout: [], stderr: [] };
28
+ }
29
+
30
+ function ctxFor(io: CapturedIO) {
31
+ return {
32
+ stdoutWrite: (line: string): void => {
33
+ io.stdout.push(line);
34
+ },
35
+ stderrWrite: (line: string): void => {
36
+ io.stderr.push(line);
37
+ },
38
+ };
39
+ }
40
+
41
+ interface UpngEncode {
42
+ encode: (imgs: ArrayBuffer[], w: number, h: number, cnum: number) => ArrayBuffer;
43
+ }
44
+
45
+ async function loadUpng(): Promise<UpngEncode> {
46
+ const mod = (await import('upng-js')) as { default?: UpngEncode } & UpngEncode;
47
+ return (mod.default ?? mod) as UpngEncode;
48
+ }
49
+
50
+ async function writeSolidPng(
51
+ path: string,
52
+ width: number,
53
+ height: number,
54
+ rgba: [number, number, number, number],
55
+ ): Promise<void> {
56
+ const upng = await loadUpng();
57
+ const pixels = new Uint8Array(width * height * 4);
58
+ for (let i = 0; i < width * height; i++) {
59
+ pixels[i * 4 + 0] = rgba[0];
60
+ pixels[i * 4 + 1] = rgba[1];
61
+ pixels[i * 4 + 2] = rgba[2];
62
+ pixels[i * 4 + 3] = rgba[3];
63
+ }
64
+ const buf = upng.encode([pixels.buffer as ArrayBuffer], width, height, 0);
65
+ await writeFile(path, Buffer.from(buf));
66
+ }
67
+
68
+ {
69
+ // ─── from atlas-cli-empty-input.test.ts ───
70
+
71
+ describe('atlas-cli-empty-input.test.ts', () => {
72
+ let tmp: string;
73
+
74
+ beforeEach(async () => {
75
+ tmp = await mkdtemp(join(tmpdir(), 'atlas-cli-empty-'));
76
+ });
77
+
78
+ afterEach(async () => {
79
+ await rm(tmp, { recursive: true, force: true });
80
+ });
81
+
82
+ describe('atlas CLI empty-input fail-fast (T-27 / AC-10 a)', () => {
83
+ it('exits 1 with atlas-empty-input + receivedCount=0', async () => {
84
+ const io = makeIO();
85
+ const code = await runCliAsset(
86
+ [
87
+ 'atlas',
88
+ '--input',
89
+ `${tmp}/nonexistent-*.png`,
90
+ '--name',
91
+ 'walk',
92
+ '--output',
93
+ tmp,
94
+ '--max-atlas-size',
95
+ '4096',
96
+ ],
97
+ ctxFor(io),
98
+ );
99
+
100
+ expect(code).toBe(1);
101
+ expect(io.stderr.length).toBe(1);
102
+ const line = io.stderr[0] as string;
103
+ expect(line.includes('\n')).toBe(false);
104
+ const parsed = JSON.parse(line) as {
105
+ code: string;
106
+ expected: string;
107
+ hint: string;
108
+ detail?: { receivedCount?: number };
109
+ };
110
+ expect(parsed.code).toBe('atlas-empty-input');
111
+ expect(typeof parsed.expected).toBe('string');
112
+ expect(parsed.expected.length).toBeGreaterThan(0);
113
+ expect(typeof parsed.hint).toBe('string');
114
+ expect(parsed.hint.length).toBeGreaterThan(0);
115
+ expect(parsed.detail?.receivedCount).toBe(0);
116
+ });
117
+ });
118
+ });
119
+ }
120
+
121
+ {
122
+ // ─── from atlas-cli-happy-path.test.ts ───
123
+
124
+ describe('atlas-cli-happy-path.test.ts', () => {
125
+ let tmp: string;
126
+
127
+ beforeEach(async () => {
128
+ tmp = await mkdtemp(join(tmpdir(), 'atlas-cli-happy-'));
129
+ });
130
+
131
+ afterEach(async () => {
132
+ await rm(tmp, { recursive: true, force: true });
133
+ });
134
+
135
+ describe('atlas CLI happy path (T-26 / AC-06)', () => {
136
+ it('packs 3 PNGs into <name>.atlas.png + <name>.atlas.meta.json', async () => {
137
+ await writeSolidPng(join(tmp, 'walk-0.png'), 64, 64, [255, 0, 0, 255]);
138
+ await writeSolidPng(join(tmp, 'walk-1.png'), 64, 64, [0, 255, 0, 255]);
139
+ await writeSolidPng(join(tmp, 'walk-2.png'), 64, 64, [0, 0, 255, 255]);
140
+
141
+ const io = makeIO();
142
+ const code = await runCliAsset(
143
+ [
144
+ 'atlas',
145
+ '--input',
146
+ `${tmp}/*.png`,
147
+ '--name',
148
+ 'walk',
149
+ '--output',
150
+ tmp,
151
+ '--max-atlas-size',
152
+ '4096',
153
+ ],
154
+ ctxFor(io),
155
+ );
156
+
157
+ expect(code).toBe(0);
158
+ expect(io.stderr).toEqual([]);
159
+
160
+ const atlasBytes = await readFile(join(tmp, 'walk.atlas.png'));
161
+ expect(atlasBytes.length).toBeGreaterThan(0);
162
+ expect(atlasBytes[0]).toBe(0x89);
163
+ expect(atlasBytes[1]).toBe(0x50);
164
+ expect(atlasBytes[2]).toBe(0x4e);
165
+ expect(atlasBytes[3]).toBe(0x47);
166
+
167
+ const metaText = await readFile(join(tmp, 'walk.atlas.meta.json'), 'utf-8');
168
+ const meta = JSON.parse(metaText) as {
169
+ name: string;
170
+ atlasWidth: number;
171
+ atlasHeight: number;
172
+ regions: Array<{ name: string; uMin: number; vMin: number; uW: number; vH: number }>;
173
+ };
174
+ expect(meta.name).toBe('walk');
175
+ expect(meta.atlasWidth).toBeGreaterThan(0);
176
+ expect(meta.atlasHeight).toBeGreaterThan(0);
177
+ expect(meta.atlasWidth).toBeLessThanOrEqual(4096);
178
+ expect(meta.atlasHeight).toBeLessThanOrEqual(4096);
179
+ expect(meta.regions).toHaveLength(3);
180
+ const regionNames = meta.regions.map((r) => r.name).sort();
181
+ expect(regionNames).toEqual(['walk-0', 'walk-1', 'walk-2']);
182
+ for (const r of meta.regions) {
183
+ expect(r.uMin).toBeGreaterThanOrEqual(0);
184
+ expect(r.uMin).toBeLessThanOrEqual(1);
185
+ expect(r.vMin).toBeGreaterThanOrEqual(0);
186
+ expect(r.vMin).toBeLessThanOrEqual(1);
187
+ expect(r.uW).toBeGreaterThan(0);
188
+ expect(r.uW).toBeLessThanOrEqual(1);
189
+ expect(r.vH).toBeGreaterThan(0);
190
+ expect(r.vH).toBeLessThanOrEqual(1);
191
+ expect(r.uMin + r.uW).toBeLessThanOrEqual(1 + 1e-9);
192
+ expect(r.vMin + r.vH).toBeLessThanOrEqual(1 + 1e-9);
193
+ }
194
+ });
195
+ });
196
+ });
197
+ }
198
+
199
+ {
200
+ // ─── from atlas-cli-size-exceeded.test.ts ───
201
+
202
+ describe('atlas-cli-size-exceeded.test.ts', () => {
203
+ let tmp: string;
204
+
205
+ beforeEach(async () => {
206
+ tmp = await mkdtemp(join(tmpdir(), 'atlas-cli-size-'));
207
+ });
208
+
209
+ afterEach(async () => {
210
+ await rm(tmp, { recursive: true, force: true });
211
+ });
212
+
213
+ describe('atlas CLI size-exceeded fail-fast (T-28 / AC-10 b)', () => {
214
+ it('exits 1 with atlas-size-exceeded + detail name/width/height/maxAtlasSize', async () => {
215
+ await writeSolidPng(join(tmp, 'giant.png'), 256, 256, [255, 200, 100, 255]);
216
+
217
+ const io = makeIO();
218
+ const code = await runCliAsset(
219
+ [
220
+ 'atlas',
221
+ '--input',
222
+ `${tmp}/*.png`,
223
+ '--name',
224
+ 'walk',
225
+ '--output',
226
+ tmp,
227
+ '--max-atlas-size',
228
+ '64',
229
+ ],
230
+ ctxFor(io),
231
+ );
232
+
233
+ expect(code).toBe(1);
234
+ expect(io.stderr.length).toBe(1);
235
+ const parsed = JSON.parse(io.stderr[0] as string) as {
236
+ code: string;
237
+ expected: string;
238
+ hint: string;
239
+ detail?: { name?: string; width?: number; height?: number; maxAtlasSize?: number };
240
+ };
241
+ expect(parsed.code).toBe('atlas-size-exceeded');
242
+ expect(typeof parsed.expected).toBe('string');
243
+ expect(parsed.expected.length).toBeGreaterThan(0);
244
+ expect(typeof parsed.hint).toBe('string');
245
+ expect(parsed.hint.length).toBeGreaterThan(0);
246
+ expect(parsed.detail?.name).toBe('giant');
247
+ expect(parsed.detail?.width).toBe(256);
248
+ expect(parsed.detail?.height).toBe(256);
249
+ expect(parsed.detail?.maxAtlasSize).toBe(64);
250
+ });
251
+ });
252
+ });
253
+ }
254
+
255
+ {
256
+ // ─── from cli-asset.test.ts ───
257
+
258
+ describe('cli-asset.test.ts', () => {
259
+ let tempDir: string;
260
+
261
+ beforeEach(async () => {
262
+ tempDir = await mkdtemp(join(tmpdir(), 'pack-cli-asset-'));
263
+ });
264
+
265
+ afterEach(async () => {
266
+ await rm(tempDir, { recursive: true, force: true });
267
+ });
268
+
269
+ describe('cli-asset (forgeax-engine-remote-asset plugin bin)', () => {
270
+ describe('subcommand routing (a)', () => {
271
+ it('routes scan as a known subcommand and returns 0 on empty roots', async () => {
272
+ const io = makeIO();
273
+ const code = await runCliAsset(['scan', '--roots', tempDir], ctxFor(io));
274
+ expect(code).toBe(0);
275
+ expect(io.stdout.length).toBeGreaterThanOrEqual(1);
276
+ expect(io.stdout[0]).toBe('[]');
277
+ });
278
+
279
+ it('routes verify as a known subcommand on a clean dir (exit 0)', async () => {
280
+ const io = makeIO();
281
+ const code = await runCliAsset(['verify'], { ...ctxFor(io), cwd: tempDir });
282
+ expect(code).toBe(0);
283
+ });
284
+
285
+ it('routes lookup and rejects malformed GUID with exit 1', async () => {
286
+ const io = makeIO();
287
+ const code = await runCliAsset(['lookup', 'not-a-uuid'], { ...ctxFor(io), cwd: tempDir });
288
+ expect(code).toBe(1);
289
+ expect(io.stderr.length).toBeGreaterThanOrEqual(1);
290
+ const parsed = JSON.parse(io.stderr[0] as string);
291
+ expect(parsed.code).toBe('pack-guid-malformed');
292
+ });
293
+
294
+ it('emits a JSON Lines envelope with code on unknown subcommand and exits 1', async () => {
295
+ const io = makeIO();
296
+ const code = await runCliAsset(['totally-not-a-subcommand'], ctxFor(io));
297
+ expect(code).toBe(1);
298
+ expect(io.stderr.length).toBe(1);
299
+ const parsed = JSON.parse(io.stderr[0] as string);
300
+ expect(typeof parsed.code).toBe('string');
301
+ expect(typeof parsed.expected).toBe('string');
302
+ expect(typeof parsed.hint).toBe('string');
303
+ });
304
+
305
+ it('prints help on --help and exits 0', async () => {
306
+ const io = makeIO();
307
+ const code = await runCliAsset(['--help'], ctxFor(io));
308
+ expect(code).toBe(0);
309
+ const help = io.stdout.join('\n');
310
+ expect(help).toContain('scan');
311
+ expect(help).toContain('lookup');
312
+ expect(help).toContain('verify');
313
+ });
314
+ });
315
+
316
+ describe('PackError stderr JSON Lines (b)', () => {
317
+ it('emits code / expected / hint as a single JSON line on pack-orphan-meta', async () => {
318
+ const orphanDir = join(tempDir, 'orphan');
319
+ await mkdir(orphanDir, { recursive: true });
320
+ await writeFile(
321
+ join(orphanDir, 'foo.png.meta.json'),
322
+ JSON.stringify({
323
+ schemaVersion: '1.0.0',
324
+ kind: 'external-asset-package',
325
+ importer: 'image',
326
+ source: 'foo.png',
327
+ importSettings: {},
328
+ subAssets: [
329
+ { guid: '01234567-89ab-7def-8123-456789abcdef', sourceIndex: 0, kind: 'texture' },
330
+ ],
331
+ }),
332
+ 'utf-8',
333
+ );
334
+ const io = makeIO();
335
+ const code = await runCliAsset(['scan', '--roots', orphanDir], ctxFor(io));
336
+ expect(code).toBe(1);
337
+ expect(io.stderr.length).toBe(1);
338
+ const line = io.stderr[0] as string;
339
+ expect(line.includes('\n')).toBe(false);
340
+ const parsed = JSON.parse(line);
341
+ expect(typeof parsed.code).toBe('string');
342
+ expect(parsed.code).toMatch(/^pack-/);
343
+ expect(typeof parsed.hint).toBe('string');
344
+ });
345
+
346
+ it('emits pack-guid-malformed via lookup with code/expected/hint', async () => {
347
+ const io = makeIO();
348
+ const code = await runCliAsset(['lookup', 'oops'], { ...ctxFor(io), cwd: tempDir });
349
+ expect(code).toBe(1);
350
+ const parsed = JSON.parse(io.stderr[0] as string);
351
+ expect(parsed.code).toBe('pack-guid-malformed');
352
+ expect(typeof parsed.expected).toBe('string');
353
+ expect(typeof parsed.hint).toBe('string');
354
+ });
355
+ });
356
+
357
+ describe('image sidecar enumeration (T-2.B1)', () => {
358
+ const IMAGE_GUID = '019e2cc6-0c86-79da-aa76-b0984c86d45c';
359
+
360
+ it('scan enumerates *.meta.json subAssets', async () => {
361
+ const sidecarDir = join(tempDir, 'wood');
362
+ await mkdir(sidecarDir, { recursive: true });
363
+ await writeFile(join(sidecarDir, 'wood-container.jpg'), 'placeholder', 'utf-8');
364
+ await writeFile(
365
+ join(sidecarDir, 'wood-container.jpg.meta.json'),
366
+ JSON.stringify({
367
+ schemaVersion: '1.0.0',
368
+ kind: 'external-asset-package',
369
+ importer: 'image',
370
+ source: 'wood-container.jpg',
371
+ importSettings: {
372
+ colorSpace: 'srgb',
373
+ mipmap: 'auto',
374
+ addressMode: 'repeat',
375
+ filterMode: 'linear',
376
+ },
377
+ subAssets: [{ guid: IMAGE_GUID, sourceIndex: 0, kind: 'texture' }],
378
+ }),
379
+ 'utf-8',
380
+ );
381
+ const io = makeIO();
382
+ const code = await runCliAsset(['scan', '--roots', sidecarDir], ctxFor(io));
383
+ expect(code).toBe(0);
384
+ const parsed = JSON.parse(io.stdout[0] as string);
385
+ expect(parsed).toHaveLength(1);
386
+ expect(parsed[0].guid).toBe(IMAGE_GUID);
387
+ expect(parsed[0].kind).toBe('texture');
388
+ expect(parsed[0].sourcePath).toContain('wood-container.jpg.meta.json');
389
+ });
390
+
391
+ it('lookup hits image GUID surfaced by *.meta.json', async () => {
392
+ const sidecarDir = join(tempDir, 'wood');
393
+ await mkdir(sidecarDir, { recursive: true });
394
+ await writeFile(join(sidecarDir, 'wood-container.jpg'), 'placeholder', 'utf-8');
395
+ await writeFile(
396
+ join(sidecarDir, 'wood-container.jpg.meta.json'),
397
+ JSON.stringify({
398
+ schemaVersion: '1.0.0',
399
+ kind: 'external-asset-package',
400
+ importer: 'image',
401
+ source: 'wood-container.jpg',
402
+ importSettings: {
403
+ colorSpace: 'srgb',
404
+ mipmap: 'auto',
405
+ addressMode: 'repeat',
406
+ filterMode: 'linear',
407
+ },
408
+ subAssets: [{ guid: IMAGE_GUID, sourceIndex: 0, kind: 'texture' }],
409
+ }),
410
+ 'utf-8',
411
+ );
412
+ const io = makeIO();
413
+ const code = await runCliAsset(['lookup', IMAGE_GUID], {
414
+ ...ctxFor(io),
415
+ cwd: sidecarDir,
416
+ });
417
+ expect(code).toBe(0);
418
+ const parsed = JSON.parse(io.stdout[0] as string);
419
+ expect(parsed.guid).toBe(IMAGE_GUID);
420
+ expect(parsed.kind).toBe('texture');
421
+ });
422
+ });
423
+
424
+ describe('exit codes (c)', () => {
425
+ it('verify ok -> 0', async () => {
426
+ const io = makeIO();
427
+ const code = await runCliAsset(['verify'], { ...ctxFor(io), cwd: tempDir });
428
+ expect(code).toBe(0);
429
+ });
430
+
431
+ it('verify err -> 1', async () => {
432
+ const subDir = join(tempDir, 'bad');
433
+ await mkdir(subDir, { recursive: true });
434
+ await writeFile(
435
+ join(subDir, 'broken.pack.json'),
436
+ '{"this is not": "schema-valid"}',
437
+ 'utf-8',
438
+ );
439
+ const io = makeIO();
440
+ const code = await runCliAsset(['verify'], { ...ctxFor(io), cwd: subDir });
441
+ expect(code).toBe(1);
442
+ });
443
+ });
444
+ });
445
+ });
446
+
447
+ void chmod;
448
+ }
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ type ProducerSemanticIdentityInput,
4
+ producerRelativeDdcKey,
5
+ producerRelativeLogicalPath,
6
+ } from '../evidence/source-inventory.js';
7
+
8
+ describe('source inventory DDC cold path contract', () => {
9
+ const input: ProducerSemanticIdentityInput = {
10
+ producerRoot: '/tmp/game',
11
+ sourcePath: '/tmp/game/assets/tree.pack.ts',
12
+ sourceDigest: 'sha256:tree',
13
+ schemaVersion: '1.0.0',
14
+ importer: 'scriptable-pack-importer@2',
15
+ codec: 'pack-v2',
16
+ settings: { profile: 'default' },
17
+ producer: 'scriptable-pack@4',
18
+ profile: 'webgpu/v1',
19
+ declaredGuids: ['tree-guid'],
20
+ };
21
+
22
+ it('rejects a source outside the injected producer root instead of guessing a basename', () => {
23
+ expect(() => producerRelativeLogicalPath('/tmp/game', '/tmp/other/tree.pack.ts')).toThrow();
24
+ });
25
+
26
+ it('keeps the correctness key independent from cache availability', () => {
27
+ const key = producerRelativeDdcKey(input);
28
+ expect(key).toMatch(/^[a-f0-9]{64}$/);
29
+ expect(
30
+ producerRelativeDdcKey({
31
+ ...input,
32
+ producerRoot: '/tmp/another-game',
33
+ sourcePath: '/tmp/another-game/assets/tree.pack.ts',
34
+ }),
35
+ ).toBe(key);
36
+ });
37
+ });
@@ -0,0 +1,112 @@
1
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { afterEach, describe, expect, it } from 'vitest';
5
+
6
+ import { scan } from '../scanner.js';
7
+
8
+ const COLLIDING_GUID = '018e7a4d-1234-7abc-8def-000000000001';
9
+ const roots: string[] = [];
10
+
11
+ const pack = (guid: string) => ({
12
+ schemaVersion: '1.0.0',
13
+ kind: 'internal-text-package',
14
+ assets: [{ guid, kind: 'mesh', payload: {}, refs: [] }],
15
+ });
16
+
17
+ const meta = (guid: string, source: string) => ({
18
+ schemaVersion: '1.0.0',
19
+ kind: 'external-asset-package',
20
+ importer: 'image',
21
+ source,
22
+ importSettings: {},
23
+ subAssets: [{ guid, sourceIndex: 0, kind: 'texture', sourceKey: 'image/main' }],
24
+ });
25
+
26
+ async function makeRoot(name: string): Promise<string> {
27
+ const root = await mkdtemp(join(tmpdir(), `forgeax-guid-${name}-`));
28
+ roots.push(root);
29
+ return root;
30
+ }
31
+
32
+ async function expectGuidCollision(
33
+ rootList: readonly string[],
34
+ expectedNames: readonly string[],
35
+ ): Promise<void> {
36
+ const result = await scan(rootList);
37
+ expect(result.ok).toBe(false);
38
+ if (result.ok) return;
39
+
40
+ expect(result.error.code).toBe('pack-guid-collision');
41
+ if (result.error.code !== 'pack-guid-collision') return;
42
+
43
+ expect(result.error.expected).toContain('pack error');
44
+ expect(result.error.hint.length).toBeGreaterThan(0);
45
+ const detail = result.error.detail;
46
+ if (!('guid' in detail) || !('paths' in detail)) return;
47
+
48
+ expect(detail.guid).toBe(COLLIDING_GUID);
49
+ expect(detail.paths).toHaveLength(2);
50
+ for (const name of expectedNames) {
51
+ expect(detail.paths.some((path) => path.endsWith(name))).toBe(true);
52
+ }
53
+ }
54
+
55
+ afterEach(async () => {
56
+ await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
57
+ });
58
+
59
+ describe('normalized GUID collision collection', () => {
60
+ it('reports a pack asset and meta subAsset collision in one root', async () => {
61
+ const root = await makeRoot('pack-meta');
62
+ await writeFile(join(root, 'asset.pack.json'), JSON.stringify(pack(COLLIDING_GUID)));
63
+ await writeFile(join(root, 'image.png'), Buffer.from('fixture'));
64
+ await writeFile(
65
+ join(root, 'image.png.meta.json'),
66
+ JSON.stringify(meta(COLLIDING_GUID, 'image.png')),
67
+ );
68
+
69
+ await expectGuidCollision([root], ['asset.pack.json', 'image.png.meta.json']);
70
+ });
71
+
72
+ it('reports meta subAsset collisions across independent roots', async () => {
73
+ const firstRoot = await makeRoot('cross-a');
74
+ const secondRoot = await makeRoot('cross-b');
75
+ await writeFile(join(firstRoot, 'hero.png'), Buffer.from('first'));
76
+ await writeFile(join(secondRoot, 'hero.png'), Buffer.from('second'));
77
+ await writeFile(
78
+ join(firstRoot, 'hero.png.meta.json'),
79
+ JSON.stringify(meta(COLLIDING_GUID, 'hero.png')),
80
+ );
81
+ await writeFile(
82
+ join(secondRoot, 'hero.png.meta.json'),
83
+ JSON.stringify(meta(COLLIDING_GUID, 'hero.png')),
84
+ );
85
+
86
+ await expectGuidCollision([firstRoot, secondRoot], ['hero.png.meta.json']);
87
+ });
88
+
89
+ it('reports a ScriptablePack output colliding with a JSON sidecar', async () => {
90
+ const root = await makeRoot('scriptable-meta');
91
+ await writeFile(join(root, 'image.png'), Buffer.from('fixture'));
92
+ await writeFile(
93
+ join(root, 'image.png.meta.json'),
94
+ JSON.stringify(meta(COLLIDING_GUID, 'image.png')),
95
+ );
96
+ await writeFile(
97
+ join(root, 'generated.pack.ts'),
98
+ `
99
+ const guid = (value: string) => new Uint8Array(value.replaceAll('-', '').match(/../g)!.map((byte: string) => Number.parseInt(byte, 16)));
100
+ export default {
101
+ schemaVersion: '1.0.0',
102
+ packageId: guid('018e7a4d-1234-7abc-8def-000000000002'),
103
+ assets: { mesh: { guid: guid('${COLLIDING_GUID}'), kind: 'mesh' } },
104
+ externalAssets: {},
105
+ build: () => ({ ok: true, value: { mesh: { kind: 'mesh' } } }),
106
+ };
107
+ `,
108
+ );
109
+
110
+ await expectGuidCollision([root], ['image.png.meta.json', 'generated.pack.ts']);
111
+ });
112
+ });
@@ -0,0 +1,20 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { isValidAssetGuidString } from '../guid';
4
+
5
+ const ownerSource = readFileSync(new URL('../guid.ts', import.meta.url), 'utf8');
6
+ const scannerSource = readFileSync(new URL('../scanner.ts', import.meta.url), 'utf8');
7
+ const cliSource = readFileSync(new URL('../cli-asset.ts', import.meta.url), 'utf8');
8
+
9
+ describe('asset GUID validator owner', () => {
10
+ it('keeps scanner and CLI validation on the GUID owner', () => {
11
+ expect(isValidAssetGuidString('019e3969-1d48-7c3b-ac24-6d68f457065f')).toBe(true);
12
+ expect(isValidAssetGuidString('not-a-guid')).toBe(false);
13
+ expect(isValidAssetGuidString(undefined)).toBe(false);
14
+ expect(ownerSource.match(/const UUID_RE = \/\^\[0-9a-f\]/g)).toHaveLength(1);
15
+ expect(scannerSource).not.toMatch(/const UUID_RE =/);
16
+ expect(cliSource).not.toMatch(/const UUID_RE =/);
17
+ expect(scannerSource.match(/isValidAssetGuidString/g)).toHaveLength(4);
18
+ expect(cliSource.match(/isValidAssetGuidString/g)).toHaveLength(3);
19
+ });
20
+ });