@oxy.so/bloom 7.1.1 → 7.1.4

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 (246) hide show
  1. package/assets/character-runtime/README.md +114 -19
  2. package/assets/character-runtime/authored-parts.mjs +754 -0
  3. package/assets/character-runtime/catalog-parts.mjs +362 -0
  4. package/assets/character-runtime/character-recipe.mjs +247 -0
  5. package/assets/character-runtime/clippo-geometry.mjs +679 -0
  6. package/assets/character-runtime/controller-mode.mjs +208 -0
  7. package/assets/character-runtime/cyclops-geometry.mjs +180 -0
  8. package/assets/character-runtime/legacy-engine.mjs +406 -54
  9. package/assets/character-runtime/legacy-geometry.mjs +242 -85
  10. package/assets/character-runtime/legacy-worker.mjs +82 -23
  11. package/assets/character-runtime/managed-avatar.mjs +12 -17
  12. package/assets/character-runtime/migrated-contours.mjs +2067 -0
  13. package/assets/character-runtime/migrated-faces.mjs +59 -0
  14. package/assets/character-runtime/pose-transition.mjs +523 -0
  15. package/assets/character-runtime/preparation-cache.mjs +60 -0
  16. package/assets/character-runtime/render-budget.mjs +5 -3
  17. package/assets/character-runtime/render-errors.mjs +180 -0
  18. package/assets/character-runtime/resolution.mjs +10 -0
  19. package/assets/character-runtime/runtime.mjs +744 -295
  20. package/assets/character-runtime/sha256.mjs +44 -0
  21. package/assets/character-runtime/shared-programs.mjs +534 -0
  22. package/assets/character-runtime/shared-surface.mjs +490 -0
  23. package/assets/character-runtime/shared-textures.mjs +420 -0
  24. package/assets/character-runtime/thumbnails/eyes/todd.png +0 -0
  25. package/docs/adoption-matrix.mdx +2 -2
  26. package/docs/agent-avatar.mdx +45 -8
  27. package/docs/agent-creator.mdx +13 -3
  28. package/docs/bottom-sheet.mdx +1 -1
  29. package/lib/commonjs/agent-avatar/AgentAvatar.js +1 -1
  30. package/lib/commonjs/agent-avatar/AgentAvatar.js.map +1 -1
  31. package/lib/commonjs/agent-avatar/CharacterAvatar.js +2 -1
  32. package/lib/commonjs/agent-avatar/CharacterAvatar.js.map +1 -1
  33. package/lib/commonjs/agent-avatar/character-colors.js +19 -0
  34. package/lib/commonjs/agent-avatar/character-colors.js.map +1 -0
  35. package/lib/commonjs/agent-avatar/character-shapes.js +106 -0
  36. package/lib/commonjs/agent-avatar/character-shapes.js.map +1 -0
  37. package/lib/commonjs/agent-avatar/config-character.js +4 -0
  38. package/lib/commonjs/agent-avatar/config-character.js.map +1 -1
  39. package/lib/commonjs/agent-avatar/legacy-recipe.js +19 -6
  40. package/lib/commonjs/agent-avatar/legacy-recipe.js.map +1 -1
  41. package/lib/commonjs/agent-creator/AgentCreatorBase.js +67 -107
  42. package/lib/commonjs/agent-creator/AgentCreatorBase.js.map +1 -1
  43. package/lib/commonjs/agent-creator/CharacterControls.js +23 -5
  44. package/lib/commonjs/agent-creator/CharacterControls.js.map +1 -1
  45. package/lib/commonjs/agent-creator/ClippoArtwork.js +63 -0
  46. package/lib/commonjs/agent-creator/ClippoArtwork.js.map +1 -0
  47. package/lib/commonjs/agent-creator/CyclopsArtwork.js +45 -0
  48. package/lib/commonjs/agent-creator/CyclopsArtwork.js.map +1 -0
  49. package/lib/commonjs/agent-creator/EmotionPicker.js +8 -1
  50. package/lib/commonjs/agent-creator/EmotionPicker.js.map +1 -1
  51. package/lib/commonjs/agent-creator/ShapeSilhouette.js +8 -1
  52. package/lib/commonjs/agent-creator/ShapeSilhouette.js.map +1 -1
  53. package/lib/commonjs/agent-creator/ToddArtwork.js +29 -0
  54. package/lib/commonjs/agent-creator/ToddArtwork.js.map +1 -0
  55. package/lib/commonjs/agent-creator/constants.js +16 -17
  56. package/lib/commonjs/agent-creator/constants.js.map +1 -1
  57. package/lib/commonjs/agent-creator/messages.js +1 -1
  58. package/lib/commonjs/agent-creator/messages.js.map +1 -1
  59. package/lib/commonjs/bottom-sheet/BottomSheetBase.js +33 -49
  60. package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
  61. package/lib/commonjs/dialog/DialogBottomSheet.js +1 -1
  62. package/lib/commonjs/dialog/DialogBottomSheet.js.map +1 -1
  63. package/lib/commonjs/locale/translations/ar.js +1 -1
  64. package/lib/commonjs/locale/translations/ar.js.map +1 -1
  65. package/lib/commonjs/locale/translations/bn.js +1 -1
  66. package/lib/commonjs/locale/translations/bn.js.map +1 -1
  67. package/lib/commonjs/locale/translations/ca.js +1 -1
  68. package/lib/commonjs/locale/translations/ca.js.map +1 -1
  69. package/lib/commonjs/locale/translations/de.js +1 -1
  70. package/lib/commonjs/locale/translations/de.js.map +1 -1
  71. package/lib/commonjs/locale/translations/es.js +1 -1
  72. package/lib/commonjs/locale/translations/es.js.map +1 -1
  73. package/lib/commonjs/locale/translations/fr.js +1 -1
  74. package/lib/commonjs/locale/translations/fr.js.map +1 -1
  75. package/lib/commonjs/locale/translations/hi.js +1 -1
  76. package/lib/commonjs/locale/translations/hi.js.map +1 -1
  77. package/lib/commonjs/locale/translations/id.js +1 -1
  78. package/lib/commonjs/locale/translations/id.js.map +1 -1
  79. package/lib/commonjs/locale/translations/it.js +1 -1
  80. package/lib/commonjs/locale/translations/it.js.map +1 -1
  81. package/lib/commonjs/locale/translations/ja.js +1 -1
  82. package/lib/commonjs/locale/translations/ja.js.map +1 -1
  83. package/lib/commonjs/locale/translations/pt.js +1 -1
  84. package/lib/commonjs/locale/translations/pt.js.map +1 -1
  85. package/lib/commonjs/locale/translations/ru.js +1 -1
  86. package/lib/commonjs/locale/translations/ru.js.map +1 -1
  87. package/lib/commonjs/locale/translations/tr.js +1 -1
  88. package/lib/commonjs/locale/translations/tr.js.map +1 -1
  89. package/lib/commonjs/locale/translations/zh.js +1 -1
  90. package/lib/commonjs/locale/translations/zh.js.map +1 -1
  91. package/lib/commonjs/surface/SurfacePaint.js +17 -4
  92. package/lib/commonjs/surface/SurfacePaint.js.map +1 -1
  93. package/lib/module/agent-avatar/AgentAvatar.js +1 -1
  94. package/lib/module/agent-avatar/AgentAvatar.js.map +1 -1
  95. package/lib/module/agent-avatar/CharacterAvatar.js +2 -1
  96. package/lib/module/agent-avatar/CharacterAvatar.js.map +1 -1
  97. package/lib/module/agent-avatar/character-colors.js +15 -0
  98. package/lib/module/agent-avatar/character-colors.js.map +1 -0
  99. package/lib/module/agent-avatar/character-shapes.js +97 -0
  100. package/lib/module/agent-avatar/character-shapes.js.map +1 -0
  101. package/lib/module/agent-avatar/config-character.js +4 -0
  102. package/lib/module/agent-avatar/config-character.js.map +1 -1
  103. package/lib/module/agent-avatar/legacy-recipe.js +19 -6
  104. package/lib/module/agent-avatar/legacy-recipe.js.map +1 -1
  105. package/lib/module/agent-creator/AgentCreatorBase.js +69 -109
  106. package/lib/module/agent-creator/AgentCreatorBase.js.map +1 -1
  107. package/lib/module/agent-creator/CharacterControls.js +24 -6
  108. package/lib/module/agent-creator/CharacterControls.js.map +1 -1
  109. package/lib/module/agent-creator/ClippoArtwork.js +59 -0
  110. package/lib/module/agent-creator/ClippoArtwork.js.map +1 -0
  111. package/lib/module/agent-creator/CyclopsArtwork.js +41 -0
  112. package/lib/module/agent-creator/CyclopsArtwork.js.map +1 -0
  113. package/lib/module/agent-creator/EmotionPicker.js +8 -1
  114. package/lib/module/agent-creator/EmotionPicker.js.map +1 -1
  115. package/lib/module/agent-creator/ShapeSilhouette.js +8 -1
  116. package/lib/module/agent-creator/ShapeSilhouette.js.map +1 -1
  117. package/lib/module/agent-creator/ToddArtwork.js +25 -0
  118. package/lib/module/agent-creator/ToddArtwork.js.map +1 -0
  119. package/lib/module/agent-creator/constants.js +8 -16
  120. package/lib/module/agent-creator/constants.js.map +1 -1
  121. package/lib/module/agent-creator/messages.js +1 -1
  122. package/lib/module/agent-creator/messages.js.map +1 -1
  123. package/lib/module/bottom-sheet/BottomSheetBase.js +33 -49
  124. package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
  125. package/lib/module/dialog/DialogBottomSheet.js +1 -1
  126. package/lib/module/dialog/DialogBottomSheet.js.map +1 -1
  127. package/lib/module/locale/translations/ar.js +1 -1
  128. package/lib/module/locale/translations/ar.js.map +1 -1
  129. package/lib/module/locale/translations/bn.js +1 -1
  130. package/lib/module/locale/translations/bn.js.map +1 -1
  131. package/lib/module/locale/translations/ca.js +1 -1
  132. package/lib/module/locale/translations/ca.js.map +1 -1
  133. package/lib/module/locale/translations/de.js +1 -1
  134. package/lib/module/locale/translations/de.js.map +1 -1
  135. package/lib/module/locale/translations/es.js +1 -1
  136. package/lib/module/locale/translations/es.js.map +1 -1
  137. package/lib/module/locale/translations/fr.js +1 -1
  138. package/lib/module/locale/translations/fr.js.map +1 -1
  139. package/lib/module/locale/translations/hi.js +1 -1
  140. package/lib/module/locale/translations/hi.js.map +1 -1
  141. package/lib/module/locale/translations/id.js +1 -1
  142. package/lib/module/locale/translations/id.js.map +1 -1
  143. package/lib/module/locale/translations/it.js +1 -1
  144. package/lib/module/locale/translations/it.js.map +1 -1
  145. package/lib/module/locale/translations/ja.js +1 -1
  146. package/lib/module/locale/translations/ja.js.map +1 -1
  147. package/lib/module/locale/translations/pt.js +1 -1
  148. package/lib/module/locale/translations/pt.js.map +1 -1
  149. package/lib/module/locale/translations/ru.js +1 -1
  150. package/lib/module/locale/translations/ru.js.map +1 -1
  151. package/lib/module/locale/translations/tr.js +1 -1
  152. package/lib/module/locale/translations/tr.js.map +1 -1
  153. package/lib/module/locale/translations/zh.js +1 -1
  154. package/lib/module/locale/translations/zh.js.map +1 -1
  155. package/lib/module/surface/SurfacePaint.js +18 -5
  156. package/lib/module/surface/SurfacePaint.js.map +1 -1
  157. package/lib/typescript/commonjs/agent-avatar/CharacterAvatar.d.ts.map +1 -1
  158. package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts +13 -0
  159. package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts.map +1 -0
  160. package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts +19 -0
  161. package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts.map +1 -0
  162. package/lib/typescript/commonjs/agent-avatar/config-character.d.ts +4 -0
  163. package/lib/typescript/commonjs/agent-avatar/config-character.d.ts.map +1 -1
  164. package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts +2 -10
  165. package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts.map +1 -1
  166. package/lib/typescript/commonjs/agent-creator/AgentCreatorBase.d.ts.map +1 -1
  167. package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts +2 -1
  168. package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts.map +1 -1
  169. package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts +6 -0
  170. package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts.map +1 -0
  171. package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts +5 -0
  172. package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts.map +1 -0
  173. package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts +1 -0
  174. package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts.map +1 -1
  175. package/lib/typescript/commonjs/agent-creator/ShapeSilhouette.d.ts.map +1 -1
  176. package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts +6 -0
  177. package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts.map +1 -0
  178. package/lib/typescript/commonjs/agent-creator/constants.d.ts +7 -15
  179. package/lib/typescript/commonjs/agent-creator/constants.d.ts.map +1 -1
  180. package/lib/typescript/commonjs/agent-creator/messages.d.ts.map +1 -1
  181. package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  182. package/lib/typescript/commonjs/bottom-sheet/types.d.ts +1 -0
  183. package/lib/typescript/commonjs/bottom-sheet/types.d.ts.map +1 -1
  184. package/lib/typescript/commonjs/surface/SurfacePaint.d.ts.map +1 -1
  185. package/lib/typescript/module/agent-avatar/CharacterAvatar.d.ts.map +1 -1
  186. package/lib/typescript/module/agent-avatar/character-colors.d.ts +13 -0
  187. package/lib/typescript/module/agent-avatar/character-colors.d.ts.map +1 -0
  188. package/lib/typescript/module/agent-avatar/character-shapes.d.ts +19 -0
  189. package/lib/typescript/module/agent-avatar/character-shapes.d.ts.map +1 -0
  190. package/lib/typescript/module/agent-avatar/config-character.d.ts +4 -0
  191. package/lib/typescript/module/agent-avatar/config-character.d.ts.map +1 -1
  192. package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts +2 -10
  193. package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts.map +1 -1
  194. package/lib/typescript/module/agent-creator/AgentCreatorBase.d.ts.map +1 -1
  195. package/lib/typescript/module/agent-creator/CharacterControls.d.ts +2 -1
  196. package/lib/typescript/module/agent-creator/CharacterControls.d.ts.map +1 -1
  197. package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts +6 -0
  198. package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts.map +1 -0
  199. package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts +5 -0
  200. package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts.map +1 -0
  201. package/lib/typescript/module/agent-creator/EmotionPicker.d.ts +1 -0
  202. package/lib/typescript/module/agent-creator/EmotionPicker.d.ts.map +1 -1
  203. package/lib/typescript/module/agent-creator/ShapeSilhouette.d.ts.map +1 -1
  204. package/lib/typescript/module/agent-creator/ToddArtwork.d.ts +6 -0
  205. package/lib/typescript/module/agent-creator/ToddArtwork.d.ts.map +1 -0
  206. package/lib/typescript/module/agent-creator/constants.d.ts +7 -15
  207. package/lib/typescript/module/agent-creator/constants.d.ts.map +1 -1
  208. package/lib/typescript/module/agent-creator/messages.d.ts.map +1 -1
  209. package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  210. package/lib/typescript/module/bottom-sheet/types.d.ts +1 -0
  211. package/lib/typescript/module/bottom-sheet/types.d.ts.map +1 -1
  212. package/lib/typescript/module/surface/SurfacePaint.d.ts.map +1 -1
  213. package/package.json +1 -1
  214. package/src/agent-avatar/AgentAvatar.tsx +1 -1
  215. package/src/agent-avatar/CharacterAvatar.tsx +4 -1
  216. package/src/agent-avatar/character-colors.ts +12 -0
  217. package/src/agent-avatar/character-shapes.ts +122 -0
  218. package/src/agent-avatar/config-character.ts +39 -6
  219. package/src/agent-avatar/legacy-recipe.ts +34 -6
  220. package/src/agent-creator/AgentCreatorBase.tsx +121 -152
  221. package/src/agent-creator/CharacterControls.tsx +39 -14
  222. package/src/agent-creator/ClippoArtwork.tsx +49 -0
  223. package/src/agent-creator/CyclopsArtwork.tsx +27 -0
  224. package/src/agent-creator/EmotionPicker.tsx +55 -16
  225. package/src/agent-creator/ShapeSilhouette.tsx +16 -1
  226. package/src/agent-creator/ToddArtwork.tsx +24 -0
  227. package/src/agent-creator/constants.ts +47 -14
  228. package/src/agent-creator/messages.ts +2 -1
  229. package/src/bottom-sheet/BottomSheetBase.tsx +24 -34
  230. package/src/bottom-sheet/types.ts +1 -0
  231. package/src/dialog/DialogBottomSheet.tsx +1 -1
  232. package/src/locale/translations/ar.ts +1 -1
  233. package/src/locale/translations/bn.ts +1 -1
  234. package/src/locale/translations/ca.ts +1 -1
  235. package/src/locale/translations/de.ts +1 -1
  236. package/src/locale/translations/es.ts +1 -1
  237. package/src/locale/translations/fr.ts +1 -1
  238. package/src/locale/translations/hi.ts +1 -1
  239. package/src/locale/translations/id.ts +1 -1
  240. package/src/locale/translations/it.ts +1 -1
  241. package/src/locale/translations/ja.ts +1 -1
  242. package/src/locale/translations/pt.ts +1 -1
  243. package/src/locale/translations/ru.ts +1 -1
  244. package/src/locale/translations/tr.ts +1 -1
  245. package/src/locale/translations/zh.ts +1 -1
  246. package/src/surface/SurfacePaint.tsx +14 -4
@@ -0,0 +1,362 @@
1
+ // Compatibility comes from original native donor meshes, never a fake enabled
2
+ // catalog selection. Source assemblies and the target's activity payload stay
3
+ // in the original renderer.
4
+ import { decodeAppearance, encodeAppearance } from './appearance-codec.mjs';
5
+ import {
6
+ authoredAssemblyRecords,
7
+ authoredEyeRecords,
8
+ assembleAuthoredRecords,
9
+ fitCatalogPartRecords,
10
+ transformAuthoredRecord,
11
+ } from './authored-parts.mjs';
12
+ import { NATIVE_PARTS } from './character-recipe.mjs';
13
+ import { sha256 } from './sha256.mjs';
14
+ const encoder = new TextEncoder();
15
+ const fail = (message) => {
16
+ throw Error(`Catalog avatar parts: ${message}`);
17
+ };
18
+ const references = new Map();
19
+ let retainedBytes = 0;
20
+ const MAXIMUM_BYTES = 8 * 1024 * 1024;
21
+ const originalPaint = new Map();
22
+ /** Read an unnamed paint from the actual original body material, not a palette approximation. */
23
+ export function originalBodyColor(module, preset) {
24
+ if (originalPaint.has(preset)) return originalPaint.get(preset);
25
+ const result = module.orbitPrepareAssembly(
26
+ module.presetAppearance(preset),
27
+ 0,
28
+ `original-paint:${preset}`,
29
+ false,
30
+ );
31
+ if (!result.bytes || result.error)
32
+ fail(result.error || 'original body paint missing');
33
+ const bytes = result.bytes,
34
+ view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
35
+ if (
36
+ bytes.length < 48 ||
37
+ view.getUint32(0, true) < 1 ||
38
+ view.getUint32(28, true) !== 1
39
+ )
40
+ fail('unrecognized original body paint layout');
41
+ const rgb = [0, 1, 2].map((k) => view.getFloat32(32 + k * 4, true));
42
+ if (rgb.some((v) => !Number.isFinite(v) || v < 0 || v > 1))
43
+ fail('invalid original body paint');
44
+ const hex =
45
+ '#' +
46
+ rgb
47
+ .map((v) =>
48
+ Math.round(v * 255)
49
+ .toString(16)
50
+ .padStart(2, '0'),
51
+ )
52
+ .join('');
53
+ originalPaint.set(preset, hex);
54
+ return hex;
55
+ }
56
+ /** Editable backing paint, with the original RGB retained for unnamed colors. */
57
+ export function originalPalettePaint(module, preset) {
58
+ const color = decodeAppearance(module.presetAppearance(preset)).color;
59
+ return module.catalog(1).some((item) => item.id === color)
60
+ ? { color }
61
+ : { color: 'yellow', bodyColor: originalBodyColor(module, preset) };
62
+ }
63
+ export function recordHasLabel(info, part, label) {
64
+ const needle = encoder.encode(label),
65
+ bytes = info.bytes;
66
+ for (
67
+ let at = part.start + 24;
68
+ at + 4 + needle.length <= part.nameOffset - 4;
69
+ at++
70
+ )
71
+ if (
72
+ new DataView(bytes.buffer, bytes.byteOffset + at, 4).getUint32(
73
+ 0,
74
+ true,
75
+ ) === needle.length &&
76
+ needle.every((v, i) => bytes[at + 4 + i] === v)
77
+ )
78
+ return true;
79
+ return false;
80
+ }
81
+ function isCategory(info, part, category) {
82
+ const ids =
83
+ category === 'eyes'
84
+ ? [...NATIVE_PARTS.eyes, 'source_integrated_eye', 'brows']
85
+ : NATIVE_PARTS[category].filter((id) => id !== 'none');
86
+ return ids.some((id) => recordHasLabel(info, part, id));
87
+ }
88
+ function record(info, part) {
89
+ return {
90
+ bytes: info.bytes.slice(part.start, part.end),
91
+ name: part.name,
92
+ bounds: part.bounds,
93
+ };
94
+ }
95
+ function mappedFaceReference(module, appearance, request) {
96
+ const result = module.orbitPrepareAssembly(
97
+ appearance,
98
+ request.quality,
99
+ `${request.key}:uncovered-donor`,
100
+ false,
101
+ );
102
+ if (!result.bytes || result.error) return null;
103
+ const info = authoredAssemblyRecords(result.bytes.slice());
104
+ return info.records.slice(1).some((part) => isCategory(info, part, 'eyes'))
105
+ ? info
106
+ : null;
107
+ }
108
+ async function reference(module, category, id, request) {
109
+ const key = JSON.stringify([category, id, request.quality]);
110
+ if (references.has(key)) {
111
+ const cached = references.get(key);
112
+ references.delete(key);
113
+ references.set(key, cached);
114
+ return cached;
115
+ }
116
+ if (category === 'eyes' && id === 'todd') {
117
+ const result = module.orbitPrepareAssembly(
118
+ module.presetAppearance('lime_frog'),
119
+ request.quality,
120
+ `${request.key}:donor:eyes:todd`,
121
+ false,
122
+ );
123
+ if (!result.bytes || result.error)
124
+ fail(result.error || 'Todd eye donor missing');
125
+ const info = authoredAssemblyRecords(result.bytes.slice());
126
+ if (!authoredEyeRecords(info).length) fail('Todd eye meshes missing');
127
+ return info;
128
+ }
129
+ if (category === 'accessory' && id === 'felipe_beret') {
130
+ const result = module.orbitPrepareAssembly(
131
+ module.presetAppearance('blue_beret'),
132
+ request.quality,
133
+ `${request.key}:donor:accessory:felipe_beret`,
134
+ false,
135
+ );
136
+ if (!result.bytes || result.error)
137
+ fail(result.error || 'Felipe beret donor missing');
138
+ const info = authoredAssemblyRecords(result.bytes.slice());
139
+ if (
140
+ !info.records
141
+ .slice(1)
142
+ .some((part) => recordHasLabel(info, part, 'beret'))
143
+ )
144
+ fail('Felipe beret mesh missing');
145
+ return info;
146
+ }
147
+ const shapes = module.catalog(0).map((item) => item.id),
148
+ eyes =
149
+ category === 'eyes'
150
+ ? [id]
151
+ : ['oval', ...NATIVE_PARTS.eyes.filter((id) => id !== 'oval')];
152
+ let donor;
153
+ for (const shape of ['circle', ...shapes.filter((id) => id !== 'circle')]) {
154
+ for (const eye of eyes) {
155
+ const appearance = encodeAppearance({
156
+ version: 1,
157
+ shape,
158
+ color: 'blue',
159
+ eyes: eye,
160
+ eyewear: category === 'eyewear' ? id : 'none',
161
+ accessories: category === 'accessory' ? [id] : [],
162
+ accessoryColors: {},
163
+ constrained: 0,
164
+ depth: 0.5,
165
+ model: null,
166
+ rig: null,
167
+ hereCharacter: null,
168
+ });
169
+ const result = module.orbitPrepareAssembly(
170
+ appearance,
171
+ request.quality,
172
+ `${request.key}:donor:${category}:${id}:${shape}:${eye}`,
173
+ false,
174
+ );
175
+ if (!result.bytes || result.error) continue;
176
+ let info = authoredAssemblyRecords(result.bytes.slice());
177
+ if (!info.records.slice(1).some((part) => recordHasLabel(info, part, id)))
178
+ continue;
179
+ if (
180
+ !info.records.slice(1).some((part) => isCategory(info, part, 'eyes'))
181
+ ) {
182
+ const exposed = decodeAppearance(appearance);
183
+ exposed.eyewear = 'none';
184
+ const face = mappedFaceReference(
185
+ module,
186
+ encodeAppearance(exposed),
187
+ request,
188
+ );
189
+ if (!face) continue;
190
+ const records = info.records.map((part) => record(info, part));
191
+ records.splice(
192
+ 1,
193
+ 0,
194
+ ...face.records
195
+ .slice(1)
196
+ .filter((part) => isCategory(face, part, 'eyes'))
197
+ .map((part) => record(face, part)),
198
+ );
199
+ info = authoredAssemblyRecords(
200
+ await assembleAuthoredRecords(info, records),
201
+ );
202
+ }
203
+ donor = info;
204
+ break;
205
+ }
206
+ if (donor) break;
207
+ }
208
+ if (!donor) fail(`no original donor for ${category}:${id}`);
209
+ if (donor.bytes.length <= MAXIMUM_BYTES) {
210
+ references.set(key, donor);
211
+ retainedBytes += donor.bytes.length;
212
+ while (references.size > 24 || retainedBytes > MAXIMUM_BYTES) {
213
+ const first = references.keys().next().value;
214
+ retainedBytes -= references.get(first).bytes.length;
215
+ references.delete(first);
216
+ }
217
+ }
218
+ return donor;
219
+ }
220
+ /** Keep actual named-preset geometry/default pieces when only parts or paint
221
+ * change. Original untouched presets never enter this adapter. */
222
+ export async function composePresetBody(module, bytes, request) {
223
+ const preset = request.authoredParts?.bodyPreset;
224
+ if (!preset) return bytes;
225
+ const prepared = module.orbitPrepareAssembly(
226
+ module.presetAppearance(preset),
227
+ request.quality,
228
+ `${request.key}:body:${preset}`,
229
+ request.activities,
230
+ );
231
+ if (!prepared.bytes || prepared.error)
232
+ fail(prepared.error || 'original preset body missing');
233
+ const source = authoredAssemblyRecords(prepared.bytes.slice()),
234
+ target = authoredAssemblyRecords(bytes),
235
+ records = source.records.map((part) => record(source, part));
236
+ if (
237
+ !source.records.slice(1).some((part) => isCategory(source, part, 'eyes'))
238
+ ) {
239
+ // Opaque sunglasses legitimately omit the original eye meshes. Recover the
240
+ // engine's actual hidden-eye reference without changing the preserved body.
241
+ const exposed = decodeAppearance(module.presetAppearance(preset));
242
+ exposed.hereCharacter = null;
243
+ exposed.eyewear = 'none';
244
+ if (exposed.model) exposed.model.eyewear = null;
245
+ const result = module.orbitPrepareAssembly(
246
+ encodeAppearance(exposed),
247
+ request.quality,
248
+ `${request.key}:hidden-eyes:${preset}`,
249
+ false,
250
+ );
251
+ if (!result.bytes || result.error)
252
+ fail(result.error || 'hidden original eye reference missing');
253
+ const face = authoredAssemblyRecords(result.bytes.slice()),
254
+ eyes = face.records
255
+ .slice(1)
256
+ .filter((part) => isCategory(face, part, 'eyes'));
257
+ if (!eyes.length) fail('hidden original eye classification missing');
258
+ records.splice(1, 0, ...eyes.map((part) => record(face, part)));
259
+ }
260
+
261
+ if (request.authoredParts.paintBody) {
262
+ const out = new DataView(records[0].bytes.buffer),
263
+ sourceView = new DataView(source.bytes.buffer, source.bytes.byteOffset),
264
+ targetView = new DataView(target.bytes.buffer, target.bytes.byteOffset);
265
+ if (
266
+ sourceView.getUint32(source.body.start + 24, true) !== 1 ||
267
+ targetView.getUint32(target.body.start + 24, true) !== 1
268
+ )
269
+ fail('unrecognized body paint layout');
270
+ for (const at of [28, 60])
271
+ for (let k = 0; k < 3; k++)
272
+ out.setFloat32(
273
+ at + k * 4,
274
+ targetView.getFloat32(target.body.start + at + k * 4, true),
275
+ true,
276
+ );
277
+ const hash = await sha256(records[0].bytes);
278
+ records[0].bytes.set(
279
+ encoder.encode(hash),
280
+ source.body.nameOffset - source.body.start,
281
+ );
282
+ records[0].name = hash + source.body.name.slice(64);
283
+ }
284
+ // Preserve the body's actual authored activity payload and fitted hands,
285
+ // instead of borrowing the neutral backing circle's fit.
286
+ const footer = source.footer.slice(),
287
+ view = new DataView(footer.buffer);
288
+ for (let k = 0; k < 3; k++) {
289
+ view.setFloat32(28 + k * 4, source.body.bounds.min[k], true);
290
+ view.setFloat32(40 + k * 4, source.body.bounds.max[k], true);
291
+ }
292
+ return assembleAuthoredRecords(
293
+ { ...source, body: { ...source.body, name: records[0].name }, footer },
294
+ records,
295
+ );
296
+ }
297
+ /** Native catalog pieces can be transplanted onto any actual body. */
298
+ export async function composeCatalogParts(
299
+ module,
300
+ bytes,
301
+ request,
302
+ categories = ['eyes', 'eyewear', 'accessory'],
303
+ ) {
304
+ let info = authoredAssemblyRecords(bytes);
305
+ for (const category of categories) {
306
+ const id = request.authoredParts?.[category];
307
+ if (!id) continue;
308
+ // An authored body's own eye style already has its exact sculpted layout.
309
+ if (
310
+ category === 'eyes' &&
311
+ request.authoredParts.bodyPreset &&
312
+ authoredEyeRecords(info).length &&
313
+ authoredEyeRecords(info).every((p) => recordHasLabel(info, p, id))
314
+ )
315
+ continue;
316
+ const virtual =
317
+ (category === 'accessory' && id === 'felipe_beret') ||
318
+ (category === 'eyes' && id === 'todd');
319
+ if (!NATIVE_PARTS[category].includes(id) && !virtual) continue;
320
+ const records = info.records.map((part) => record(info, part));
321
+ const removed = new Set(
322
+ info.records
323
+ .slice(1)
324
+ .filter((part) => isCategory(info, part, category))
325
+ .map((part) => part.part),
326
+ );
327
+ if (category === 'eyes' && !removed.size)
328
+ fail('target face classification missing');
329
+ let fitted = [];
330
+ if (id !== 'none') {
331
+ const donor = await reference(module, category, id, request);
332
+ const transforms = fitCatalogPartRecords(
333
+ info,
334
+ donor,
335
+ category,
336
+ id === 'felipe_beret' ? 'beret' : id,
337
+ {
338
+ singleEye: request.authoredParts?.eyes === 'cyclops',
339
+ faceLayout: request.faceLayout,
340
+ },
341
+ );
342
+ fitted = await Promise.all(
343
+ transforms.map(({ part, ...transform }) =>
344
+ transformAuthoredRecord(donor.bytes, part, transform),
345
+ ),
346
+ );
347
+ }
348
+ const kept = records.filter((_, index) => !removed.has(index));
349
+ const at = category === 'eyes' ? 1 : kept.length;
350
+ kept.splice(at, 0, ...fitted);
351
+ bytes = await assembleAuthoredRecords(info, kept);
352
+ info = authoredAssemblyRecords(bytes);
353
+ }
354
+ return bytes;
355
+ }
356
+ export function catalogPartCacheStats() {
357
+ return {
358
+ entries: references.size,
359
+ bytes: retainedBytes,
360
+ maximumBytes: MAXIMUM_BYTES,
361
+ };
362
+ }
@@ -0,0 +1,247 @@
1
+ /** Resolve editable defaults without passing virtual catalog IDs to the engine. */
2
+ import { MIGRATED_CONTOURS } from './migrated-contours.mjs';
3
+ export function isLegacyRecipe(props) {
4
+ return !props.config.character || props.config.character.preset === 'bloom';
5
+ }
6
+ export const MIGRATED_SHAPE_IDS = Object.freeze([
7
+ 'slender',
8
+ 'pocket',
9
+ 'petal',
10
+ 'star',
11
+ 'cloud',
12
+ 'shield',
13
+ 'pebble',
14
+ 'squircle',
15
+ ]);
16
+ export function withCharacterGeometry(props) {
17
+ const shape = props.config.character?.selections?.shape;
18
+ if (!Object.hasOwn(MIGRATED_CONTOURS, shape)) return props;
19
+ return {
20
+ ...props,
21
+ legacy: props.legacy?.points
22
+ ? props.legacy
23
+ : { points: MIGRATED_CONTOURS[shape] },
24
+ };
25
+ }
26
+ const PRESET_DEFAULTS = Object.freeze({
27
+ blue_beret: {
28
+ shape: 'six_lobed_flower',
29
+ eyes: 'oval',
30
+ eyewear: 'none',
31
+ accessory: 'felipe_beret',
32
+ },
33
+ alfred: {
34
+ shape: 'rounded_triangle',
35
+ eyes: 'sleepy_lids',
36
+ eyewear: 'tall_oval_frames',
37
+ accessory: 'bow',
38
+ },
39
+ purple_heart: {
40
+ shape: 'heart',
41
+ eyes: 'oval',
42
+ eyewear: 'round_sunglasses',
43
+ accessory: 'none',
44
+ },
45
+ lime_frog: {
46
+ shape: 'rounded_head_two_ears',
47
+ eyes: 'todd',
48
+ eyewear: 'none',
49
+ accessory: 'bow',
50
+ },
51
+ coral_monocle: {
52
+ shape: 'twelve_scalloped_rosette',
53
+ eyes: 'double_highlights',
54
+ eyewear: 'monocle',
55
+ accessory: 'none',
56
+ },
57
+ gus: {
58
+ shape: 'rounded_diamond',
59
+ eyes: 'oval',
60
+ eyewear: 'classic_sunglasses',
61
+ accessory: 'orb',
62
+ },
63
+ blue_spectacles: {
64
+ shape: 'rounded_cube',
65
+ eyes: 'crescent_inset',
66
+ eyewear: 'tall_oval_frames',
67
+ accessory: 'three_lobe',
68
+ },
69
+ lime_headphones: {
70
+ shape: 'circle_two_ears',
71
+ eyes: 'swept_lids',
72
+ eyewear: 'none',
73
+ accessory: 'headphones',
74
+ },
75
+ });
76
+ export function characterRecipe(props) {
77
+ if (props.legacy && isLegacyRecipe(props))
78
+ return {
79
+ preset: 'legacy',
80
+ selections: {
81
+ shape: props.legacy.shape ?? 'circle',
82
+ eyes: props.legacy.eyes ?? 'oval',
83
+ ...props.legacy.selections,
84
+ },
85
+ ...props.legacy.patch,
86
+ ...(props.legacy.eyeSpacing !== undefined
87
+ ? { eyeSpacing: props.legacy.eyeSpacing }
88
+ : {}),
89
+ };
90
+ const recipe = props.config.character;
91
+ if (recipe?.preset !== 'clippo') return recipe;
92
+ return {
93
+ ...recipe,
94
+ selections: { shape: 'clippo', eyes: 'clippo', ...recipe.selections },
95
+ // A palette selection is an explicit replacement of the preset's grey.
96
+ ...(!recipe.bodyColor && !recipe.selections?.color
97
+ ? { bodyColor: '#999b9d' }
98
+ : {}),
99
+ };
100
+ }
101
+
102
+ export function authoredPartsFor(props) {
103
+ const recipe = characterRecipe(props);
104
+ const selections = recipe?.selections ?? {};
105
+ const customized = Boolean(
106
+ props.legacy || recipe?.bodyColor || Object.keys(selections).length,
107
+ );
108
+ const defaults = !isLegacyRecipe(props)
109
+ ? PRESET_DEFAULTS[recipe.preset]
110
+ : undefined;
111
+ const selectedShape = selections.shape ?? defaults?.shape;
112
+ const shape =
113
+ selectedShape === 'todd' ? 'rounded_head_two_ears' : selectedShape;
114
+ const eyes =
115
+ selections.eyes ??
116
+ defaults?.eyes ??
117
+ (customized && recipe?.preset === 'lime_frog' ? 'todd' : undefined);
118
+ const accessory =
119
+ selections.accessory ??
120
+ defaults?.accessory ??
121
+ (customized && recipe?.preset === 'blue_beret'
122
+ ? 'felipe_beret'
123
+ : undefined);
124
+ if (!customized) return {};
125
+ const bodyPreset =
126
+ !shape &&
127
+ !isLegacyRecipe(props) &&
128
+ ORIGINAL_PRESETS.includes(recipe?.preset)
129
+ ? recipe.preset
130
+ : undefined;
131
+ return {
132
+ ...(bodyPreset ? { bodyPreset } : {}),
133
+ ...(bodyPreset &&
134
+ (recipe.bodyColor || selections.color || bodyPreset !== recipe.preset)
135
+ ? { paintBody: true }
136
+ : {}),
137
+ ...(selections.shape === 'clippo' ? { shape: 'clippo' } : {}),
138
+ ...((!isLegacyRecipe(props) || !props.legacy?.points) &&
139
+ (MIGRATED_SHAPE_IDS.includes(shape) || NATIVE_PARTS.shape.includes(shape))
140
+ ? { faceShape: shape }
141
+ : {}),
142
+ ...(eyes ? { eyes } : {}),
143
+ ...((selections.eyewear ?? defaults?.eyewear)
144
+ ? { eyewear: selections.eyewear ?? defaults?.eyewear }
145
+ : {}),
146
+ ...(accessory ? { accessory } : {}),
147
+ };
148
+ }
149
+ export const ORIGINAL_PRESETS = Object.freeze([
150
+ 'blue_beret',
151
+ 'alfred',
152
+ 'purple_heart',
153
+ 'lime_frog',
154
+ 'coral_monocle',
155
+ 'gus',
156
+ 'blue_spectacles',
157
+ 'lime_headphones',
158
+ ]);
159
+ // Exact shapes reported by the original engine's eight named appearances.
160
+ const BODY_SIGNATURES = Object.freeze({
161
+ six_lobed_flower: 'blue_beret',
162
+ rounded_triangle: 'alfred',
163
+ heart: 'purple_heart',
164
+ rounded_head_two_ears: 'lime_frog',
165
+ twelve_scalloped_rosette: 'coral_monocle',
166
+ rounded_diamond: 'gus',
167
+ rounded_cube: 'blue_spectacles',
168
+ circle_two_ears: 'lime_headphones',
169
+ todd: 'lime_frog',
170
+ });
171
+ /** The body's authored reaction follows its geometry, independently of its pieces. */
172
+ export function bodySignatureFor(props) {
173
+ const recipe = characterRecipe(props),
174
+ parts = authoredPartsFor(props);
175
+ if (parts.bodyPreset) return parts.bodyPreset;
176
+ if (
177
+ !isLegacyRecipe(props) &&
178
+ !recipe.selections?.shape &&
179
+ ORIGINAL_PRESETS.includes(recipe.preset)
180
+ )
181
+ return recipe.preset;
182
+ // Distinct migrated contours and Clippo use Todd's original signature as the
183
+ // shared standard; they never fall back to the original Wave/maracas.
184
+ if (props.legacy?.points) return 'lime_frog';
185
+ return (
186
+ BODY_SIGNATURES[
187
+ recipe.selections?.shape ?? PRESET_DEFAULTS[recipe.preset]?.shape
188
+ ] ?? 'lime_frog'
189
+ );
190
+ }
191
+ /** Prepared authored bodies retain their actual defaults, not the fitting scene's catalog selections. */
192
+ export function bodySelectionDefaults(preset, appearance) {
193
+ return {
194
+ shape: appearance.shape,
195
+ eyes: preset === 'lime_frog' ? 'todd' : appearance.eyes,
196
+ eyewear: appearance.eyewear ?? 'none',
197
+ accessory:
198
+ preset === 'blue_beret'
199
+ ? 'felipe_beret'
200
+ : (appearance.accessories?.[0] ?? 'none'),
201
+ };
202
+ }
203
+ export const NATIVE_PARTS = Object.freeze({
204
+ shape: Object.freeze([
205
+ 'circle',
206
+ 'rounded_triangle',
207
+ 'capsule',
208
+ 'rounded_head_two_ears',
209
+ 'six_lobed_flower',
210
+ 'heart',
211
+ 'four_lobed_butterfly',
212
+ 'circle_two_ears',
213
+ 'twelve_scalloped_rosette',
214
+ 'rounded_cube',
215
+ 'rounded_diamond',
216
+ ]),
217
+ eyes: Object.freeze([
218
+ 'swept_lids',
219
+ 'oval',
220
+ 'sparkle_capsules',
221
+ 'highlight_capsules',
222
+ 'dots',
223
+ 'double_highlights',
224
+ 'round_inset',
225
+ 'crescent_inset',
226
+ 'sleepy_lids',
227
+ ]),
228
+ eyewear: Object.freeze([
229
+ 'none',
230
+ 'monocle',
231
+ 'tall_oval_frames',
232
+ 'separate_trapezoid_lenses',
233
+ 'classic_sunglasses',
234
+ 'round_sunglasses',
235
+ ]),
236
+ accessory: Object.freeze([
237
+ 'none',
238
+ 'headphones',
239
+ 'bow',
240
+ 'beanie',
241
+ 'hat',
242
+ 'beret',
243
+ 'orb',
244
+ 'three_lobe',
245
+ 'crown',
246
+ ]),
247
+ });