@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
@@ -1,15 +1,19 @@
1
- import { useContext, useMemo, useState } from 'react';
1
+ import { useContext, useMemo, useRef, useState } from 'react';
2
2
  import { Platform } from 'react-native';
3
3
  import { AgentAvatar, type AvatarConfig } from '../agent-avatar';
4
+ import {
5
+ CHARACTER_SHAPES,
6
+ NATIVE_CHARACTER_SHAPES,
7
+ createConfigForShape,
8
+ currentCharacterShape,
9
+ materializeCharacter,
10
+ } from '../agent-avatar/character-shapes';
4
11
  import type { AvatarCharacterCategory } from '../agent-avatar/config-character';
5
12
  import {
6
13
  CharacterRuntimeContext,
7
14
  CharacterCapabilitiesContext,
8
15
  } from '../agent-avatar/context';
9
- import {
10
- legacyCharacterRecipe,
11
- legacyNativeShape,
12
- } from '../agent-avatar/legacy-recipe';
16
+ import { legacyCharacterRecipe } from '../agent-avatar/legacy-recipe';
13
17
  import { Button } from '../button/Button';
14
18
  import { CloseButton } from '../button/CloseButton';
15
19
  import { useDirectionProps } from '../hooks/use-is-rtl';
@@ -56,58 +60,86 @@ export function AgentCreatorBase(props: AgentCreatorProps) {
56
60
  );
57
61
  const { runtimeUrl } = runtime;
58
62
  const beta = Boolean(c.character && c.character.preset !== 'bloom');
59
- const nativeShape = legacyNativeShape(c);
60
63
  const key = JSON.stringify(beta ? c.character : legacyCharacterRecipe(c));
61
- const capabilities =
64
+ const currentCapabilities =
62
65
  catalog.capabilitiesByKey?.get(key) ??
63
66
  (catalog.capabilities?.key === key ? catalog.capabilities : undefined);
67
+ const retained = useRef<{ owner: string; selected: Record<string, string> }>({
68
+ owner: '',
69
+ selected: {},
70
+ });
71
+ const owner = JSON.stringify([agent.id, c.character?.preset ?? 'bloom']);
72
+ if (retained.current.owner !== owner)
73
+ retained.current = { owner, selected: {} };
74
+ if (currentCapabilities)
75
+ retained.current.selected = currentCapabilities.selected;
76
+ const capabilities = currentCapabilities ?? {
77
+ selected: retained.current.selected,
78
+ available: {},
79
+ };
64
80
  const betaColor =
65
- capabilities?.selected.color ?? c.character?.selections?.color;
81
+ c.character?.selections?.color ?? capabilities?.selected.color;
82
+ const betaBodyColor =
83
+ c.character?.bodyColor ??
84
+ (c.character?.preset === 'clippo' && !c.character.selections?.color
85
+ ? '#999b9d'
86
+ : undefined) ??
87
+ (!betaColor ? capabilities?.selected.bodyColor : undefined);
66
88
  const customColorValue = beta
67
- ? (c.character?.bodyColor ??
89
+ ? (betaBodyColor ??
90
+ (c.character?.selections?.color
91
+ ? CHARACTER_COLORS[
92
+ c.character.selections.color as keyof typeof CHARACTER_COLORS
93
+ ]
94
+ : undefined) ??
95
+ capabilities?.selected.bodyColor ??
68
96
  CHARACTER_COLORS[betaColor as keyof typeof CHARACTER_COLORS] ??
69
97
  avatarHex(c))
70
- : (c.character?.bodyColor ?? avatarHex(c));
98
+ : (c.character?.bodyColor ??
99
+ CHARACTER_COLORS[
100
+ c.character?.selections?.color as keyof typeof CHARACTER_COLORS
101
+ ] ??
102
+ avatarHex(c));
71
103
  const selectedEyes =
72
- capabilities?.selected.eyes ?? c.character?.selections?.eyes ?? 'oval';
104
+ c.character?.selections?.eyes ??
105
+ capabilities?.selected.eyes ??
106
+ (c.character?.preset === 'clippo'
107
+ ? 'clippo'
108
+ : c.character?.preset === 'lime_frog'
109
+ ? 'todd'
110
+ : 'oval');
111
+ const selectedEyewear =
112
+ c.character?.selections?.eyewear ?? capabilities?.selected.eyewear;
113
+ const selectedAccessory =
114
+ c.character?.selections?.accessory ?? capabilities?.selected.accessory;
115
+ const selectedParts = {
116
+ ...(c.character?.selections?.eyes ||
117
+ capabilities.selected.eyes ||
118
+ c.character?.preset === 'clippo' ||
119
+ c.character?.preset === 'lime_frog'
120
+ ? { eyes: selectedEyes }
121
+ : {}),
122
+ ...(selectedEyewear ? { eyewear: selectedEyewear } : {}),
123
+ ...(selectedAccessory ? { accessory: selectedAccessory } : {}),
124
+ };
73
125
  const characterConfig = (
74
126
  category: AvatarCharacterCategory,
75
127
  id: string,
76
128
  ): AvatarConfig => {
77
- if (
78
- !beta &&
79
- (category === 'eyes' ||
80
- category === 'eyewear' ||
81
- category === 'accessory')
82
- )
83
- return {
84
- ...c,
85
- character: {
86
- ...c.character,
87
- preset: 'bloom',
88
- selections: { ...c.character?.selections, [category]: id },
89
- },
90
- };
91
- const character = beta
92
- ? c.character!
93
- : {
94
- preset: 'blue_beret',
95
- bodyColor: customColorValue,
96
- selections: {
97
- eyewear:
98
- c.character?.selections?.eyewear ??
99
- capabilities?.selected.eyewear ??
100
- 'none',
101
- accessory:
102
- c.character?.selections?.accessory ??
103
- capabilities?.selected.accessory ??
104
- 'none',
105
- eyes: selectedEyes,
106
- },
107
- };
108
- const selections = { ...character.selections, [category]: id };
109
- if (category === 'shape') delete selections.eyes;
110
- const next = { ...character, selections };
129
+ if (category === 'shape')
130
+ return createConfigForShape(c, id, capabilities.selected);
131
+ const character = materializeCharacter(c, capabilities.selected);
132
+ // Choosing a body retains the edited face and the preset's effective parts.
133
+ // Presets supply defaults; their catalog restrictions do not own edits.
134
+ const selections = {
135
+ ...selectedParts,
136
+ ...character.selections,
137
+ [category]: id,
138
+ };
139
+ const next = {
140
+ ...character,
141
+ selections,
142
+ };
111
143
  if (category === 'color') delete next.bodyColor;
112
144
  return { ...c, character: next };
113
145
  };
@@ -117,103 +149,37 @@ export function AgentCreatorBase(props: AgentCreatorProps) {
117
149
  ? runtimeUrl.slice(0, runtimeUrl.lastIndexOf('/') + 1)
118
150
  : '';
119
151
  const eyeBackground = customColorValue;
120
- const migratedShapes = [
121
- ...(['slender', 'pocket', 'petal', 'star', 'cloud', 'shield'] as const).map(
122
- (shape) => ({
123
- family: 'fold' as const,
124
- shape,
125
- title: messages.shapes[shape],
126
- }),
127
- ),
128
- ...(
129
- [
130
- ['pebble', 'Pebble'],
131
- ['squircle', 'Squircle'],
132
- ] as const
133
- ).map(([shape, title]) => ({
134
- family: 'blob' as const,
135
- shape,
136
- title: messages.characterOption('shape', shape, title),
137
- })),
138
- ];
139
- const migratedConfig = (
140
- family: 'fold' | 'blob',
141
- shape: string,
142
- ): AvatarConfig => {
143
- const { character: _character, ...existing } = c;
144
- const color = hexAppearance(customColorValue);
145
- return {
146
- ...existing,
147
- ...color,
148
- lightEyes: beta ? false : c.lightEyes,
149
- family,
150
- ...(family === 'fold'
151
- ? { foldShape: shape as AvatarConfig['foldShape'] }
152
- : { shape: shape as AvatarConfig['shape'] }),
153
- ...(runtimeUrl
154
- ? {
155
- character: {
156
- preset: 'bloom',
157
- selections: {
158
- eyes: selectedEyes,
159
- ...((capabilities?.selected.eyewear ??
160
- c.character?.selections?.eyewear)
161
- ? {
162
- eyewear:
163
- capabilities?.selected.eyewear ??
164
- c.character?.selections?.eyewear,
165
- }
166
- : {}),
167
- ...((capabilities?.selected.accessory ??
168
- c.character?.selections?.accessory)
169
- ? {
170
- accessory:
171
- capabilities?.selected.accessory ??
172
- c.character?.selections?.accessory,
173
- }
174
- : {}),
175
- },
176
- },
177
- }
178
- : {}),
179
- };
180
- };
181
152
  const shapeChoices = runtimeUrl
182
- ? [
183
- ...CHARACTER_OPTIONS.shape.map(([id, title]) => ({
184
- id,
185
- label: messages.characterOption('shape', id, title),
186
- config: characterConfig('shape', id),
187
- thumbnail: `${thumbnailBase}thumbnails/shapes/${id}.png`,
188
- })),
189
- ...migratedShapes.map(({ family, shape, title }) => ({
190
- id: `legacy:${family}:${shape}`,
191
- label: title,
192
- config: migratedConfig(family, shape),
193
- })),
194
- ]
153
+ ? CHARACTER_SHAPES.map(([id, title]) => ({
154
+ id,
155
+ label:
156
+ id in messages.shapes
157
+ ? messages.shapes[id as keyof typeof messages.shapes]
158
+ : messages.characterOption('shape', id, title),
159
+ config: createConfigForShape(c, id, capabilities.selected),
160
+ ...(NATIVE_CHARACTER_SHAPES.some(([shape]) => shape === id) &&
161
+ id !== 'clippo' &&
162
+ id !== 'todd'
163
+ ? { thumbnail: `${thumbnailBase}thumbnails/shapes/${id}.png` }
164
+ : {}),
165
+ }))
195
166
  : undefined;
196
- const shapeValue = beta
197
- ? (capabilities?.selected.shape ?? c.character?.selections?.shape)
198
- : (nativeShape ??
199
- `legacy:${c.family}:${c.family === 'fold' ? c.foldShape : c.shape}`);
200
- const selectShape = (id: string) => {
201
- if (id.startsWith('legacy:')) {
202
- const [, family, shape] = id.split(':');
203
- onChange({
204
- ...agent,
205
- avatar: migratedConfig(family as 'fold' | 'blob', shape!),
206
- });
207
- } else selectCharacter('shape', id);
208
- };
167
+ const shapeValue =
168
+ c.character?.selections?.shape ??
169
+ (beta
170
+ ? (capabilities.selected.shape ??
171
+ (c.character?.preset === 'clippo' ? 'clippo' : undefined))
172
+ : currentCharacterShape(c));
173
+ const selectShape = (id: string) => selectCharacter('shape', id);
209
174
  const eyeChoices = runtimeUrl
210
175
  ? CHARACTER_OPTIONS.eyes.map(([id, title]) => ({
211
176
  id,
212
177
  label: messages.characterOption('eyes', id, title),
213
178
  config: characterConfig('eyes', id),
214
- thumbnail: `${thumbnailBase}thumbnails/eyes/${id}.png`,
215
- disabled:
216
- !capabilities || capabilities.available[`eyes:${id}`] === false,
179
+ ...(id === 'clippo' || id === 'cyclops'
180
+ ? { artwork: id }
181
+ : { thumbnail: `${thumbnailBase}thumbnails/eyes/${id}.png` }),
182
+ disabled: false,
217
183
  }))
218
184
  : undefined;
219
185
  const appearance = (patch: Partial<AvatarConfig>) => {
@@ -367,8 +333,15 @@ export function AgentCreatorBase(props: AgentCreatorProps) {
367
333
  </StyledView>
368
334
  )}
369
335
  <CharacterControls
370
- character={c.character ?? { preset: 'bloom' }}
336
+ character={
337
+ runtimeUrl
338
+ ? beta
339
+ ? c.character
340
+ : materializeCharacter(c, capabilities.selected)
341
+ : c.character
342
+ }
371
343
  capabilitiesKey={key}
344
+ selected={capabilities.selected}
372
345
  onChange={(character) => {
373
346
  if (character)
374
347
  onChange({ ...agent, avatar: { ...c, character } });
@@ -386,7 +359,7 @@ export function AgentCreatorBase(props: AgentCreatorProps) {
386
359
  boxShadow: '0 1px 2px rgba(0,0,0,.05)',
387
360
  }}
388
361
  >
389
- {!beta &&
362
+ {!runtimeUrl &&
390
363
  AVATAR_COLORS.map(([hue, saturation, name, center, edge]) => {
391
364
  const active =
392
365
  c.hue === hue &&
@@ -415,15 +388,11 @@ export function AgentCreatorBase(props: AgentCreatorProps) {
415
388
  </StyledPressable>
416
389
  );
417
390
  })}
418
- {beta &&
391
+ {runtimeUrl &&
419
392
  CHARACTER_OPTIONS.color.map(([id, title]) => {
420
- const active =
421
- !c.character?.bodyColor &&
422
- capabilities?.selected.color === id;
393
+ const active = !betaBodyColor && betaColor === id;
423
394
  const hex = CHARACTER_COLORS[id];
424
- const disabled =
425
- !capabilities ||
426
- capabilities.available[`color:${id}`] === false;
395
+ const disabled = false;
427
396
  return (
428
397
  <StyledPressable
429
398
  key={id}
@@ -446,18 +415,18 @@ export function AgentCreatorBase(props: AgentCreatorProps) {
446
415
  <PopoverTrigger asChild label={messages.customColor}>
447
416
  <StyledPressable
448
417
  aria-pressed={
449
- beta ? Boolean(c.character?.bodyColor) : undefined
418
+ runtimeUrl ? Boolean(betaBodyColor) : undefined
450
419
  }
451
420
  accessibilityState={{
452
- selected: beta && Boolean(c.character?.bodyColor),
421
+ selected: Boolean(runtimeUrl) && Boolean(betaBodyColor),
453
422
  }}
454
423
  className="relative size-[26px] shrink-0 overflow-hidden rounded-full"
455
424
  >
456
425
  <GlossArt
457
- rainbow={!beta || !c.character?.bodyColor}
458
- center={beta ? c.character?.bodyColor : undefined}
459
- edge={beta ? c.character?.bodyColor : undefined}
460
- active={beta && Boolean(c.character?.bodyColor)}
426
+ rainbow={!runtimeUrl || !betaBodyColor}
427
+ center={runtimeUrl ? betaBodyColor : undefined}
428
+ edge={runtimeUrl ? betaBodyColor : undefined}
429
+ active={Boolean(runtimeUrl) && Boolean(betaBodyColor)}
461
430
  />
462
431
  </StyledPressable>
463
432
  </PopoverTrigger>
@@ -473,14 +442,14 @@ export function AgentCreatorBase(props: AgentCreatorProps) {
473
442
  onChange={(hex) => {
474
443
  const next = hexAppearance(hex);
475
444
  if (!next) return;
476
- if (beta)
445
+ if (runtimeUrl)
477
446
  onChange({
478
447
  ...agent,
479
448
  avatar: {
480
449
  ...c,
481
450
  ...next,
482
451
  character: {
483
- ...c.character!,
452
+ ...materializeCharacter(c, capabilities.selected),
484
453
  bodyColor: hex.toLowerCase(),
485
454
  },
486
455
  },
@@ -1,4 +1,4 @@
1
- import { useContext } from 'react';
1
+ import { useContext, useRef } from 'react';
2
2
  import {
3
3
  CharacterRuntimeContext,
4
4
  CharacterCapabilitiesContext,
@@ -16,10 +16,12 @@ import { useAgentCreatorBindings, useAgentCreatorMessages } from './context';
16
16
  export function CharacterControls({
17
17
  character,
18
18
  capabilitiesKey,
19
+ selected,
19
20
  onChange,
20
21
  }: {
21
22
  character?: AvatarCharacterConfig;
22
23
  capabilitiesKey?: string;
24
+ selected?: Readonly<Record<string, string>>;
23
25
  onChange: (character: AvatarCharacterConfig | undefined) => void;
24
26
  }) {
25
27
  const messages = useAgentCreatorMessages();
@@ -36,11 +38,21 @@ export function CharacterControls({
36
38
  SelectItem,
37
39
  SelectItemText,
38
40
  } = useAgentCreatorBindings();
39
- if (!runtimeUrl) return null;
40
41
  const key = capabilitiesKey ?? JSON.stringify(character);
41
- const resolved =
42
+ const current =
42
43
  capabilitiesByKey?.get(key) ??
43
44
  (character && capabilities?.key === key ? capabilities : undefined);
45
+ const retained = useRef<{
46
+ preset?: string;
47
+ selected: Readonly<Record<string, string>>;
48
+ }>({ selected: {} });
49
+ if (retained.current.preset !== character?.preset)
50
+ retained.current = { preset: character?.preset, selected: {} };
51
+ if (current) retained.current.selected = current.selected;
52
+ const resolved = {
53
+ selected: selected ?? current?.selected ?? retained.current.selected,
54
+ };
55
+ if (!runtimeUrl) return null;
44
56
  const row = (
45
57
  label: string,
46
58
  value: string,
@@ -90,11 +102,14 @@ export function CharacterControls({
90
102
  if (character && !presets.some((item) => item.id === character.preset))
91
103
  presets.push({
92
104
  id: character.preset,
93
- title: messages.characterOption(
94
- 'preset',
95
- character.preset,
96
- character.preset,
97
- ),
105
+ title:
106
+ character.preset === 'legacy'
107
+ ? messages.proceduralAvatar
108
+ : messages.characterOption(
109
+ 'preset',
110
+ character.preset,
111
+ character.preset,
112
+ ),
98
113
  });
99
114
  const labels: Record<AvatarCharacterCategory, string> = {
100
115
  shape: messages.shape,
@@ -120,8 +135,8 @@ export function CharacterControls({
120
135
  (category) =>
121
136
  row(
122
137
  labels[category],
123
- resolved?.selected[category] ??
124
- character.selections?.[category] ??
138
+ character.selections?.[category] ??
139
+ resolved?.selected[category] ??
125
140
  'none',
126
141
  (category === 'accessory'
127
142
  ? [
@@ -132,14 +147,24 @@ export function CharacterControls({
132
147
  ).map(([id, title]) => ({
133
148
  id,
134
149
  title: messages.characterOption(category, id, title),
135
- disabled:
136
- !resolved ||
137
- resolved.available[`${category}:${id}`] === false,
150
+ disabled: false,
138
151
  })),
139
152
  (id) => {
140
153
  onChange({
141
154
  ...character,
142
- selections: { ...character.selections, [category]: id },
155
+ selections: {
156
+ ...(['eyes', 'eyewear', 'accessory'] as const).reduce(
157
+ (parts, part) => ({
158
+ ...parts,
159
+ ...(resolved?.selected[part]
160
+ ? { [part]: resolved.selected[part] }
161
+ : {}),
162
+ }),
163
+ {},
164
+ ),
165
+ ...character.selections,
166
+ [category]: id,
167
+ },
143
168
  });
144
169
  },
145
170
  labels[category],
@@ -0,0 +1,49 @@
1
+ import Svg, { Circle, Path } from 'react-native-svg';
2
+ import { CHARACTER_COLORS } from './constants';
3
+
4
+ /** Selector artwork only. The avatar itself uses Clippo's actual 3D meshes. */
5
+ export function ClippoArtwork({
6
+ kind,
7
+ opacity = 1,
8
+ }: {
9
+ kind: 'shape' | 'eyes';
10
+ opacity?: number;
11
+ }) {
12
+ const eyes = kind === 'eyes';
13
+ return (
14
+ <Svg
15
+ width={eyes ? 27 : 56}
16
+ height={eyes ? 27 : 56}
17
+ viewBox={eyes ? '0 0 104 100' : '0 -4 90 170'}
18
+ preserveAspectRatio="xMidYMid meet"
19
+ opacity={opacity}
20
+ accessible={false}
21
+ pointerEvents="none"
22
+ >
23
+ {eyes ? (
24
+ <>
25
+ <Circle cx={27} cy={44} r={21} fill="#f4f3ee" />
26
+ <Circle cx={77} cy={58} r={21} fill="#f4f3ee" />
27
+ <Circle cx={27} cy={44} r={15} fill="#3c2b2d" />
28
+ <Circle cx={77} cy={58} r={15} fill="#3c2b2d" />
29
+ <Path
30
+ d="M11 8 C18 5 29 5 38 9 M63 21 C72 21 82 26 88 32"
31
+ fill="none"
32
+ stroke="#3c2b2d"
33
+ strokeWidth={12}
34
+ strokeLinecap="round"
35
+ />
36
+ </>
37
+ ) : (
38
+ <Path
39
+ d="M25.4 79 C23 102 24 115 35 120 C46 125 53 119 52 105 C48 84 46 63 56 40 C67 16 63 6 46 3 C27 -1 20 15 14 30 C5 51 2 70 5 91 C8 128 19 151 40 157 C57 162 72 153 75 134 C80 114 69 107 72 93 C73 83 76 75 80 71"
40
+ fill="none"
41
+ stroke={CHARACTER_COLORS.pink}
42
+ strokeWidth={8.5}
43
+ strokeLinecap="round"
44
+ strokeLinejoin="round"
45
+ />
46
+ )}
47
+ </Svg>
48
+ );
49
+ }
@@ -0,0 +1,27 @@
1
+ import Svg, { Circle, Ellipse, Path } from 'react-native-svg';
2
+
3
+ /** Static selector artwork; the runtime renders the fitted single-eye mesh. */
4
+ export function CyclopsArtwork({ opacity = 1 }: { opacity?: number }) {
5
+ return (
6
+ <Svg
7
+ width={27}
8
+ height={27}
9
+ viewBox="0 0 100 100"
10
+ opacity={opacity}
11
+ accessible={false}
12
+ pointerEvents="none"
13
+ >
14
+ <Ellipse cx={50} cy={50} rx={46} ry={44} fill="#f4f5ee" />
15
+ <Circle
16
+ cx={50}
17
+ cy={50}
18
+ r={24}
19
+ fill="#148a78"
20
+ stroke="#174e45"
21
+ strokeWidth={3}
22
+ />
23
+ <Circle cx={50} cy={50} r={14} fill="#101918" />
24
+ <Path d="M61 31 L71 38 L65 46 L57 40 Z" fill="#ffffff" />
25
+ </Svg>
26
+ );
27
+ }
@@ -12,13 +12,26 @@ import Animated, {
12
12
  } from 'react-native-reanimated';
13
13
  import { EYES, type AvatarConfig } from '../agent-avatar';
14
14
  import { AgentFace } from '../agent-avatar/AgentFace';
15
- import { StyledImage, StyledPressable, StyledView } from '../styles/styled-primitives';
15
+ import { ClippoArtwork } from './ClippoArtwork';
16
+ import { CyclopsArtwork } from './CyclopsArtwork';
17
+ import {
18
+ StyledImage,
19
+ StyledPressable,
20
+ StyledView,
21
+ } from '../styles/styled-primitives';
16
22
  import { useTheme } from '../theme/use-theme';
17
23
  import { useAgentCreatorMessages } from './context';
18
24
  import { useTrackEvents } from './use-track-events';
19
25
 
20
26
  const AnimatedView = Animated.createAnimatedComponent(StyledView);
21
- export type EmotionChoice = { id: string; label: string; config: AvatarConfig; disabled?: boolean; thumbnail?: string };
27
+ export type EmotionChoice = {
28
+ id: string;
29
+ label: string;
30
+ config: AvatarConfig;
31
+ disabled?: boolean;
32
+ thumbnail?: string;
33
+ artwork?: 'clippo' | 'cyclops';
34
+ };
22
35
  function Emotion({
23
36
  config,
24
37
  choice,
@@ -71,21 +84,40 @@ function Emotion({
71
84
  pointerEvents="auto"
72
85
  className={`pointer-events-auto flex size-[34px] cursor-pointer items-center justify-center overflow-hidden rounded-full border-2 outline-none transition-[border-color,box-shadow] hover:ring-2 hover:ring-border-button-hover focus-visible:ring-2 focus-visible:ring-border-focus-ring ${active ? 'border-foreground-icon-secondary' : 'border-transparent'}`}
73
86
  style={{
74
- backgroundColor: backgroundColor ?? `hsl(${config.hue}, ${config.saturation}%, ${config.lightness ?? 80}%)`,
87
+ backgroundColor:
88
+ backgroundColor ??
89
+ `hsl(${config.hue}, ${config.saturation}%, ${config.lightness ?? 80}%)`,
75
90
  borderColor: active ? colors.icon : 'transparent',
76
91
  }}
77
92
  >
78
- {choice.thumbnail ? <StyledImage source={{ uri: choice.thumbnail }} resizeMode="contain" accessible={false} style={{ width: 27, height: 27, opacity: choice.disabled ? 0.35 : 1 }} /> : <AgentFace
79
- config={{
80
- ...choice.config,
81
- eyeSize: 24,
82
- eyeGap: 36,
83
- lookAt: 'center',
84
- motion: 0,
85
- idle: false,
86
- }}
87
- size={34}
88
- />}
93
+ {choice.artwork === 'cyclops' ? (
94
+ <CyclopsArtwork opacity={choice.disabled ? 0.35 : 1} />
95
+ ) : choice.artwork === 'clippo' ? (
96
+ <ClippoArtwork kind="eyes" opacity={choice.disabled ? 0.35 : 1} />
97
+ ) : choice.thumbnail ? (
98
+ <StyledImage
99
+ source={{ uri: choice.thumbnail }}
100
+ resizeMode="contain"
101
+ accessible={false}
102
+ style={{
103
+ width: 27,
104
+ height: 27,
105
+ opacity: choice.disabled ? 0.35 : 1,
106
+ }}
107
+ />
108
+ ) : (
109
+ <AgentFace
110
+ config={{
111
+ ...choice.config,
112
+ eyeSize: 24,
113
+ eyeGap: 36,
114
+ lookAt: 'center',
115
+ motion: 0,
116
+ idle: false,
117
+ }}
118
+ size={34}
119
+ />
120
+ )}
89
121
  </StyledPressable>
90
122
  </AnimatedView>
91
123
  </StyledView>
@@ -109,9 +141,16 @@ export function EmotionPicker({
109
141
  onChange: (eyes: AvatarConfig['eyes']) => void;
110
142
  }) {
111
143
  const messages = useAgentCreatorMessages();
112
- const choices = choicesProp ?? EYES.map((eyes) => ({ id: eyes, label: messages.emotions[eyes], config: { ...config, eyes } }));
144
+ const choices =
145
+ choicesProp ??
146
+ EYES.map((eyes) => ({
147
+ id: eyes,
148
+ label: messages.emotions[eyes],
149
+ config: { ...config, eyes },
150
+ }));
113
151
  const value = valueProp ?? config.eyes;
114
- const emit = (id: string) => onSelect ? onSelect(id) : onChange(id as AvatarConfig['eyes']);
152
+ const emit = (id: string) =>
153
+ onSelect ? onSelect(id) : onChange(id as AvatarConfig['eyes']);
115
154
  const ref = useRef<View>(null);
116
155
  const visible = useSharedValue(true);
117
156
  const target = useSharedValue(0);
@@ -1,9 +1,12 @@
1
+ import { characterShapeGeometry } from '../agent-avatar/character-shapes';
1
2
  import { memo } from 'react';
2
3
  import Svg, { Path } from 'react-native-svg';
3
4
  import { legacyContours } from '../agent-avatar/legacy-contours';
4
5
  import { radialContour } from '../agent-avatar/legacy-recipe';
5
6
  import type { AvatarConfig } from '../agent-avatar/model';
6
7
  import { CHARACTER_COLORS } from './constants';
8
+ import { ClippoArtwork } from './ClippoArtwork';
9
+ import { ToddArtwork } from './ToddArtwork';
7
10
 
8
11
  const silhouettes = new Map<string, { path: string; viewBox: string }>();
9
12
 
@@ -49,7 +52,19 @@ export const ShapeSilhouette = memo(function ShapeSilhouette({
49
52
  }: {
50
53
  config: AvatarConfig;
51
54
  }) {
52
- const { path, viewBox } = silhouette(config);
55
+ if (
56
+ config.character?.selections?.shape === 'todd' ||
57
+ (config.character?.preset === 'lime_frog' &&
58
+ !config.character.selections?.shape)
59
+ )
60
+ return <ToddArtwork />;
61
+ if (
62
+ config.character?.selections?.shape === 'clippo' ||
63
+ (config.character?.preset === 'clippo' &&
64
+ !config.character.selections?.shape)
65
+ )
66
+ return <ClippoArtwork kind="shape" />;
67
+ const { path, viewBox } = silhouette(characterShapeGeometry(config));
53
68
  return (
54
69
  <Svg
55
70
  width={56}