@forgeax/engine-pack 0.0.0-dev.8d955ade1c79

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 (276) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +429 -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__/owner-chain.integration.test.d.ts +2 -0
  51. package/dist/__tests__/owner-chain.integration.test.d.ts.map +1 -0
  52. package/dist/__tests__/pack-v2-fixture-migration.test.d.ts +2 -0
  53. package/dist/__tests__/pack-v2-fixture-migration.test.d.ts.map +1 -0
  54. package/dist/__tests__/pack-v2-schema.test.d.ts +2 -0
  55. package/dist/__tests__/pack-v2-schema.test.d.ts.map +1 -0
  56. package/dist/__tests__/pack.unit.test.d.ts +2 -0
  57. package/dist/__tests__/pack.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/package-finalizer.contract.test.d.ts +2 -0
  59. package/dist/__tests__/package-finalizer.contract.test.d.ts.map +1 -0
  60. package/dist/__tests__/producer-contract-schema.unit.test.d.ts +2 -0
  61. package/dist/__tests__/producer-contract-schema.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/resolve-asset-source.test.d.ts +2 -0
  63. package/dist/__tests__/resolve-asset-source.test.d.ts.map +1 -0
  64. package/dist/__tests__/runtime-projection.unit.test.d.ts +2 -0
  65. package/dist/__tests__/runtime-projection.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/runtime.browser.test.d.ts +2 -0
  67. package/dist/__tests__/runtime.browser.test.d.ts.map +1 -0
  68. package/dist/__tests__/scanner-inventory.contract.test.d.ts +2 -0
  69. package/dist/__tests__/scanner-inventory.contract.test.d.ts.map +1 -0
  70. package/dist/__tests__/scanner-inventory.test.d.ts +2 -0
  71. package/dist/__tests__/scanner-inventory.test.d.ts.map +1 -0
  72. package/dist/__tests__/scanner-mount-cycle.test.d.ts +2 -0
  73. package/dist/__tests__/scanner-mount-cycle.test.d.ts.map +1 -0
  74. package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts +2 -0
  75. package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts.map +1 -0
  76. package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts +2 -0
  77. package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/scriptable-pack.test-d.d.ts +2 -0
  79. package/dist/__tests__/scriptable-pack.test-d.d.ts.map +1 -0
  80. package/dist/__tests__/scriptable-pack.unit.test.d.ts +2 -0
  81. package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +1 -0
  82. package/dist/__tests__/topology-actions.unit.test.d.ts +2 -0
  83. package/dist/__tests__/topology-actions.unit.test.d.ts.map +1 -0
  84. package/dist/__tests__/topology.unit.test.d.ts +2 -0
  85. package/dist/__tests__/topology.unit.test.d.ts.map +1 -0
  86. package/dist/artifact-path.d.ts +9 -0
  87. package/dist/artifact-path.d.ts.map +1 -0
  88. package/dist/artifact-path.mjs +53 -0
  89. package/dist/artifact-path.mjs.map +1 -0
  90. package/dist/atlas/run-atlas.d.ts +8 -0
  91. package/dist/atlas/run-atlas.d.ts.map +1 -0
  92. package/dist/atlas/shelf-pack.d.ts +55 -0
  93. package/dist/atlas/shelf-pack.d.ts.map +1 -0
  94. package/dist/bridge.d.ts +2 -0
  95. package/dist/bridge.d.ts.map +1 -0
  96. package/dist/bridge.mjs +3 -0
  97. package/dist/bridge.mjs.map +1 -0
  98. package/dist/build.d.ts +19 -0
  99. package/dist/build.d.ts.map +1 -0
  100. package/dist/build.mjs +10203 -0
  101. package/dist/build.mjs.map +1 -0
  102. package/dist/builtin.d.ts +44 -0
  103. package/dist/builtin.d.ts.map +1 -0
  104. package/dist/builtin.mjs +141 -0
  105. package/dist/builtin.mjs.map +1 -0
  106. package/dist/catalog-builder.d.ts +43 -0
  107. package/dist/catalog-builder.d.ts.map +1 -0
  108. package/dist/catalog-delta.d.ts +11 -0
  109. package/dist/catalog-delta.d.ts.map +1 -0
  110. package/dist/catalog-projection.d.ts +91 -0
  111. package/dist/catalog-projection.d.ts.map +1 -0
  112. package/dist/cli-asset.d.ts +22 -0
  113. package/dist/cli-asset.d.ts.map +1 -0
  114. package/dist/cli-asset.mjs +9697 -0
  115. package/dist/cli-asset.mjs.map +1 -0
  116. package/dist/config.d.ts +6 -0
  117. package/dist/config.d.ts.map +1 -0
  118. package/dist/config.mjs +29 -0
  119. package/dist/config.mjs.map +1 -0
  120. package/dist/deriveAssetName.d.ts +15 -0
  121. package/dist/deriveAssetName.d.ts.map +1 -0
  122. package/dist/errors.d.ts +30 -0
  123. package/dist/errors.d.ts.map +1 -0
  124. package/dist/errors.mjs +19 -0
  125. package/dist/errors.mjs.map +1 -0
  126. package/dist/evidence/material-cook.d.ts +92 -0
  127. package/dist/evidence/material-cook.d.ts.map +1 -0
  128. package/dist/evidence/offline-evidence.d.ts +25 -0
  129. package/dist/evidence/offline-evidence.d.ts.map +1 -0
  130. package/dist/evidence/source-inventory.d.ts +29 -0
  131. package/dist/evidence/source-inventory.d.ts.map +1 -0
  132. package/dist/guid.d.ts +45 -0
  133. package/dist/guid.d.ts.map +1 -0
  134. package/dist/guid.mjs +94 -0
  135. package/dist/guid.mjs.map +1 -0
  136. package/dist/index.d.ts +15 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.mjs +8612 -0
  139. package/dist/index.mjs.map +1 -0
  140. package/dist/material/artifact-writer.d.ts +14 -0
  141. package/dist/material/artifact-writer.d.ts.map +1 -0
  142. package/dist/material-cook.d.ts +2 -0
  143. package/dist/material-cook.d.ts.map +1 -0
  144. package/dist/material-cook.mjs +248 -0
  145. package/dist/material-cook.mjs.map +1 -0
  146. package/dist/mesh-bin-contract.d.ts +36 -0
  147. package/dist/mesh-bin-contract.d.ts.map +1 -0
  148. package/dist/mesh-bin-contract.mjs +96 -0
  149. package/dist/mesh-bin-contract.mjs.map +1 -0
  150. package/dist/name.mjs +22 -0
  151. package/dist/name.mjs.map +1 -0
  152. package/dist/native-cooker-registry.d.ts +47 -0
  153. package/dist/native-cooker-registry.d.ts.map +1 -0
  154. package/dist/native-cooker.d.ts +3 -0
  155. package/dist/native-cooker.d.ts.map +1 -0
  156. package/dist/native-cooker.mjs +142 -0
  157. package/dist/native-cooker.mjs.map +1 -0
  158. package/dist/package-finalizer.d.ts +121 -0
  159. package/dist/package-finalizer.d.ts.map +1 -0
  160. package/dist/producer-contract.d.ts +19 -0
  161. package/dist/producer-contract.d.ts.map +1 -0
  162. package/dist/resolve-asset-source.d.ts +4 -0
  163. package/dist/resolve-asset-source.d.ts.map +1 -0
  164. package/dist/resolve-asset-source.mjs +109 -0
  165. package/dist/resolve-asset-source.mjs.map +1 -0
  166. package/dist/runtime-projection.d.ts +14 -0
  167. package/dist/runtime-projection.d.ts.map +1 -0
  168. package/dist/runtime-publication.d.ts +44 -0
  169. package/dist/runtime-publication.d.ts.map +1 -0
  170. package/dist/runtime.d.ts +8 -0
  171. package/dist/runtime.d.ts.map +1 -0
  172. package/dist/runtime.mjs +7261 -0
  173. package/dist/runtime.mjs.map +1 -0
  174. package/dist/scanner.d.ts +113 -0
  175. package/dist/scanner.d.ts.map +1 -0
  176. package/dist/scanner.mjs +8819 -0
  177. package/dist/scanner.mjs.map +1 -0
  178. package/dist/schema-compiled.d.ts +12 -0
  179. package/dist/schema-compiled.d.ts.map +1 -0
  180. package/dist/schema.mjs +7369 -0
  181. package/dist/schema.mjs.map +1 -0
  182. package/dist/scriptable-pack-node.d.ts +62 -0
  183. package/dist/scriptable-pack-node.d.ts.map +1 -0
  184. package/dist/scriptable-pack-node.mjs +1674 -0
  185. package/dist/scriptable-pack-node.mjs.map +1 -0
  186. package/dist/scriptable-pack-worker.d.ts +2 -0
  187. package/dist/scriptable-pack-worker.d.ts.map +1 -0
  188. package/dist/scriptable-pack-worker.mjs +224 -0
  189. package/dist/scriptable-pack-worker.mjs.map +1 -0
  190. package/dist/scriptable-pack.d.ts +558 -0
  191. package/dist/scriptable-pack.d.ts.map +1 -0
  192. package/dist/scriptable-pack.mjs +672 -0
  193. package/dist/scriptable-pack.mjs.map +1 -0
  194. package/dist/topology.d.ts +10 -0
  195. package/dist/topology.d.ts.map +1 -0
  196. package/package.json +154 -0
  197. package/schema/meta.schema.json +173 -0
  198. package/schema/pack.schema.json +232 -0
  199. package/src/__tests__/ai-recovery.unit.test.ts +114 -0
  200. package/src/__tests__/artifact-path.test.ts +41 -0
  201. package/src/__tests__/asset-evidence-error-hints.test.ts +46 -0
  202. package/src/__tests__/asset-evidence-lifecycle.test.ts +104 -0
  203. package/src/__tests__/asset-evidence-schema.test.ts +86 -0
  204. package/src/__tests__/atlas-cli-region-mismatch.test.ts +134 -0
  205. package/src/__tests__/atlas-error-code-owner.test-d.ts +23 -0
  206. package/src/__tests__/cli-asset-evidence.integration.test.ts +172 -0
  207. package/src/__tests__/cli.unit.test.ts +448 -0
  208. package/src/__tests__/evidence-source-inventory.unit.test.ts +37 -0
  209. package/src/__tests__/guid-collision.unit.test.ts +112 -0
  210. package/src/__tests__/guid-validator-surface.unit.test.ts +20 -0
  211. package/src/__tests__/load-asset-config.test.ts +121 -0
  212. package/src/__tests__/material-cook-dependencies.unit.test.ts +112 -0
  213. package/src/__tests__/material-cook-receipt.unit.test.ts +148 -0
  214. package/src/__tests__/material-cook-schema.unit.test.ts +152 -0
  215. package/src/__tests__/mesh-bin-consumer-surface.unit.test.ts +22 -0
  216. package/src/__tests__/mesh-bin-contract.unit.test.ts +32 -0
  217. package/src/__tests__/meta-schema-optional.test.ts +116 -0
  218. package/src/__tests__/meta-source-overrides.unit.test.ts +71 -0
  219. package/src/__tests__/native-cooker-registry.test.ts +64 -0
  220. package/src/__tests__/offline-evidence-artifact-verification-owner.test-d.ts +64 -0
  221. package/src/__tests__/offline-evidence.integration.test.ts +106 -0
  222. package/src/__tests__/owner-chain.integration.test.ts +78 -0
  223. package/src/__tests__/pack-v2-fixture-migration.test.ts +87 -0
  224. package/src/__tests__/pack-v2-schema.test.ts +142 -0
  225. package/src/__tests__/pack.unit.test.ts +2218 -0
  226. package/src/__tests__/package-finalizer.contract.test.ts +105 -0
  227. package/src/__tests__/producer-contract-schema.unit.test.ts +92 -0
  228. package/src/__tests__/resolve-asset-source.test.ts +121 -0
  229. package/src/__tests__/runtime-projection.unit.test.ts +87 -0
  230. package/src/__tests__/runtime.browser.test.ts +15 -0
  231. package/src/__tests__/scanner-inventory.contract.test.ts +167 -0
  232. package/src/__tests__/scanner-inventory.test.ts +76 -0
  233. package/src/__tests__/scanner-mount-cycle.test.ts +232 -0
  234. package/src/__tests__/scriptable-pack-cli.integration.test.ts +47 -0
  235. package/src/__tests__/scriptable-pack-ddc-key.unit.test.ts +63 -0
  236. package/src/__tests__/scriptable-pack.test-d.ts +131 -0
  237. package/src/__tests__/scriptable-pack.unit.test.ts +789 -0
  238. package/src/__tests__/topology-actions.unit.test.ts +94 -0
  239. package/src/__tests__/topology.unit.test.ts +85 -0
  240. package/src/artifact-path.ts +69 -0
  241. package/src/atlas/run-atlas.ts +358 -0
  242. package/src/atlas/shelf-pack.ts +172 -0
  243. package/src/atlas/upng-ambient.d.ts +13 -0
  244. package/src/bridge.ts +2 -0
  245. package/src/build.ts +139 -0
  246. package/src/builtin.ts +117 -0
  247. package/src/catalog-builder.ts +313 -0
  248. package/src/catalog-delta.ts +189 -0
  249. package/src/catalog-projection.ts +344 -0
  250. package/src/cli-asset.ts +586 -0
  251. package/src/config.ts +36 -0
  252. package/src/deriveAssetName.ts +42 -0
  253. package/src/errors.ts +47 -0
  254. package/src/evidence/material-cook.ts +411 -0
  255. package/src/evidence/offline-evidence.ts +90 -0
  256. package/src/evidence/source-inventory.ts +193 -0
  257. package/src/guid.ts +99 -0
  258. package/src/index.ts +108 -0
  259. package/src/material/artifact-writer.ts +26 -0
  260. package/src/material-cook.ts +15 -0
  261. package/src/mesh-bin-contract.ts +150 -0
  262. package/src/native-cooker-registry.ts +235 -0
  263. package/src/native-cooker.ts +2 -0
  264. package/src/package-finalizer.ts +394 -0
  265. package/src/producer-contract.ts +277 -0
  266. package/src/resolve-asset-source.ts +88 -0
  267. package/src/runtime-projection.ts +100 -0
  268. package/src/runtime-publication.ts +190 -0
  269. package/src/runtime.ts +23 -0
  270. package/src/scanner.ts +715 -0
  271. package/src/schema/material-cook.schema.json +119 -0
  272. package/src/schema-compiled.ts +238 -0
  273. package/src/scriptable-pack-node.ts +1394 -0
  274. package/src/scriptable-pack-worker.ts +308 -0
  275. package/src/scriptable-pack.ts +1006 -0
  276. package/src/topology.ts +157 -0
@@ -0,0 +1,172 @@
1
+ // Pure-function shelf packer for the `forgeax-engine-remote-asset atlas`
2
+ // subcommand (feat-20260521-sprite-atlas-animation M5' T-31). Maps a list
3
+ // of decoded RGBA sprites to a non-overlapping atlas region map +
4
+ // atlasWidth / atlasHeight envelope.
5
+ //
6
+ // Why pure-fn (plan-strategy section 2 D-4):
7
+ // - sidecar (`<name>.atlas.meta.json`) addresses regions by source name,
8
+ // so the on-disk contract is stable under algorithm swaps. Future feats
9
+ // can replace shelfPack with MaxRects / rectpack2D without breaking
10
+ // downstream consumers (charter P5 producer/consumer split).
11
+ // - the caller (runAtlas) owns disk IO + decode; this file owns only the
12
+ // geometric layout decision so unit tests do not need filesystem state.
13
+ //
14
+ // Algorithm (research F-1 Godot chart_pack shelf precedent, ~80 LOC MVP):
15
+ // 1. Validate: empty input -> 'atlas-empty-input'; any single image
16
+ // width/height > maxAtlasSize -> 'atlas-size-exceeded' (caller cannot
17
+ // recover by repacking, so we fail-fast before the inner loop).
18
+ // 2. Sort by height descending (standard shelf optimisation -- tall
19
+ // sprites on the bottom shelves keep gaps small); break ties by name
20
+ // so layouts are deterministic across runs (helps reproducible builds
21
+ // + cross-platform CI parity).
22
+ // 3. Pick a target shelf width by `nextPow2(ceil(sqrt(totalArea)))` so
23
+ // the first shelf width is roughly square; clamp to maxAtlasSize and
24
+ // widen if any single image exceeds the initial guess.
25
+ // 4. Walk left-to-right; start a new shelf when the cursor would overflow
26
+ // the target width. New shelves stack downward; bail out with
27
+ // 'atlas-size-exceeded' if a placement would push past maxAtlasSize.
28
+ // 5. atlasHeight = nextPow2(usedHeight) clamped to maxAtlasSize so the
29
+ // output dimensions are GPU-friendly (most sprite atlases live happily
30
+ // inside power-of-two limits even when WebGPU lifts the requirement).
31
+
32
+ import type { ImageErrorCode } from '@forgeax/engine-types';
33
+
34
+ /**
35
+ * Decoded image input for shelfPack. `pixels` length must equal
36
+ * `width * height * 4` (RGBA8); the field is carried so the caller can
37
+ * blit into the atlas buffer without re-decoding inside this module.
38
+ */
39
+ export interface AtlasImageInput {
40
+ readonly name: string;
41
+ readonly width: number;
42
+ readonly height: number;
43
+ readonly pixels: Uint8Array;
44
+ }
45
+
46
+ /**
47
+ * One placed sprite inside the packed atlas. `(x, y)` is the top-left
48
+ * corner in pixel coordinates; `(w, h)` mirrors the source dimensions.
49
+ */
50
+ export interface AtlasRegion {
51
+ readonly name: string;
52
+ readonly x: number;
53
+ readonly y: number;
54
+ readonly w: number;
55
+ readonly h: number;
56
+ }
57
+
58
+ export interface ShelfPackResult {
59
+ readonly atlasWidth: number;
60
+ readonly atlasHeight: number;
61
+ readonly regions: ReadonlyArray<AtlasRegion>;
62
+ }
63
+
64
+ /**
65
+ * Detail shape used by both `atlas-empty-input` and `atlas-size-exceeded`
66
+ * branches. Fields are optional because the two codes carry different
67
+ * subsets; the caller projects each branch onto the matching
68
+ * `ImageErrorDetail` variant before emitting the structured error.
69
+ */
70
+ export interface ShelfPackError {
71
+ readonly code: Extract<ImageErrorCode, 'atlas-empty-input' | 'atlas-size-exceeded'>;
72
+ readonly detail: {
73
+ readonly name?: string;
74
+ readonly width?: number;
75
+ readonly height?: number;
76
+ readonly maxAtlasSize?: number;
77
+ readonly receivedCount?: number;
78
+ };
79
+ }
80
+
81
+ export type ShelfPackOutcome =
82
+ | { readonly ok: true; readonly value: ShelfPackResult }
83
+ | { readonly ok: false; readonly error: ShelfPackError };
84
+
85
+ function sizeExceeded(
86
+ image: AtlasImageInput,
87
+ maxAtlasSize: number,
88
+ ): { readonly ok: false; readonly error: ShelfPackError } {
89
+ return {
90
+ ok: false,
91
+ error: {
92
+ code: 'atlas-size-exceeded',
93
+ detail: {
94
+ name: image.name,
95
+ width: image.width,
96
+ height: image.height,
97
+ maxAtlasSize,
98
+ },
99
+ },
100
+ };
101
+ }
102
+
103
+ function nextPow2(n: number): number {
104
+ if (n <= 1) return 1;
105
+ let p = 1;
106
+ while (p < n) p <<= 1;
107
+ return p;
108
+ }
109
+
110
+ export function shelfPack(
111
+ images: ReadonlyArray<AtlasImageInput>,
112
+ opts: { readonly maxAtlasSize: number },
113
+ ): ShelfPackOutcome {
114
+ const { maxAtlasSize } = opts;
115
+
116
+ if (images.length === 0) {
117
+ return {
118
+ ok: false,
119
+ error: { code: 'atlas-empty-input', detail: { receivedCount: 0 } },
120
+ };
121
+ }
122
+
123
+ for (const img of images) {
124
+ if (img.width > maxAtlasSize || img.height > maxAtlasSize) {
125
+ return sizeExceeded(img, maxAtlasSize);
126
+ }
127
+ }
128
+
129
+ const sorted = [...images].sort((a, b) => {
130
+ if (b.height !== a.height) return b.height - a.height;
131
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
132
+ });
133
+
134
+ let totalArea = 0;
135
+ let maxImageWidth = 0;
136
+ for (const img of sorted) {
137
+ totalArea += img.width * img.height;
138
+ if (img.width > maxImageWidth) maxImageWidth = img.width;
139
+ }
140
+ let targetWidth = nextPow2(Math.ceil(Math.sqrt(totalArea)));
141
+ if (maxImageWidth > targetWidth) targetWidth = nextPow2(maxImageWidth);
142
+ if (targetWidth > maxAtlasSize) targetWidth = maxAtlasSize;
143
+
144
+ const regions: AtlasRegion[] = [];
145
+ let cursorX = 0;
146
+ let cursorY = 0;
147
+ const firstHeight = sorted[0]?.height ?? 0;
148
+ let currentShelfHeight = firstHeight;
149
+ let usedWidth = 0;
150
+
151
+ for (const img of sorted) {
152
+ if (cursorX + img.width > targetWidth && cursorX !== 0) {
153
+ cursorY += currentShelfHeight;
154
+ cursorX = 0;
155
+ currentShelfHeight = img.height;
156
+ }
157
+ if (cursorY + img.height > maxAtlasSize) {
158
+ return sizeExceeded(img, maxAtlasSize);
159
+ }
160
+ regions.push({ name: img.name, x: cursorX, y: cursorY, w: img.width, h: img.height });
161
+ cursorX += img.width;
162
+ if (cursorX > usedWidth) usedWidth = cursorX;
163
+ }
164
+
165
+ const usedHeight = cursorY + currentShelfHeight;
166
+ let atlasWidth = nextPow2(usedWidth);
167
+ if (atlasWidth > maxAtlasSize) atlasWidth = maxAtlasSize;
168
+ let atlasHeight = nextPow2(usedHeight);
169
+ if (atlasHeight > maxAtlasSize) atlasHeight = maxAtlasSize;
170
+
171
+ return { ok: true, value: { atlasWidth, atlasHeight, regions } };
172
+ }
@@ -0,0 +1,13 @@
1
+ // Ambient declaration for `upng-js` (PNG decoder/encoder) used by the
2
+ // `forgeax-engine-remote-asset atlas` subcommand (run-atlas.ts) and by
3
+ // its hermetic CLI integration tests under `__tests__/`. The package
4
+ // ships without bundled `.d.ts`; consumer sites cast through local
5
+ // interfaces to match the subset they use.
6
+ //
7
+ // Mirrors `packages/image/src/image-decoders.d.ts` so the `import('upng-js')`
8
+ // site in run-atlas.ts type-resolves under tsc -b without an `any` escape
9
+ // hatch leak. The pack package keeps a private declaration here rather
10
+ // than reaching into engine-image (`@forgeax/engine-image` would create a
11
+ // circular dep: image already depends on pack via @forgeax/engine-pack).
12
+
13
+ declare module 'upng-js';
package/src/bridge.ts ADDED
@@ -0,0 +1,2 @@
1
+ // bridge.ts — AssetRegistry bridge placeholder (M2)
2
+ export {};
package/src/build.ts ADDED
@@ -0,0 +1,139 @@
1
+ // @forgeax/engine-pack/build
2
+ // Node-only catalog, source scanning, package finalization, and build evidence.
3
+
4
+ export type {
5
+ AnimationClip,
6
+ AnimationGraph,
7
+ Asset,
8
+ AudioClipAsset,
9
+ EquirectAsset,
10
+ FontAsset,
11
+ MaterialAsset,
12
+ MeshAsset,
13
+ PackV2,
14
+ PackV2Error,
15
+ ParticleEffectAsset,
16
+ RenderPipelineAsset,
17
+ SamplerAsset,
18
+ SceneAsset,
19
+ SkeletonAsset,
20
+ SkinAsset,
21
+ TextureAsset,
22
+ TilesetAsset,
23
+ VideoAsset,
24
+ } from '@forgeax/engine-types';
25
+ export type { ArtifactPathContext } from './artifact-path.js';
26
+ export { validateArtifactPath } from './artifact-path.js';
27
+ export {
28
+ buildCatalogProjection,
29
+ type CatalogAuthority,
30
+ type CatalogBuildError,
31
+ type CatalogBuildErrorCode,
32
+ type CatalogBuildProjectionOptions,
33
+ type CatalogBuildResult,
34
+ type CatalogImporterPolicy,
35
+ type CatalogProducerVisibility,
36
+ metaPathForGuid,
37
+ } from './catalog-builder.js';
38
+ export { calculateCatalogDelta } from './catalog-delta.js';
39
+ export {
40
+ type CatalogOutputDeclaration,
41
+ type CatalogProducerMeta,
42
+ type CookedPackageProjection,
43
+ catalogProjectionFor,
44
+ currentProjectionFor,
45
+ findReservedAssetKindConflict,
46
+ type PackageCatalogInput,
47
+ projectCookedPackageEntry,
48
+ projectExternalCatalogEntries,
49
+ projectPackageCatalog,
50
+ projectRuntimeCatalogRow,
51
+ type RuntimeCatalogRowInput,
52
+ } from './catalog-projection.js';
53
+ export { type LoadAssetConfigResult, loadAssetConfig } from './config.js';
54
+ export {
55
+ type CookedMaterialRecord,
56
+ collectMaterialCookRefs,
57
+ createMaterialArtifactDigest,
58
+ type MaterialCookArtifact,
59
+ type MaterialCookIdentityExpectation,
60
+ type MaterialCookReceipt,
61
+ type MaterialCookRecordError,
62
+ type MaterialCookRefs,
63
+ projectCookedMaterialRecord,
64
+ serializeCookedMaterialRecord,
65
+ serializeMaterialCookReceipt,
66
+ validateCookedMaterialRecord,
67
+ validateMaterialCookReceipt,
68
+ } from './evidence/material-cook.js';
69
+ export { buildOfflineAssetEvidence, packageVerification } from './evidence/offline-evidence.js';
70
+ export {
71
+ decodeMeshBinHeader,
72
+ MESH_BIN_DIGEST_BYTES,
73
+ MESH_BIN_HEADER_V4_BYTES,
74
+ MESH_BIN_PROJECTION_VERSION,
75
+ MESH_BIN_VERSION,
76
+ type MeshBinContractError,
77
+ type MeshBinHeaderResult,
78
+ type MeshBinHeaderV4,
79
+ writeMeshBinHeader,
80
+ } from './mesh-bin-contract.js';
81
+ export {
82
+ type AuthoredPackAssetInput,
83
+ type AuthoredPackInput,
84
+ type FinalizedPackageProduct,
85
+ finalizePackageProduct,
86
+ finalizePackageTransportSource,
87
+ type PackageArtifactBody,
88
+ type PackageFinalizePolicy,
89
+ type PackageFinalizeResult,
90
+ type PackageFinalizerError,
91
+ type PackageProduct,
92
+ type PackageProductAsset,
93
+ packageTransportRevision,
94
+ upgradeLegacyAuthoredPack,
95
+ } from './package-finalizer.js';
96
+ export { validateProducerContract, validateProducerOutputs } from './producer-contract.js';
97
+ export { resolveAssetSource } from './resolve-asset-source.js';
98
+ export { parsePackV2, validateMeta, validatePack, validatePackV2 } from './runtime.js';
99
+ export {
100
+ createRuntimePackPublication,
101
+ type RuntimePackAssetInput,
102
+ type RuntimePackEnvelope,
103
+ type RuntimePackInput,
104
+ type RuntimePackPublication,
105
+ type RuntimePackPublicationInput,
106
+ } from './runtime-publication.js';
107
+ export {
108
+ type InventoryDeclaration,
109
+ type ScanInventory,
110
+ type ScanOptions,
111
+ type ScriptablePackInventoryDeclaration,
112
+ type ScriptablePackScanOptions,
113
+ STANDARD_SCRIPTABLE_PACK_SCAN_OPTIONS,
114
+ scanInventory,
115
+ } from './scanner.js';
116
+ export {
117
+ type AssetReader,
118
+ isScriptablePackAssetKind,
119
+ projectScriptablePackMeta,
120
+ projectScriptablePackSceneComponents,
121
+ SCRIPTABLE_PACK_ASSET_KINDS,
122
+ SCRIPTABLE_PACK_CAPABILITY_MANIFEST,
123
+ type ScriptablePackAssetDeclaration,
124
+ type ScriptablePackAssetDeclarations,
125
+ type ScriptablePackAssetFor,
126
+ type ScriptablePackAssetKind,
127
+ type ScriptablePackDefinition,
128
+ type ScriptablePackError,
129
+ type ScriptablePackExternalAssets,
130
+ type ScriptablePackMetaJson,
131
+ type ScriptablePackOutputs,
132
+ type ScriptablePackPublicationEnvelope,
133
+ type ScriptablePackReadError,
134
+ type ScriptablePackSceneComponent,
135
+ type ScriptablePackSceneComponentInput,
136
+ type ScriptablePackSourceClosureEntry,
137
+ validateScriptablePackDefinition,
138
+ } from './scriptable-pack.js';
139
+ export { calculateTopologyDiff, diffTopology } from './topology.js';
package/src/builtin.ts ADDED
@@ -0,0 +1,117 @@
1
+ import type { AssetGuid } from './guid.js';
2
+ import { AssetGuid as AssetGuidNS } from './guid.js';
3
+
4
+ /**
5
+ * ForgeaX engine namespace UUID for UUIDv5 derivation.
6
+ * Value: 9a09805a-7623-482e-b322-9fc3591f2a38
7
+ * This is a custom namespace; it is intentionally NOT the RFC 4122 X.500 namespace.
8
+ */
9
+ const _nsResult = AssetGuidNS.parse('9a09805a-7623-482e-b322-9fc3591f2a38');
10
+ if (!_nsResult.ok) throw new Error('builtin: FORGEAX_NAMESPACE is not a valid UUID');
11
+ export const FORGEAX_NAMESPACE: AssetGuid = _nsResult.value;
12
+
13
+ /**
14
+ * Derive a stable UUIDv5 AssetGuid from a name within the ForgeaX namespace.
15
+ * Uses SHA-1 via Node.js `node:crypto` or Web Crypto API (RFC 4122 §4.3).
16
+ * Deterministic: same name always produces the same bytes.
17
+ */
18
+ export async function deriveBuiltin(name: string): Promise<AssetGuid> {
19
+ const nsBytes = FORGEAX_NAMESPACE;
20
+ const nameBytes = new TextEncoder().encode(name);
21
+
22
+ // Concatenate namespace bytes + name bytes for SHA-1 digest
23
+ const concat = new Uint8Array(16 + nameBytes.length);
24
+ concat.set(nsBytes, 0);
25
+ concat.set(nameBytes, 16);
26
+
27
+ let hashBytes: Uint8Array;
28
+
29
+ if (typeof globalThis !== 'undefined' && globalThis.crypto?.subtle !== undefined) {
30
+ // Browser / Web Crypto path
31
+ const buffer = await globalThis.crypto.subtle.digest('SHA-1', concat);
32
+ hashBytes = new Uint8Array(buffer);
33
+ } else {
34
+ // Node.js path
35
+ const { createHash } = await import('node:crypto');
36
+ const hash = createHash('sha1');
37
+ hash.update(concat);
38
+ hashBytes = hash.digest();
39
+ }
40
+
41
+ // RFC 4122 §4.3: assemble UUIDv5 from first 16 bytes of SHA-1 digest
42
+ const uuidBytes = new Uint8Array(16);
43
+ uuidBytes.set(hashBytes.subarray(0, 16));
44
+
45
+ // Set version bits: byte 6, high nibble = 0x5 (version 5)
46
+ // biome-ignore lint/style/noNonNullAssertion: uuidBytes is 16 bytes
47
+ uuidBytes[6] = (uuidBytes[6]! & 0x0f) | 0x50;
48
+ // Set variant bits: byte 8, high 2 bits = 0b10
49
+ // biome-ignore lint/style/noNonNullAssertion: uuidBytes is 16 bytes
50
+ uuidBytes[8] = (uuidBytes[8]! & 0x3f) | 0x80;
51
+
52
+ return uuidBytes as AssetGuid;
53
+ }
54
+
55
+ function formatSync(bytes: Uint8Array): string {
56
+ return AssetGuidNS.format(bytes as AssetGuid);
57
+ }
58
+
59
+ async function deriveSync(name: string): Promise<string> {
60
+ return formatSync(await deriveBuiltin(name));
61
+ }
62
+
63
+ /**
64
+ * Pre-computed compile-time constant for the HANDLE_CUBE builtin GUID.
65
+ * Derived from: deriveBuiltin('HANDLE_CUBE') under FORGEAX_NAMESPACE.
66
+ */
67
+ export const BUILTIN_HANDLE_CUBE = await deriveSync('HANDLE_CUBE');
68
+
69
+ /**
70
+ * Pre-computed compile-time constant for the HANDLE_TRIANGLE builtin GUID.
71
+ * Derived from: deriveBuiltin('HANDLE_TRIANGLE') under FORGEAX_NAMESPACE.
72
+ */
73
+ export const BUILTIN_HANDLE_TRIANGLE = await deriveSync('HANDLE_TRIANGLE');
74
+
75
+ /**
76
+ * Pre-computed compile-time constant for the HANDLE_QUAD builtin GUID.
77
+ * Derived from: deriveBuiltin('HANDLE_QUAD') under FORGEAX_NAMESPACE.
78
+ */
79
+ export const BUILTIN_HANDLE_QUAD = await deriveSync('HANDLE_QUAD');
80
+
81
+ /** Pre-computed UUIDv5 for the Engine-owned sphere mesh descriptor. */
82
+ export const BUILTIN_HANDLE_SPHERE = await deriveSync('HANDLE_SPHERE');
83
+
84
+ /** Pre-computed UUIDv5 for the Engine-owned nine-slice quad descriptor. */
85
+ export const BUILTIN_HANDLE_NINESLICE_QUAD = await deriveSync('HANDLE_NINESLICE_QUAD');
86
+
87
+ /** Pre-computed UUIDv5 for the Engine-owned cylinder mesh descriptor. */
88
+ export const BUILTIN_HANDLE_CYLINDER = await deriveSync('HANDLE_CYLINDER');
89
+
90
+ export type BuiltinMeshGeometry =
91
+ | 'procedural-cube'
92
+ | 'procedural-triangle'
93
+ | 'procedural-quad'
94
+ | 'procedural-sphere'
95
+ | 'procedural-nine-slice-quad'
96
+ | 'procedural-cylinder';
97
+
98
+ /**
99
+ * The complete Engine-owned procedural mesh catalog.
100
+ *
101
+ * These are Pack descriptors, not runtime payloads: the Geometry decoder
102
+ * expands each `geometry` token into its validated MeshAsset. DevKit uses this
103
+ * list to materialize only the descriptors a standalone project does not
104
+ * already author, keeping the shipped game self-contained without duplicate
105
+ * GUID declarations.
106
+ */
107
+ export const BUILTIN_MESH_ASSETS: ReadonlyArray<{
108
+ readonly guid: string;
109
+ readonly geometry: BuiltinMeshGeometry;
110
+ }> = Object.freeze([
111
+ { guid: BUILTIN_HANDLE_CUBE, geometry: 'procedural-cube' },
112
+ { guid: BUILTIN_HANDLE_TRIANGLE, geometry: 'procedural-triangle' },
113
+ { guid: BUILTIN_HANDLE_QUAD, geometry: 'procedural-quad' },
114
+ { guid: BUILTIN_HANDLE_SPHERE, geometry: 'procedural-sphere' },
115
+ { guid: BUILTIN_HANDLE_NINESLICE_QUAD, geometry: 'procedural-nine-slice-quad' },
116
+ { guid: BUILTIN_HANDLE_CYLINDER, geometry: 'procedural-cylinder' },
117
+ ]);