@oxy.so/bloom 7.1.2 → 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 (236) 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/agent-avatar.mdx +45 -8
  26. package/docs/agent-creator.mdx +13 -3
  27. package/lib/commonjs/agent-avatar/AgentAvatar.js +1 -1
  28. package/lib/commonjs/agent-avatar/AgentAvatar.js.map +1 -1
  29. package/lib/commonjs/agent-avatar/CharacterAvatar.js +2 -1
  30. package/lib/commonjs/agent-avatar/CharacterAvatar.js.map +1 -1
  31. package/lib/commonjs/agent-avatar/character-colors.js +19 -0
  32. package/lib/commonjs/agent-avatar/character-colors.js.map +1 -0
  33. package/lib/commonjs/agent-avatar/character-shapes.js +106 -0
  34. package/lib/commonjs/agent-avatar/character-shapes.js.map +1 -0
  35. package/lib/commonjs/agent-avatar/config-character.js +4 -0
  36. package/lib/commonjs/agent-avatar/config-character.js.map +1 -1
  37. package/lib/commonjs/agent-avatar/legacy-recipe.js +19 -6
  38. package/lib/commonjs/agent-avatar/legacy-recipe.js.map +1 -1
  39. package/lib/commonjs/agent-creator/AgentCreatorBase.js +67 -107
  40. package/lib/commonjs/agent-creator/AgentCreatorBase.js.map +1 -1
  41. package/lib/commonjs/agent-creator/CharacterControls.js +23 -5
  42. package/lib/commonjs/agent-creator/CharacterControls.js.map +1 -1
  43. package/lib/commonjs/agent-creator/ClippoArtwork.js +63 -0
  44. package/lib/commonjs/agent-creator/ClippoArtwork.js.map +1 -0
  45. package/lib/commonjs/agent-creator/CyclopsArtwork.js +45 -0
  46. package/lib/commonjs/agent-creator/CyclopsArtwork.js.map +1 -0
  47. package/lib/commonjs/agent-creator/EmotionPicker.js +8 -1
  48. package/lib/commonjs/agent-creator/EmotionPicker.js.map +1 -1
  49. package/lib/commonjs/agent-creator/ShapeSilhouette.js +8 -1
  50. package/lib/commonjs/agent-creator/ShapeSilhouette.js.map +1 -1
  51. package/lib/commonjs/agent-creator/ToddArtwork.js +29 -0
  52. package/lib/commonjs/agent-creator/ToddArtwork.js.map +1 -0
  53. package/lib/commonjs/agent-creator/constants.js +16 -17
  54. package/lib/commonjs/agent-creator/constants.js.map +1 -1
  55. package/lib/commonjs/agent-creator/messages.js +1 -1
  56. package/lib/commonjs/agent-creator/messages.js.map +1 -1
  57. package/lib/commonjs/locale/translations/ar.js +1 -1
  58. package/lib/commonjs/locale/translations/ar.js.map +1 -1
  59. package/lib/commonjs/locale/translations/bn.js +1 -1
  60. package/lib/commonjs/locale/translations/bn.js.map +1 -1
  61. package/lib/commonjs/locale/translations/ca.js +1 -1
  62. package/lib/commonjs/locale/translations/ca.js.map +1 -1
  63. package/lib/commonjs/locale/translations/de.js +1 -1
  64. package/lib/commonjs/locale/translations/de.js.map +1 -1
  65. package/lib/commonjs/locale/translations/es.js +1 -1
  66. package/lib/commonjs/locale/translations/es.js.map +1 -1
  67. package/lib/commonjs/locale/translations/fr.js +1 -1
  68. package/lib/commonjs/locale/translations/fr.js.map +1 -1
  69. package/lib/commonjs/locale/translations/hi.js +1 -1
  70. package/lib/commonjs/locale/translations/hi.js.map +1 -1
  71. package/lib/commonjs/locale/translations/id.js +1 -1
  72. package/lib/commonjs/locale/translations/id.js.map +1 -1
  73. package/lib/commonjs/locale/translations/it.js +1 -1
  74. package/lib/commonjs/locale/translations/it.js.map +1 -1
  75. package/lib/commonjs/locale/translations/ja.js +1 -1
  76. package/lib/commonjs/locale/translations/ja.js.map +1 -1
  77. package/lib/commonjs/locale/translations/pt.js +1 -1
  78. package/lib/commonjs/locale/translations/pt.js.map +1 -1
  79. package/lib/commonjs/locale/translations/ru.js +1 -1
  80. package/lib/commonjs/locale/translations/ru.js.map +1 -1
  81. package/lib/commonjs/locale/translations/tr.js +1 -1
  82. package/lib/commonjs/locale/translations/tr.js.map +1 -1
  83. package/lib/commonjs/locale/translations/zh.js +1 -1
  84. package/lib/commonjs/locale/translations/zh.js.map +1 -1
  85. package/lib/module/agent-avatar/AgentAvatar.js +1 -1
  86. package/lib/module/agent-avatar/AgentAvatar.js.map +1 -1
  87. package/lib/module/agent-avatar/CharacterAvatar.js +2 -1
  88. package/lib/module/agent-avatar/CharacterAvatar.js.map +1 -1
  89. package/lib/module/agent-avatar/character-colors.js +15 -0
  90. package/lib/module/agent-avatar/character-colors.js.map +1 -0
  91. package/lib/module/agent-avatar/character-shapes.js +97 -0
  92. package/lib/module/agent-avatar/character-shapes.js.map +1 -0
  93. package/lib/module/agent-avatar/config-character.js +4 -0
  94. package/lib/module/agent-avatar/config-character.js.map +1 -1
  95. package/lib/module/agent-avatar/legacy-recipe.js +19 -6
  96. package/lib/module/agent-avatar/legacy-recipe.js.map +1 -1
  97. package/lib/module/agent-creator/AgentCreatorBase.js +69 -109
  98. package/lib/module/agent-creator/AgentCreatorBase.js.map +1 -1
  99. package/lib/module/agent-creator/CharacterControls.js +24 -6
  100. package/lib/module/agent-creator/CharacterControls.js.map +1 -1
  101. package/lib/module/agent-creator/ClippoArtwork.js +59 -0
  102. package/lib/module/agent-creator/ClippoArtwork.js.map +1 -0
  103. package/lib/module/agent-creator/CyclopsArtwork.js +41 -0
  104. package/lib/module/agent-creator/CyclopsArtwork.js.map +1 -0
  105. package/lib/module/agent-creator/EmotionPicker.js +8 -1
  106. package/lib/module/agent-creator/EmotionPicker.js.map +1 -1
  107. package/lib/module/agent-creator/ShapeSilhouette.js +8 -1
  108. package/lib/module/agent-creator/ShapeSilhouette.js.map +1 -1
  109. package/lib/module/agent-creator/ToddArtwork.js +25 -0
  110. package/lib/module/agent-creator/ToddArtwork.js.map +1 -0
  111. package/lib/module/agent-creator/constants.js +8 -16
  112. package/lib/module/agent-creator/constants.js.map +1 -1
  113. package/lib/module/agent-creator/messages.js +1 -1
  114. package/lib/module/agent-creator/messages.js.map +1 -1
  115. package/lib/module/locale/translations/ar.js +1 -1
  116. package/lib/module/locale/translations/ar.js.map +1 -1
  117. package/lib/module/locale/translations/bn.js +1 -1
  118. package/lib/module/locale/translations/bn.js.map +1 -1
  119. package/lib/module/locale/translations/ca.js +1 -1
  120. package/lib/module/locale/translations/ca.js.map +1 -1
  121. package/lib/module/locale/translations/de.js +1 -1
  122. package/lib/module/locale/translations/de.js.map +1 -1
  123. package/lib/module/locale/translations/es.js +1 -1
  124. package/lib/module/locale/translations/es.js.map +1 -1
  125. package/lib/module/locale/translations/fr.js +1 -1
  126. package/lib/module/locale/translations/fr.js.map +1 -1
  127. package/lib/module/locale/translations/hi.js +1 -1
  128. package/lib/module/locale/translations/hi.js.map +1 -1
  129. package/lib/module/locale/translations/id.js +1 -1
  130. package/lib/module/locale/translations/id.js.map +1 -1
  131. package/lib/module/locale/translations/it.js +1 -1
  132. package/lib/module/locale/translations/it.js.map +1 -1
  133. package/lib/module/locale/translations/ja.js +1 -1
  134. package/lib/module/locale/translations/ja.js.map +1 -1
  135. package/lib/module/locale/translations/pt.js +1 -1
  136. package/lib/module/locale/translations/pt.js.map +1 -1
  137. package/lib/module/locale/translations/ru.js +1 -1
  138. package/lib/module/locale/translations/ru.js.map +1 -1
  139. package/lib/module/locale/translations/tr.js +1 -1
  140. package/lib/module/locale/translations/tr.js.map +1 -1
  141. package/lib/module/locale/translations/zh.js +1 -1
  142. package/lib/module/locale/translations/zh.js.map +1 -1
  143. package/lib/typescript/commonjs/agent-avatar/CharacterAvatar.d.ts.map +1 -1
  144. package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts +13 -0
  145. package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts.map +1 -0
  146. package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts +19 -0
  147. package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts.map +1 -0
  148. package/lib/typescript/commonjs/agent-avatar/config-character.d.ts +4 -0
  149. package/lib/typescript/commonjs/agent-avatar/config-character.d.ts.map +1 -1
  150. package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts +2 -10
  151. package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts.map +1 -1
  152. package/lib/typescript/commonjs/agent-creator/AgentCreatorBase.d.ts.map +1 -1
  153. package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts +2 -1
  154. package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts.map +1 -1
  155. package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts +6 -0
  156. package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts.map +1 -0
  157. package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts +5 -0
  158. package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts.map +1 -0
  159. package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts +1 -0
  160. package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts.map +1 -1
  161. package/lib/typescript/commonjs/agent-creator/ShapeSilhouette.d.ts.map +1 -1
  162. package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts +6 -0
  163. package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts.map +1 -0
  164. package/lib/typescript/commonjs/agent-creator/constants.d.ts +7 -15
  165. package/lib/typescript/commonjs/agent-creator/constants.d.ts.map +1 -1
  166. package/lib/typescript/commonjs/agent-creator/messages.d.ts.map +1 -1
  167. package/lib/typescript/module/agent-avatar/CharacterAvatar.d.ts.map +1 -1
  168. package/lib/typescript/module/agent-avatar/character-colors.d.ts +13 -0
  169. package/lib/typescript/module/agent-avatar/character-colors.d.ts.map +1 -0
  170. package/lib/typescript/module/agent-avatar/character-shapes.d.ts +19 -0
  171. package/lib/typescript/module/agent-avatar/character-shapes.d.ts.map +1 -0
  172. package/lib/typescript/module/agent-avatar/config-character.d.ts +4 -0
  173. package/lib/typescript/module/agent-avatar/config-character.d.ts.map +1 -1
  174. package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts +2 -10
  175. package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts.map +1 -1
  176. package/lib/typescript/module/agent-creator/AgentCreatorBase.d.ts.map +1 -1
  177. package/lib/typescript/module/agent-creator/CharacterControls.d.ts +2 -1
  178. package/lib/typescript/module/agent-creator/CharacterControls.d.ts.map +1 -1
  179. package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts +6 -0
  180. package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts.map +1 -0
  181. package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts +5 -0
  182. package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts.map +1 -0
  183. package/lib/typescript/module/agent-creator/EmotionPicker.d.ts +1 -0
  184. package/lib/typescript/module/agent-creator/EmotionPicker.d.ts.map +1 -1
  185. package/lib/typescript/module/agent-creator/ShapeSilhouette.d.ts.map +1 -1
  186. package/lib/typescript/module/agent-creator/ToddArtwork.d.ts +6 -0
  187. package/lib/typescript/module/agent-creator/ToddArtwork.d.ts.map +1 -0
  188. package/lib/typescript/module/agent-creator/constants.d.ts +7 -15
  189. package/lib/typescript/module/agent-creator/constants.d.ts.map +1 -1
  190. package/lib/typescript/module/agent-creator/messages.d.ts.map +1 -1
  191. package/package.json +1 -1
  192. package/src/agent-avatar/AgentAvatar.tsx +1 -1
  193. package/src/agent-avatar/CharacterAvatar.tsx +4 -1
  194. package/src/agent-avatar/character-colors.ts +12 -0
  195. package/src/agent-avatar/character-shapes.ts +122 -0
  196. package/src/agent-avatar/config-character.ts +39 -6
  197. package/src/agent-avatar/legacy-recipe.ts +34 -6
  198. package/src/agent-creator/AgentCreatorBase.tsx +121 -152
  199. package/src/agent-creator/CharacterControls.tsx +39 -14
  200. package/src/agent-creator/ClippoArtwork.tsx +49 -0
  201. package/src/agent-creator/CyclopsArtwork.tsx +27 -0
  202. package/src/agent-creator/EmotionPicker.tsx +55 -16
  203. package/src/agent-creator/ShapeSilhouette.tsx +16 -1
  204. package/src/agent-creator/ToddArtwork.tsx +24 -0
  205. package/src/agent-creator/constants.ts +47 -14
  206. package/src/agent-creator/messages.ts +2 -1
  207. package/src/locale/translations/ar.ts +1 -1
  208. package/src/locale/translations/bn.ts +1 -1
  209. package/src/locale/translations/ca.ts +1 -1
  210. package/src/locale/translations/de.ts +1 -1
  211. package/src/locale/translations/es.ts +1 -1
  212. package/src/locale/translations/fr.ts +1 -1
  213. package/src/locale/translations/hi.ts +1 -1
  214. package/src/locale/translations/id.ts +1 -1
  215. package/src/locale/translations/it.ts +1 -1
  216. package/src/locale/translations/ja.ts +1 -1
  217. package/src/locale/translations/pt.ts +1 -1
  218. package/src/locale/translations/ru.ts +1 -1
  219. package/src/locale/translations/tr.ts +1 -1
  220. package/src/locale/translations/zh.ts +1 -1
  221. package/src/__tests__/support/adoption-matrix.ts +0 -683
  222. package/src/__tests__/support/card-surface.ts +0 -25
  223. package/src/__tests__/support/collision-fixture-barrel.ts +0 -20
  224. package/src/__tests__/support/commerce-harness.tsx +0 -97
  225. package/src/__tests__/support/composite.ts +0 -68
  226. package/src/__tests__/support/constructed-style-sheets.ts +0 -68
  227. package/src/__tests__/support/messages-in.ts +0 -10
  228. package/src/__tests__/support/press-host.ts +0 -30
  229. package/src/__tests__/support/rendered-style.ts +0 -99
  230. package/src/__tests__/support/unread-hook-fixture.ts +0 -33
  231. package/src/__tests__/support/worklet-capture-fixture.tsx +0 -28
  232. package/src/agent-avatar/__tests__/artwork-golden.json +0 -774
  233. package/src/agent-creator/__tests__/support/character-runtime-fixture.tsx +0 -26
  234. package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +0 -9346
  235. package/src/theme/__tests__/__fixtures__/tonal-glass-primary-failures.json +0 -69
  236. package/src/theme/__tests__/fixtures/color-engine-golden.json +0 -1
@@ -1,76 +1,429 @@
1
+ import {
2
+ NATIVE_PARTS,
3
+ ORIGINAL_PRESETS,
4
+ MIGRATED_SHAPE_IDS,
5
+ } from './character-recipe.mjs';
1
6
  import createModule from './orbit-characters.mjs';
7
+ import { createControllerModeAdapter } from './controller-mode.mjs';
8
+ import {
9
+ createPreparationCache,
10
+ preparationKey,
11
+ } from './preparation-cache.mjs';
12
+ import { waitForSharedRender } from './shared-surface.mjs';
2
13
 
14
+ let controller;
3
15
  const pool = new Map();
16
+ const authoredFacades = new Map();
4
17
  const pending = new Map();
5
- let worker,
18
+ const prepared = createPreparationCache();
19
+ const originalScope = Object.freeze({ points: undefined });
20
+ let modulePromise,
21
+ originalFacade,
22
+ currentScope,
23
+ worker,
6
24
  idleTimer,
25
+ characters = 0,
7
26
  sequence = 0;
27
+
28
+ const dispatchTimers = new Map();
29
+ const preparationQueue = [];
30
+ let activePreparation;
31
+
32
+ function pumpPreparationQueue() {
33
+ if (pending.size || dispatchTimers.size) return;
34
+ if (activePreparation && !activePreparation.released) return;
35
+ activePreparation = undefined;
36
+ const next = preparationQueue.shift();
37
+ if (!next) return;
38
+ next.signal?.removeEventListener('abort', next.abort);
39
+ const lease = { released: false };
40
+ activePreparation = lease;
41
+ next.resolve(() => {
42
+ if (lease.released) return;
43
+ lease.released = true;
44
+ pumpPreparationQueue();
45
+ });
46
+ }
47
+
48
+ function scoped(scope, operation) {
49
+ const previous = currentScope;
50
+ const schedule = globalThis.setTimeout;
51
+ const cancel = globalThis.clearTimeout;
52
+ currentScope = scope;
53
+ // Emscripten dispatches preparation asynchronously. Keep the owning scope
54
+ // through its timer, but preserve the engine's timing and callback ordering.
55
+ // The preparation lease prevents other characters from joining that global
56
+ // native queue until this owner's preparation has settled.
57
+ globalThis.setTimeout = function (callback, delay, ...args) {
58
+ const nativeDispatcher =
59
+ typeof callback === 'function' &&
60
+ Number(delay) === 0 &&
61
+ Function.prototype.toString
62
+ .call(callback)
63
+ .includes(
64
+ 'safeSetTimeout.mapping[id]=undefined;callUserCallback(func)',
65
+ );
66
+ let handle;
67
+ handle = schedule.call(
68
+ globalThis,
69
+ typeof callback === 'function'
70
+ ? (...values) => {
71
+ if (nativeDispatcher) dispatchTimers.delete(handle);
72
+ try {
73
+ return scoped(scope, () => callback(...values));
74
+ } finally {
75
+ pumpPreparationQueue();
76
+ }
77
+ }
78
+ : callback,
79
+ delay,
80
+ ...args,
81
+ );
82
+ if (nativeDispatcher) dispatchTimers.set(handle, scope);
83
+ return handle;
84
+ };
85
+ globalThis.clearTimeout = function (handle) {
86
+ dispatchTimers.delete(handle);
87
+ return cancel.call(globalThis, handle);
88
+ };
89
+ try {
90
+ return operation();
91
+ } finally {
92
+ globalThis.setTimeout = schedule;
93
+ globalThis.clearTimeout = cancel;
94
+ currentScope = previous;
95
+ pumpPreparationQueue();
96
+ }
97
+ }
98
+
99
+ /** Fair, cancelable setup lease for the native engine's global prepare queue.
100
+ * Acquire before constructing or changing a Character's geometry. Release
101
+ * after preparation/paint, or dispose/error. A released owner with a pending
102
+ * native callback retains its turn until that callback has completed.
103
+ */
104
+ export function acquireCharacterPreparation(signal) {
105
+ return new Promise((resolve, reject) => {
106
+ const abort = () => {
107
+ const index = preparationQueue.indexOf(entry);
108
+ if (index >= 0) preparationQueue.splice(index, 1);
109
+ reject(new DOMException('Character preparation aborted', 'AbortError'));
110
+ pumpPreparationQueue();
111
+ };
112
+ const entry = { resolve, reject, signal, abort };
113
+ if (signal?.aborted) {
114
+ abort();
115
+ return;
116
+ }
117
+ signal?.addEventListener('abort', abort, { once: true });
118
+ preparationQueue.push(entry);
119
+ pumpPreparationQueue();
120
+ });
121
+ }
122
+
123
+ function stopIdleWorker() {
124
+ clearTimeout(idleTimer);
125
+ if (characters || pool.size || pending.size || dispatchTimers.size) return;
126
+ idleTimer = setTimeout(() => {
127
+ if (!characters && !pool.size && !pending.size && !dispatchTimers.size) {
128
+ worker?.terminate();
129
+ worker = undefined;
130
+ }
131
+ }, 0);
132
+ }
133
+
134
+ async function finishRequest(id, result, reused = false) {
135
+ const entry = pending.get(id);
136
+ if (!entry || entry.delivering) return;
137
+ entry.delivering = true;
138
+ try {
139
+ await entry.complete(result, reused);
140
+ } finally {
141
+ // Keep the owner's turn while the GPU boundary and native completion are
142
+ // still waiting, even if that owner has already been canceled or deleted.
143
+ pending.delete(id);
144
+ pumpPreparationQueue();
145
+ stopIdleWorker();
146
+ }
147
+ }
148
+
8
149
  function preparationWorker() {
150
+ clearTimeout(idleTimer);
9
151
  if (worker) return worker;
10
152
  worker = new Worker(new URL('./legacy-worker.mjs', import.meta.url), {
11
153
  type: 'module',
12
154
  });
13
155
  worker.onmessage = ({ data }) => {
14
- const entry = pending.get(data.id);
15
- if (!entry) return;
16
- pending.delete(data.id);
17
- entry.complete(data);
156
+ void finishRequest(data.id, data);
18
157
  };
19
158
  worker.onerror = () => {
20
- const failed = [...pending.values()];
21
- pending.clear();
159
+ const failed = [...pending.keys()];
22
160
  worker?.terminate();
23
161
  worker = undefined;
24
- for (const entry of failed)
25
- entry.complete({
26
- error: 'Legacy character preparation failed',
162
+ for (const id of failed)
163
+ void finishRequest(id, {
164
+ error: 'Character preparation failed',
27
165
  milliseconds: 0,
28
166
  });
29
167
  };
30
168
  return worker;
31
169
  }
32
170
 
33
- /** Separate geometry caches for different contours; one shared preparation worker.
34
- * Unused rendering modules are released, never retained in an unbounded catalog.
35
- */
36
- export async function acquireLegacyEngine(points) {
37
- clearTimeout(idleTimer);
38
- const key = JSON.stringify(points);
39
- let entry = pool.get(key);
40
- if (!entry) {
41
- entry = { references: 0, module: null };
42
- entry.module = createModule({
43
- locateFile: (file) => new URL(file, import.meta.url).href,
44
- }).then((module) => {
45
- module.orbitPrepare = (id, appearance, quality, cacheKey, activities) => {
46
- const request = ++sequence;
47
- pending.set(request, {
48
- owner: entry,
49
- complete: (result) => {
171
+ function sharedModule() {
172
+ if (!modulePromise) {
173
+ controller = createControllerModeAdapter(
174
+ () => new URL('./orbit-characters.wasm', import.meta.url),
175
+ );
176
+ modulePromise = Promise.race([
177
+ createModule({
178
+ locateFile: (file) => new URL(file, import.meta.url).href,
179
+ instantiateWasm: controller.instantiateWasm,
180
+ }),
181
+ controller.failure,
182
+ ])
183
+ .then((module) => {
184
+ module.orbitPrepare = (
185
+ id,
186
+ appearance,
187
+ quality,
188
+ cacheKey,
189
+ activities,
190
+ ) => {
191
+ const scope = currentScope;
192
+ if (!scope) {
50
193
  module.orbitCompletePreparation(
51
194
  id,
52
- result.bytes ?? null,
53
- result.error || '',
54
- result.milliseconds || 0,
195
+ null,
196
+ 'Character preparation requires a scoped call',
197
+ 0,
55
198
  );
56
- },
57
- });
58
- try {
59
- preparationWorker().postMessage({
60
- id: request,
199
+ return;
200
+ }
201
+ const request = ++sequence;
202
+ const cacheKeyExact = preparationKey({
61
203
  appearance,
62
204
  quality,
63
205
  key: cacheKey,
64
206
  activities,
65
- points,
207
+ points: scope.points,
208
+ authoredParts: scope.authoredParts,
209
+ eyeSpacing: scope.authoredParts?.eyeSpacing,
210
+ });
211
+ const complete = async (result, reused = false) => {
212
+ await waitForSharedRender();
213
+ return scoped(scope, () => {
214
+ if (result.bytes && !result.error && !reused)
215
+ prepared.set(cacheKeyExact, result.bytes);
216
+ module.orbitCompletePreparation(
217
+ id,
218
+ result.bytes ?? null,
219
+ result.error || '',
220
+ result.milliseconds || 0,
221
+ );
222
+ });
223
+ };
224
+ pending.set(request, { complete });
225
+ const cached = prepared.get(cacheKeyExact);
226
+ if (cached) {
227
+ // Preserve asynchronous completion and the owner's preparation
228
+ // turn. Completing inside orbitPrepare would re-enter native
229
+ // dispatch while its job is still being registered.
230
+ queueMicrotask(() => {
231
+ void finishRequest(
232
+ request,
233
+ { bytes: cached, milliseconds: 0 },
234
+ true,
235
+ );
236
+ });
237
+ return;
238
+ }
239
+ try {
240
+ preparationWorker().postMessage({
241
+ id: request,
242
+ appearance,
243
+ quality,
244
+ key: cacheKey,
245
+ activities,
246
+ points: scope.points,
247
+ authoredParts: scope.authoredParts,
248
+ eyeSpacing: scope.authoredParts?.eyeSpacing,
249
+ });
250
+ } catch (error) {
251
+ void finishRequest(request, {
252
+ error: String(error),
253
+ milliseconds: 0,
254
+ });
255
+ }
256
+ };
257
+ return module;
258
+ })
259
+ .catch((error) => {
260
+ modulePromise = undefined;
261
+ throw error;
262
+ });
263
+ }
264
+ return modulePromise;
265
+ }
266
+
267
+ function facade(module, scope) {
268
+ const result = Object.create(module);
269
+ const handles = new WeakMap();
270
+ function wrap(target, activities = false, source) {
271
+ controller.register(target, activities, source);
272
+ const methods = new Map();
273
+ let deleted = false;
274
+ characters++;
275
+ const proxy = new Proxy(target, {
276
+ get(instance, key) {
277
+ const value = Reflect.get(instance, key, instance);
278
+ if (typeof value !== 'function' || key === 'constructor') return value;
279
+ if (!methods.has(key))
280
+ methods.set(key, (...args) => {
281
+ const returned = scoped(scope, () => value.apply(instance, args));
282
+ if (key === 'applyActivity' && returned === 0 && activities)
283
+ controller.markBound(instance);
284
+ if (key === 'delete' && !deleted) {
285
+ controller.unregister(instance);
286
+ deleted = true;
287
+ characters--;
288
+ stopIdleWorker();
289
+ }
290
+ // Embind handle clones retain the same native character and scope.
291
+ if (returned !== instance && returned instanceof module.Character)
292
+ return wrap(returned, activities, instance);
293
+ return returned;
66
294
  });
67
- } catch (error) {
68
- pending.delete(request);
69
- module.orbitCompletePreparation(id, null, String(error), 0);
70
- }
71
- };
72
- return module;
295
+ return methods.get(key);
296
+ },
73
297
  });
298
+ handles.set(proxy, target);
299
+ return proxy;
300
+ }
301
+ result.controllerMode = (character, enabled) => {
302
+ const instance = handles.get(character);
303
+ if (!instance)
304
+ throw new Error(
305
+ 'Character controller belongs to a different engine scope',
306
+ );
307
+ return scoped(scope, () => controller.controllerMode(instance, enabled));
308
+ };
309
+ result.controllerState = (character) => {
310
+ const instance = handles.get(character);
311
+ if (!instance)
312
+ throw new Error(
313
+ 'Character controller belongs to a different engine scope',
314
+ );
315
+ return scoped(scope, () => controller.controllerState(instance));
316
+ };
317
+ result.controllerSignature = (character, preset) => {
318
+ const instance = handles.get(character);
319
+ if (!instance)
320
+ throw new Error(
321
+ 'Character controller belongs to a different engine scope',
322
+ );
323
+ return scoped(scope, () =>
324
+ controller.controllerSignature(instance, preset),
325
+ );
326
+ };
327
+ result.controllerWithSignature = (character, preset, operation) => {
328
+ const instance = handles.get(character);
329
+ if (!instance)
330
+ throw new Error(
331
+ 'Character controller belongs to a different engine scope',
332
+ );
333
+ return scoped(scope, () =>
334
+ controller.controllerWithSignature(instance, preset, operation),
335
+ );
336
+ };
337
+ result.Character = function Character(...args) {
338
+ clearTimeout(idleTimer);
339
+ return wrap(
340
+ scoped(scope, () => new module.Character(...args)),
341
+ args[3]?.activities,
342
+ );
343
+ };
344
+ result.Character.prototype = module.Character.prototype;
345
+ return result;
346
+ }
347
+
348
+ /** Original and custom bodies share one WASM instance and preparation worker.
349
+ * Prepared mesh identities distinguish contours inside the shared GPU cache.
350
+ */
351
+ function normalizeAuthoredParts(value) {
352
+ if (!value || !Object.keys(value).length) return undefined;
353
+ const keys = [
354
+ 'bodyPreset',
355
+ 'paintBody',
356
+ 'shape',
357
+ 'eyes',
358
+ 'eyewear',
359
+ 'accessory',
360
+ 'eyeSpacing',
361
+ 'faceShape',
362
+ ];
363
+ if (
364
+ Object.keys(value).some((key) => !keys.includes(key)) ||
365
+ (value.bodyPreset !== undefined &&
366
+ !ORIGINAL_PRESETS.includes(value.bodyPreset)) ||
367
+ (value.paintBody !== undefined && typeof value.paintBody !== 'boolean') ||
368
+ (value.shape !== undefined && value.shape !== 'clippo') ||
369
+ (value.faceShape !== undefined &&
370
+ ![...MIGRATED_SHAPE_IDS, ...NATIVE_PARTS.shape].includes(
371
+ value.faceShape,
372
+ )) ||
373
+ (value.eyes !== undefined &&
374
+ ![...NATIVE_PARTS.eyes, 'todd', 'clippo', 'cyclops'].includes(
375
+ value.eyes,
376
+ )) ||
377
+ (value.eyewear !== undefined &&
378
+ !NATIVE_PARTS.eyewear.includes(value.eyewear)) ||
379
+ (value.accessory !== undefined &&
380
+ ![...NATIVE_PARTS.accessory, 'felipe_beret'].includes(value.accessory))
381
+ )
382
+ throw new Error('Unsupported authored character part');
383
+ return Object.freeze(
384
+ Object.fromEntries(
385
+ keys
386
+ .filter((key) => value[key] !== undefined && key !== 'eyeSpacing')
387
+ .map((key) => [key, value[key]]),
388
+ ),
389
+ );
390
+ }
391
+ export async function getCharacterEngine(parts) {
392
+ const authoredParts = normalizeAuthoredParts(parts);
393
+ const module = await sharedModule();
394
+ if (!authoredParts) {
395
+ originalFacade ??= facade(module, originalScope);
396
+ return originalFacade;
397
+ }
398
+ // Retain a bounded set of recipe facades; live Characters own their scopes.
399
+ const key = JSON.stringify(authoredParts);
400
+ if (!authoredFacades.has(key))
401
+ authoredFacades.set(
402
+ key,
403
+ facade(module, { points: undefined, authoredParts }),
404
+ );
405
+ const result = authoredFacades.get(key);
406
+ authoredFacades.delete(key);
407
+ authoredFacades.set(key, result);
408
+ if (authoredFacades.size > 64)
409
+ authoredFacades.delete(authoredFacades.keys().next().value);
410
+ return result;
411
+ }
412
+
413
+ export async function acquireLegacyEngine(points, parts) {
414
+ clearTimeout(idleTimer);
415
+ const authoredParts = normalizeAuthoredParts(parts);
416
+ const key = JSON.stringify([points, authoredParts]);
417
+ let entry = pool.get(key);
418
+ if (!entry) {
419
+ const scope = {
420
+ points: points.map((point) => point.slice()),
421
+ authoredParts,
422
+ };
423
+ entry = {
424
+ references: 0,
425
+ facade: sharedModule().then((module) => facade(module, scope)),
426
+ };
74
427
  pool.set(key, entry);
75
428
  }
76
429
  entry.references++;
@@ -78,20 +431,11 @@ export async function acquireLegacyEngine(points) {
78
431
  const release = () => {
79
432
  if (released) return;
80
433
  released = true;
81
- if (--entry.references !== 0) return;
82
- pool.delete(key);
83
- for (const [id, request] of pending)
84
- if (request.owner === entry) pending.delete(id);
85
- if (!pool.size)
86
- idleTimer = setTimeout(() => {
87
- if (!pool.size) {
88
- worker?.terminate();
89
- worker = undefined;
90
- }
91
- }, 0);
434
+ if (--entry.references === 0) pool.delete(key);
435
+ stopIdleWorker();
92
436
  };
93
437
  try {
94
- return { module: await entry.module, release };
438
+ return { module: await entry.facade, release };
95
439
  } catch (error) {
96
440
  release();
97
441
  throw error;
@@ -99,7 +443,15 @@ export async function acquireLegacyEngine(points) {
99
443
  }
100
444
 
101
445
  export const legacyEngineStats = () => ({
446
+ // Historical field counts leased contour entries; sharedModules is the
447
+ // actual WASM count, including the instance used by original characters.
102
448
  modules: pool.size,
449
+ sharedModules: modulePromise ? 1 : 0,
450
+ characters,
451
+ preparationWaiters: preparationQueue.length,
452
+ preparationActive: !!activePreparation,
453
+ dispatchTimers: dispatchTimers.size,
103
454
  pending: pending.size,
104
455
  worker: !!worker,
456
+ preparationCache: prepared.stats(),
105
457
  });