@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,176 @@
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 nextPow2(n: number): number {
86
+ if (n <= 1) return 1;
87
+ let p = 1;
88
+ while (p < n) p <<= 1;
89
+ return p;
90
+ }
91
+
92
+ export function shelfPack(
93
+ images: ReadonlyArray<AtlasImageInput>,
94
+ opts: { readonly maxAtlasSize: number },
95
+ ): ShelfPackOutcome {
96
+ const { maxAtlasSize } = opts;
97
+
98
+ if (images.length === 0) {
99
+ return {
100
+ ok: false,
101
+ error: { code: 'atlas-empty-input', detail: { receivedCount: 0 } },
102
+ };
103
+ }
104
+
105
+ for (const img of images) {
106
+ if (img.width > maxAtlasSize || img.height > maxAtlasSize) {
107
+ return {
108
+ ok: false,
109
+ error: {
110
+ code: 'atlas-size-exceeded',
111
+ detail: {
112
+ name: img.name,
113
+ width: img.width,
114
+ height: img.height,
115
+ maxAtlasSize,
116
+ },
117
+ },
118
+ };
119
+ }
120
+ }
121
+
122
+ const sorted = [...images].sort((a, b) => {
123
+ if (b.height !== a.height) return b.height - a.height;
124
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
125
+ });
126
+
127
+ let totalArea = 0;
128
+ let maxImageWidth = 0;
129
+ for (const img of sorted) {
130
+ totalArea += img.width * img.height;
131
+ if (img.width > maxImageWidth) maxImageWidth = img.width;
132
+ }
133
+ let targetWidth = nextPow2(Math.ceil(Math.sqrt(totalArea)));
134
+ if (maxImageWidth > targetWidth) targetWidth = nextPow2(maxImageWidth);
135
+ if (targetWidth > maxAtlasSize) targetWidth = maxAtlasSize;
136
+
137
+ const regions: AtlasRegion[] = [];
138
+ let cursorX = 0;
139
+ let cursorY = 0;
140
+ const firstHeight = sorted[0]?.height ?? 0;
141
+ let currentShelfHeight = firstHeight;
142
+ let usedWidth = 0;
143
+
144
+ for (const img of sorted) {
145
+ if (cursorX + img.width > targetWidth && cursorX !== 0) {
146
+ cursorY += currentShelfHeight;
147
+ cursorX = 0;
148
+ currentShelfHeight = img.height;
149
+ }
150
+ if (cursorY + img.height > maxAtlasSize) {
151
+ return {
152
+ ok: false,
153
+ error: {
154
+ code: 'atlas-size-exceeded',
155
+ detail: {
156
+ name: img.name,
157
+ width: img.width,
158
+ height: img.height,
159
+ maxAtlasSize,
160
+ },
161
+ },
162
+ };
163
+ }
164
+ regions.push({ name: img.name, x: cursorX, y: cursorY, w: img.width, h: img.height });
165
+ cursorX += img.width;
166
+ if (cursorX > usedWidth) usedWidth = cursorX;
167
+ }
168
+
169
+ const usedHeight = cursorY + currentShelfHeight;
170
+ let atlasWidth = nextPow2(usedWidth);
171
+ if (atlasWidth > maxAtlasSize) atlasWidth = maxAtlasSize;
172
+ let atlasHeight = nextPow2(usedHeight);
173
+ if (atlasHeight > maxAtlasSize) atlasHeight = maxAtlasSize;
174
+
175
+ return { ok: true, value: { atlasWidth, atlasHeight, regions } };
176
+ }
@@ -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/builtin.ts ADDED
@@ -0,0 +1,79 @@
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');