@oxy.so/bloom 7.1.4 → 7.1.5

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 (220) hide show
  1. package/assets/character-runtime/README.md +19 -114
  2. package/assets/character-runtime/legacy-engine.mjs +54 -406
  3. package/assets/character-runtime/legacy-geometry.mjs +85 -242
  4. package/assets/character-runtime/legacy-worker.mjs +23 -82
  5. package/assets/character-runtime/managed-avatar.mjs +18 -12
  6. package/assets/character-runtime/render-budget.mjs +11 -6
  7. package/assets/character-runtime/runtime.mjs +295 -744
  8. package/docs/agent-avatar.mdx +8 -45
  9. package/docs/agent-creator.mdx +3 -13
  10. package/lib/commonjs/agent-avatar/AgentAvatar.js +1 -1
  11. package/lib/commonjs/agent-avatar/AgentAvatar.js.map +1 -1
  12. package/lib/commonjs/agent-avatar/CharacterAvatar.js +1 -2
  13. package/lib/commonjs/agent-avatar/CharacterAvatar.js.map +1 -1
  14. package/lib/commonjs/agent-avatar/config-character.js +0 -4
  15. package/lib/commonjs/agent-avatar/config-character.js.map +1 -1
  16. package/lib/commonjs/agent-avatar/legacy-recipe.js +6 -19
  17. package/lib/commonjs/agent-avatar/legacy-recipe.js.map +1 -1
  18. package/lib/commonjs/agent-creator/AgentCreatorBase.js +107 -67
  19. package/lib/commonjs/agent-creator/AgentCreatorBase.js.map +1 -1
  20. package/lib/commonjs/agent-creator/CharacterControls.js +5 -23
  21. package/lib/commonjs/agent-creator/CharacterControls.js.map +1 -1
  22. package/lib/commonjs/agent-creator/EmotionPicker.js +1 -8
  23. package/lib/commonjs/agent-creator/EmotionPicker.js.map +1 -1
  24. package/lib/commonjs/agent-creator/ShapeSilhouette.js +1 -8
  25. package/lib/commonjs/agent-creator/ShapeSilhouette.js.map +1 -1
  26. package/lib/commonjs/agent-creator/constants.js +17 -16
  27. package/lib/commonjs/agent-creator/constants.js.map +1 -1
  28. package/lib/commonjs/agent-creator/messages.js +1 -1
  29. package/lib/commonjs/agent-creator/messages.js.map +1 -1
  30. package/lib/commonjs/locale/translations/ar.js +1 -1
  31. package/lib/commonjs/locale/translations/ar.js.map +1 -1
  32. package/lib/commonjs/locale/translations/bn.js +1 -1
  33. package/lib/commonjs/locale/translations/bn.js.map +1 -1
  34. package/lib/commonjs/locale/translations/ca.js +1 -1
  35. package/lib/commonjs/locale/translations/ca.js.map +1 -1
  36. package/lib/commonjs/locale/translations/de.js +1 -1
  37. package/lib/commonjs/locale/translations/de.js.map +1 -1
  38. package/lib/commonjs/locale/translations/es.js +1 -1
  39. package/lib/commonjs/locale/translations/es.js.map +1 -1
  40. package/lib/commonjs/locale/translations/fr.js +1 -1
  41. package/lib/commonjs/locale/translations/fr.js.map +1 -1
  42. package/lib/commonjs/locale/translations/hi.js +1 -1
  43. package/lib/commonjs/locale/translations/hi.js.map +1 -1
  44. package/lib/commonjs/locale/translations/id.js +1 -1
  45. package/lib/commonjs/locale/translations/id.js.map +1 -1
  46. package/lib/commonjs/locale/translations/it.js +1 -1
  47. package/lib/commonjs/locale/translations/it.js.map +1 -1
  48. package/lib/commonjs/locale/translations/ja.js +1 -1
  49. package/lib/commonjs/locale/translations/ja.js.map +1 -1
  50. package/lib/commonjs/locale/translations/pt.js +1 -1
  51. package/lib/commonjs/locale/translations/pt.js.map +1 -1
  52. package/lib/commonjs/locale/translations/ru.js +1 -1
  53. package/lib/commonjs/locale/translations/ru.js.map +1 -1
  54. package/lib/commonjs/locale/translations/tr.js +1 -1
  55. package/lib/commonjs/locale/translations/tr.js.map +1 -1
  56. package/lib/commonjs/locale/translations/zh.js +1 -1
  57. package/lib/commonjs/locale/translations/zh.js.map +1 -1
  58. package/lib/module/agent-avatar/AgentAvatar.js +1 -1
  59. package/lib/module/agent-avatar/AgentAvatar.js.map +1 -1
  60. package/lib/module/agent-avatar/CharacterAvatar.js +1 -2
  61. package/lib/module/agent-avatar/CharacterAvatar.js.map +1 -1
  62. package/lib/module/agent-avatar/config-character.js +0 -4
  63. package/lib/module/agent-avatar/config-character.js.map +1 -1
  64. package/lib/module/agent-avatar/legacy-recipe.js +6 -19
  65. package/lib/module/agent-avatar/legacy-recipe.js.map +1 -1
  66. package/lib/module/agent-creator/AgentCreatorBase.js +109 -69
  67. package/lib/module/agent-creator/AgentCreatorBase.js.map +1 -1
  68. package/lib/module/agent-creator/CharacterControls.js +6 -24
  69. package/lib/module/agent-creator/CharacterControls.js.map +1 -1
  70. package/lib/module/agent-creator/EmotionPicker.js +1 -8
  71. package/lib/module/agent-creator/EmotionPicker.js.map +1 -1
  72. package/lib/module/agent-creator/ShapeSilhouette.js +1 -8
  73. package/lib/module/agent-creator/ShapeSilhouette.js.map +1 -1
  74. package/lib/module/agent-creator/constants.js +16 -8
  75. package/lib/module/agent-creator/constants.js.map +1 -1
  76. package/lib/module/agent-creator/messages.js +1 -1
  77. package/lib/module/agent-creator/messages.js.map +1 -1
  78. package/lib/module/locale/translations/ar.js +1 -1
  79. package/lib/module/locale/translations/ar.js.map +1 -1
  80. package/lib/module/locale/translations/bn.js +1 -1
  81. package/lib/module/locale/translations/bn.js.map +1 -1
  82. package/lib/module/locale/translations/ca.js +1 -1
  83. package/lib/module/locale/translations/ca.js.map +1 -1
  84. package/lib/module/locale/translations/de.js +1 -1
  85. package/lib/module/locale/translations/de.js.map +1 -1
  86. package/lib/module/locale/translations/es.js +1 -1
  87. package/lib/module/locale/translations/es.js.map +1 -1
  88. package/lib/module/locale/translations/fr.js +1 -1
  89. package/lib/module/locale/translations/fr.js.map +1 -1
  90. package/lib/module/locale/translations/hi.js +1 -1
  91. package/lib/module/locale/translations/hi.js.map +1 -1
  92. package/lib/module/locale/translations/id.js +1 -1
  93. package/lib/module/locale/translations/id.js.map +1 -1
  94. package/lib/module/locale/translations/it.js +1 -1
  95. package/lib/module/locale/translations/it.js.map +1 -1
  96. package/lib/module/locale/translations/ja.js +1 -1
  97. package/lib/module/locale/translations/ja.js.map +1 -1
  98. package/lib/module/locale/translations/pt.js +1 -1
  99. package/lib/module/locale/translations/pt.js.map +1 -1
  100. package/lib/module/locale/translations/ru.js +1 -1
  101. package/lib/module/locale/translations/ru.js.map +1 -1
  102. package/lib/module/locale/translations/tr.js +1 -1
  103. package/lib/module/locale/translations/tr.js.map +1 -1
  104. package/lib/module/locale/translations/zh.js +1 -1
  105. package/lib/module/locale/translations/zh.js.map +1 -1
  106. package/lib/typescript/commonjs/agent-avatar/CharacterAvatar.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/agent-avatar/config-character.d.ts +0 -4
  108. package/lib/typescript/commonjs/agent-avatar/config-character.d.ts.map +1 -1
  109. package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts +10 -2
  110. package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/agent-creator/AgentCreatorBase.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts +1 -2
  113. package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts.map +1 -1
  114. package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts +0 -1
  115. package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts.map +1 -1
  116. package/lib/typescript/commonjs/agent-creator/ShapeSilhouette.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/agent-creator/constants.d.ts +15 -7
  118. package/lib/typescript/commonjs/agent-creator/constants.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/agent-creator/messages.d.ts.map +1 -1
  120. package/lib/typescript/module/agent-avatar/CharacterAvatar.d.ts.map +1 -1
  121. package/lib/typescript/module/agent-avatar/config-character.d.ts +0 -4
  122. package/lib/typescript/module/agent-avatar/config-character.d.ts.map +1 -1
  123. package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts +10 -2
  124. package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts.map +1 -1
  125. package/lib/typescript/module/agent-creator/AgentCreatorBase.d.ts.map +1 -1
  126. package/lib/typescript/module/agent-creator/CharacterControls.d.ts +1 -2
  127. package/lib/typescript/module/agent-creator/CharacterControls.d.ts.map +1 -1
  128. package/lib/typescript/module/agent-creator/EmotionPicker.d.ts +0 -1
  129. package/lib/typescript/module/agent-creator/EmotionPicker.d.ts.map +1 -1
  130. package/lib/typescript/module/agent-creator/ShapeSilhouette.d.ts.map +1 -1
  131. package/lib/typescript/module/agent-creator/constants.d.ts +15 -7
  132. package/lib/typescript/module/agent-creator/constants.d.ts.map +1 -1
  133. package/lib/typescript/module/agent-creator/messages.d.ts.map +1 -1
  134. package/package.json +1 -1
  135. package/src/agent-avatar/AgentAvatar.tsx +1 -1
  136. package/src/agent-avatar/CharacterAvatar.tsx +1 -4
  137. package/src/agent-avatar/config-character.ts +6 -39
  138. package/src/agent-avatar/legacy-recipe.ts +6 -34
  139. package/src/agent-creator/AgentCreatorBase.tsx +152 -121
  140. package/src/agent-creator/CharacterControls.tsx +14 -39
  141. package/src/agent-creator/EmotionPicker.tsx +16 -55
  142. package/src/agent-creator/ShapeSilhouette.tsx +1 -16
  143. package/src/agent-creator/constants.ts +14 -47
  144. package/src/agent-creator/messages.ts +1 -2
  145. package/src/locale/translations/ar.ts +1 -1
  146. package/src/locale/translations/bn.ts +1 -1
  147. package/src/locale/translations/ca.ts +1 -1
  148. package/src/locale/translations/de.ts +1 -1
  149. package/src/locale/translations/es.ts +1 -1
  150. package/src/locale/translations/fr.ts +1 -1
  151. package/src/locale/translations/hi.ts +1 -1
  152. package/src/locale/translations/id.ts +1 -1
  153. package/src/locale/translations/it.ts +1 -1
  154. package/src/locale/translations/ja.ts +1 -1
  155. package/src/locale/translations/pt.ts +1 -1
  156. package/src/locale/translations/ru.ts +1 -1
  157. package/src/locale/translations/tr.ts +1 -1
  158. package/src/locale/translations/zh.ts +1 -1
  159. package/assets/character-runtime/authored-parts.mjs +0 -754
  160. package/assets/character-runtime/catalog-parts.mjs +0 -362
  161. package/assets/character-runtime/character-recipe.mjs +0 -247
  162. package/assets/character-runtime/clippo-geometry.mjs +0 -679
  163. package/assets/character-runtime/controller-mode.mjs +0 -208
  164. package/assets/character-runtime/cyclops-geometry.mjs +0 -180
  165. package/assets/character-runtime/migrated-contours.mjs +0 -2067
  166. package/assets/character-runtime/migrated-faces.mjs +0 -59
  167. package/assets/character-runtime/pose-transition.mjs +0 -523
  168. package/assets/character-runtime/preparation-cache.mjs +0 -60
  169. package/assets/character-runtime/render-errors.mjs +0 -180
  170. package/assets/character-runtime/resolution.mjs +0 -10
  171. package/assets/character-runtime/sha256.mjs +0 -44
  172. package/assets/character-runtime/shared-programs.mjs +0 -534
  173. package/assets/character-runtime/shared-surface.mjs +0 -490
  174. package/assets/character-runtime/shared-textures.mjs +0 -420
  175. package/assets/character-runtime/thumbnails/eyes/todd.png +0 -0
  176. package/lib/commonjs/agent-avatar/character-colors.js +0 -19
  177. package/lib/commonjs/agent-avatar/character-colors.js.map +0 -1
  178. package/lib/commonjs/agent-avatar/character-shapes.js +0 -106
  179. package/lib/commonjs/agent-avatar/character-shapes.js.map +0 -1
  180. package/lib/commonjs/agent-creator/ClippoArtwork.js +0 -63
  181. package/lib/commonjs/agent-creator/ClippoArtwork.js.map +0 -1
  182. package/lib/commonjs/agent-creator/CyclopsArtwork.js +0 -45
  183. package/lib/commonjs/agent-creator/CyclopsArtwork.js.map +0 -1
  184. package/lib/commonjs/agent-creator/ToddArtwork.js +0 -29
  185. package/lib/commonjs/agent-creator/ToddArtwork.js.map +0 -1
  186. package/lib/module/agent-avatar/character-colors.js +0 -15
  187. package/lib/module/agent-avatar/character-colors.js.map +0 -1
  188. package/lib/module/agent-avatar/character-shapes.js +0 -97
  189. package/lib/module/agent-avatar/character-shapes.js.map +0 -1
  190. package/lib/module/agent-creator/ClippoArtwork.js +0 -59
  191. package/lib/module/agent-creator/ClippoArtwork.js.map +0 -1
  192. package/lib/module/agent-creator/CyclopsArtwork.js +0 -41
  193. package/lib/module/agent-creator/CyclopsArtwork.js.map +0 -1
  194. package/lib/module/agent-creator/ToddArtwork.js +0 -25
  195. package/lib/module/agent-creator/ToddArtwork.js.map +0 -1
  196. package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts +0 -13
  197. package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts.map +0 -1
  198. package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts +0 -19
  199. package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts.map +0 -1
  200. package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts +0 -6
  201. package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts.map +0 -1
  202. package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts +0 -5
  203. package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts.map +0 -1
  204. package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts +0 -6
  205. package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts.map +0 -1
  206. package/lib/typescript/module/agent-avatar/character-colors.d.ts +0 -13
  207. package/lib/typescript/module/agent-avatar/character-colors.d.ts.map +0 -1
  208. package/lib/typescript/module/agent-avatar/character-shapes.d.ts +0 -19
  209. package/lib/typescript/module/agent-avatar/character-shapes.d.ts.map +0 -1
  210. package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts +0 -6
  211. package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts.map +0 -1
  212. package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts +0 -5
  213. package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts.map +0 -1
  214. package/lib/typescript/module/agent-creator/ToddArtwork.d.ts +0 -6
  215. package/lib/typescript/module/agent-creator/ToddArtwork.d.ts.map +0 -1
  216. package/src/agent-avatar/character-colors.ts +0 -12
  217. package/src/agent-avatar/character-shapes.ts +0 -122
  218. package/src/agent-creator/ClippoArtwork.tsx +0 -49
  219. package/src/agent-creator/CyclopsArtwork.tsx +0 -27
  220. package/src/agent-creator/ToddArtwork.tsx +0 -24
@@ -1,28 +1,19 @@
1
1
  // Adapter for the recovered renderer's prepared vertex layout. Packaged meshes,
2
2
  // resource manifests, the WASM binary, and the appearance record stay untouched.
3
- const decoder = new TextDecoder(),
4
- encoder = new TextEncoder();
5
- const BODY = ":body:no-shadow:studio-fur-v6:";
3
+ const decoder = new TextDecoder(), encoder = new TextEncoder();
4
+ const BODY = ':body:no-shadow:studio-fur-v6:';
6
5
  // Highlights are separate non-shadow-casting meshes in the original renderer.
7
- const SURFACES = [":surface:shadow", ":surface:no-shadow"];
6
+ const SURFACES = [':surface:shadow', ':surface:no-shadow'];
8
7
  const SURFACE_KEY = /^[a-f0-9]{64}:surface:(?:shadow|no-shadow)$/;
9
8
  const FUR_SURFACE_KEY = /^[a-f0-9]{64}:surface:shadow:studio-fur-v6:/;
10
- const STRIDE = 96,
11
- TAU = Math.PI * 2;
12
- function invalid(message) {
13
- throw new Error(`Unsupported prepared avatar geometry: ${message}`);
14
- }
15
- function finite(value) {
16
- if (!Number.isFinite(value)) invalid("non-finite coordinate");
17
- return value;
18
- }
9
+ const STRIDE = 96, TAU = Math.PI * 2;
10
+ function invalid(message) { throw new Error(`Unsupported prepared avatar geometry: ${message}`); }
11
+ function finite(value) { if (!Number.isFinite(value)) invalid('non-finite coordinate'); return value; }
19
12
  function findAll(bytes, text) {
20
- const needle = encoder.encode(text),
21
- result = [];
13
+ const needle = encoder.encode(text), result = [];
22
14
  outer: for (let i = 0; i <= bytes.length - needle.length; i++) {
23
15
  if (bytes[i] !== needle[0]) continue;
24
- for (let j = 1; j < needle.length; j++)
25
- if (bytes[i + j] !== needle[j]) continue outer;
16
+ for (let j = 1; j < needle.length; j++) if (bytes[i + j] !== needle[j]) continue outer;
26
17
  result.push(i);
27
18
  }
28
19
  return result;
@@ -30,187 +21,82 @@ function findAll(bytes, text) {
30
21
 
31
22
  /** Inspect the bounded, length-prefixed mesh records; reject unknown layouts. */
32
23
  export function inspectLegacyAssembly(bytes) {
33
- if (!(bytes instanceof Uint8Array) || bytes.length < 400)
34
- invalid("truncated assembly");
24
+ if (!(bytes instanceof Uint8Array) || bytes.length < 400) invalid('truncated assembly');
35
25
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
36
- const uint = (at) => {
37
- if (at < 0 || at + 4 > bytes.length) invalid("truncated count");
38
- return view.getUint32(at, true);
39
- };
26
+ const uint = at => { if (at < 0 || at + 4 > bytes.length) invalid('truncated count'); return view.getUint32(at, true); };
40
27
  const bodies = findAll(bytes, BODY);
41
- if (bodies.length !== 1) invalid("expected one furry body");
28
+ if (bodies.length !== 1) invalid('expected one furry body');
42
29
  const bodyStart = bodies[0] - 64;
43
- const starts = [
44
- bodyStart,
45
- ...SURFACES.flatMap((kind) =>
46
- findAll(bytes, kind)
47
- .map((i) => i - 64)
48
- .filter((i) => {
49
- if (i <= bodyStart || i < 4) return false;
50
- const length = uint(i - 4);
51
- if (length < 79 || length > 8192 || i + length > bytes.length)
52
- return false;
53
- const name = decoder.decode(bytes.subarray(i, i + length));
54
- return SURFACE_KEY.test(name) || FUR_SURFACE_KEY.test(name);
55
- }),
56
- ).sort((a, b) => a - b),
57
- ];
30
+ const starts = [bodyStart, ...SURFACES.flatMap(kind => findAll(bytes, kind)
31
+ .map(i => i - 64)
32
+ .filter(i => {
33
+ if (i <= bodyStart || i < 4) return false;
34
+ const length = uint(i - 4);
35
+ if (length < 79 || length > 8192 || i + length > bytes.length) return false;
36
+ const name = decoder.decode(bytes.subarray(i, i + length));
37
+ return SURFACE_KEY.test(name) || FUR_SURFACE_KEY.test(name);
38
+ }))
39
+ .sort((a, b) => a - b)];
58
40
  const meshCount = uint(0);
59
- if (meshCount !== starts.length || meshCount > 40)
60
- invalid("mesh count/key mismatch");
41
+ if (meshCount !== starts.length || meshCount > 40) invalid('mesh count/key mismatch');
61
42
  const parts = starts.map((nameOffset, part) => {
62
- const nameLength = uint(nameOffset - 4),
63
- name = decoder.decode(
64
- bytes.subarray(nameOffset, nameOffset + nameLength),
65
- );
66
- if (
67
- nameLength < 79 ||
68
- nameLength > 8192 ||
69
- nameOffset + nameLength > bytes.length ||
70
- !/^[a-f0-9]{64}:/.test(name)
71
- )
72
- invalid("mesh identity");
73
- if (
74
- part === 0
75
- ? !name.includes(BODY)
76
- : !SURFACE_KEY.test(name) && !FUR_SURFACE_KEY.test(name)
77
- )
78
- invalid("mesh kind");
79
- const vertexCount = uint(nameOffset + nameLength),
80
- vertexOffset = nameOffset + nameLength + 4;
81
- if (vertexCount < 3 || vertexCount > 1000000) invalid("vertex count");
43
+ const nameLength = uint(nameOffset - 4), name = decoder.decode(bytes.subarray(nameOffset, nameOffset + nameLength));
44
+ if (nameLength < 79 || nameLength > 8192 || nameOffset + nameLength > bytes.length || !/^[a-f0-9]{64}:/.test(name)) invalid('mesh identity');
45
+ if (part === 0 ? !name.includes(BODY) : !SURFACE_KEY.test(name) && !FUR_SURFACE_KEY.test(name)) invalid('mesh kind');
46
+ const vertexCount = uint(nameOffset + nameLength), vertexOffset = nameOffset + nameLength + 4;
47
+ if (vertexCount < 3 || vertexCount > 1000000) invalid('vertex count');
82
48
  let at = vertexOffset + vertexCount * STRIDE;
83
- const count32 = uint(at);
84
- at += 4;
85
- if (count32 > 6000000 || at + count32 * 4 > bytes.length)
86
- invalid("32-bit indices");
87
- for (let i = 0; i < count32; i++)
88
- if (uint(at + i * 4) >= vertexCount) invalid("index outside mesh");
49
+ const count32 = uint(at); at += 4;
50
+ if (count32 > 6000000 || at + count32 * 4 > bytes.length) invalid('32-bit indices');
51
+ for (let i = 0; i < count32; i++) if (uint(at + i * 4) >= vertexCount) invalid('index outside mesh');
89
52
  at += count32 * 4;
90
- const count16 = uint(at);
91
- at += 4;
92
- if (count16 > 6000000 || at + count16 * 2 > bytes.length)
93
- invalid("16-bit indices");
94
- for (let i = 0; i < count16; i++)
95
- if (view.getUint16(at + i * 2, true) >= vertexCount)
96
- invalid("index outside mesh");
53
+ const count16 = uint(at); at += 4;
54
+ if (count16 > 6000000 || at + count16 * 2 > bytes.length) invalid('16-bit indices');
55
+ for (let i = 0; i < count16; i++) if (view.getUint16(at + i * 2, true) >= vertexCount) invalid('index outside mesh');
97
56
  at += count16 * 2;
98
57
  // Empty supplemental vertex buffer followed by three boolean mesh flags.
99
- if (
100
- at + 7 > bytes.length ||
101
- uint(at) !== 0 ||
102
- bytes[at + 4] > 1 ||
103
- bytes[at + 5] > 1 ||
104
- bytes[at + 6] > 1
105
- )
106
- invalid("mesh flags");
107
- const hasFur = bytes[at + 6] === 1;
108
- at += 7;
109
- const furCount = hasFur ? uint(at) : 0,
110
- furOffset = hasFur ? at + 4 : 0;
111
- if (
112
- furCount > 1000000 ||
113
- (hasFur && furOffset + furCount * 64 > bytes.length)
114
- )
115
- invalid("fur records");
58
+ if (at + 7 > bytes.length || uint(at) !== 0 || bytes[at + 4] > 1 || bytes[at + 5] > 1 || bytes[at + 6] > 1) invalid('mesh flags');
59
+ const hasFur = bytes[at + 6] === 1; at += 7;
60
+ const furCount = hasFur ? uint(at) : 0, furOffset = hasFur ? at + 4 : 0;
61
+ if (furCount > 1000000 || hasFur && furOffset + furCount * 64 > bytes.length) invalid('fur records');
116
62
  const geometryEnd = hasFur ? furOffset + furCount * 64 : at;
117
- if (
118
- geometryEnd >
119
- (starts[part + 1] === undefined ? bytes.length : starts[part + 1] - 4)
120
- )
121
- invalid("overlapping meshes");
122
- if (part === 0 && !hasFur) invalid("body lacks fur layout");
123
- if (FUR_SURFACE_KEY.test(name) && !hasFur)
124
- invalid("accessory lacks fur layout");
125
- const min = [Infinity, Infinity, Infinity],
126
- max = [-Infinity, -Infinity, -Infinity];
127
- for (let i = 0; i < vertexCount; i++)
128
- for (let k = 0; k < 24; k++) {
129
- const value = finite(
130
- view.getFloat32(vertexOffset + i * STRIDE + k * 4, true),
131
- );
132
- if (k < 3) {
133
- min[k] = Math.min(min[k], value);
134
- max[k] = Math.max(max[k], value);
135
- }
136
- }
137
- return {
138
- part,
139
- name,
140
- nameOffset,
141
- nameLength,
142
- vertexCount,
143
- vertexOffset,
144
- furCount,
145
- furOffset,
146
- geometryEnd,
147
- bounds: { min, max },
148
- };
63
+ if (geometryEnd > (starts[part + 1] === undefined ? bytes.length : starts[part + 1] - 4)) invalid('overlapping meshes');
64
+ if (part === 0 && !hasFur) invalid('body lacks fur layout');
65
+ if (FUR_SURFACE_KEY.test(name) && !hasFur) invalid('accessory lacks fur layout');
66
+ const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
67
+ for (let i = 0; i < vertexCount; i++) for (let k = 0; k < 24; k++) {
68
+ const value = finite(view.getFloat32(vertexOffset + i * STRIDE + k * 4, true));
69
+ if (k < 3) { min[k] = Math.min(min[k], value); max[k] = Math.max(max[k], value); }
70
+ }
71
+ return { part, name, nameOffset, nameLength, vertexCount, vertexOffset, furCount, furOffset, bounds: { min, max } };
149
72
  });
150
73
  return { body: parts[0], parts };
151
74
  }
152
75
 
153
76
  function mapper({ points, radii, depthScale = 1 }) {
154
- if (!Number.isFinite(depthScale) || depthScale <= 0 || depthScale > 4)
155
- invalid("depth scale");
156
- if ((points && radii) || (!points && !radii))
157
- invalid("provide points or radii");
77
+ if (!Number.isFinite(depthScale) || depthScale <= 0 || depthScale > 4) invalid('depth scale');
78
+ if (points && radii || !points && !radii) invalid('provide points or radii');
158
79
  const count = (points || radii).length;
159
- if (count < 16 || count > 65536) invalid("contour resolution");
160
- if (points)
161
- for (const p of points) {
162
- if (p.length !== 2) invalid("contour point");
163
- p.forEach(finite);
164
- }
165
- else
166
- for (const radius of radii)
167
- if (!(finite(radius) > 0)) invalid("contour radius");
80
+ if (count < 16 || count > 65536) invalid('contour resolution');
81
+ if (points) for (const p of points) { if (p.length !== 2) invalid('contour point'); p.forEach(finite); }
82
+ else for (const radius of radii) if (!(finite(radius) > 0)) invalid('contour radius');
168
83
  return (x, y, z) => {
169
- const radius = Math.hypot(x, y),
170
- angle = (Math.atan2(y, x) + TAU) % TAU;
171
- const slot = (angle / TAU) * count,
172
- index = Math.floor(slot),
173
- t = slot - index,
174
- next = (index + 1) % count;
175
- if (points)
176
- return [
177
- radius * (points[index][0] * (1 - t) + points[next][0] * t),
178
- radius * (points[index][1] * (1 - t) + points[next][1] * t),
179
- z * depthScale,
180
- ];
84
+ const radius = Math.hypot(x, y), angle = (Math.atan2(y, x) + TAU) % TAU;
85
+ const slot = angle / TAU * count, index = Math.floor(slot), t = slot - index, next = (index + 1) % count;
86
+ if (points) return [radius * (points[index][0] * (1 - t) + points[next][0] * t), radius * (points[index][1] * (1 - t) + points[next][1] * t), z * depthScale];
181
87
  const scale = radii[index] * (1 - t) + radii[next] * t;
182
88
  return [x * scale, y * scale, z * depthScale];
183
89
  };
184
90
  }
185
91
  function derivative(map, p) {
186
- const epsilon = 0.00005,
187
- base = map(...p);
188
- const columns = [0, 1, 2].map((k) => {
189
- const q = [...p];
190
- q[k] += epsilon;
191
- return map(...q).map((v, i) => (v - base[i]) / epsilon);
192
- });
92
+ const epsilon = .00005, base = map(...p);
93
+ const columns = [0, 1, 2].map(k => { const q = [...p]; q[k] += epsilon; return map(...q).map((v, i) => (v - base[i]) / epsilon); });
193
94
  const [a, b, c] = columns;
194
- const cross = (u, v) => [
195
- u[1] * v[2] - u[2] * v[1],
196
- u[2] * v[0] - u[0] * v[2],
197
- u[0] * v[1] - u[1] * v[0],
198
- ];
95
+ const cross = (u, v) => [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]];
199
96
  const inverse = [cross(b, c), cross(c, a), cross(a, b)];
200
97
  const determinant = a.reduce((sum, v, i) => sum + v * inverse[0][i], 0);
201
- if (Math.abs(determinant) < 0.000001) invalid("singular contour");
202
- return {
203
- base,
204
- vector: (v) =>
205
- [0, 1, 2].map((i) =>
206
- columns.reduce((sum, col, j) => sum + col[i] * v[j], 0),
207
- ),
208
- normal: (v) =>
209
- [0, 1, 2].map(
210
- (i) =>
211
- inverse.reduce((sum, col, j) => sum + col[i] * v[j], 0) / determinant,
212
- ),
213
- };
98
+ if (Math.abs(determinant) < .000001) invalid('singular contour');
99
+ return { base, vector: v => [0, 1, 2].map(i => columns.reduce((sum, col, j) => sum + col[i] * v[j], 0)), normal: v => [0, 1, 2].map(i => inverse.reduce((sum, col, j) => sum + col[i] * v[j], 0) / determinant) };
214
100
  }
215
101
 
216
102
  /** Rigid attachment anchors on the source circle, in the engine's XY units.
@@ -219,10 +105,9 @@ function derivative(map, p) {
219
105
  */
220
106
  export function legacyAttachmentOffset(points, accessory) {
221
107
  let anchor;
222
- if (accessory === "bow") anchor = [0, -1, 0];
223
- else if (accessory === "three_lobe") anchor = [-0.5, Math.sqrt(0.75), 0];
224
- else if (["beanie", "hat", "beret", "orb", "crown"].includes(accessory))
225
- anchor = [0, 1, 0];
108
+ if (accessory === 'bow') anchor = [0, -1, 0];
109
+ else if (accessory === 'three_lobe') anchor = [-.5, Math.sqrt(.75), 0];
110
+ else if (['beanie', 'hat', 'beret', 'orb', 'crown'].includes(accessory)) anchor = [0, 1, 0];
226
111
  else return undefined;
227
112
  return mapper({ points })(...anchor).map((value, i) => value - anchor[i]);
228
113
  }
@@ -235,89 +120,47 @@ export function legacyAttachmentOffset(points, accessory) {
235
120
  * Attachments may explicitly transform a numbered part around an origin.
236
121
  */
237
122
  export function deformLegacyAssembly(bytes, options) {
238
- const info = inspectLegacyAssembly(bytes),
239
- result = bytes.slice();
240
- if (!/^[a-f0-9]{64}$/.test(options.geometryKey || ""))
241
- invalid("missing geometry cache key");
242
- const view = new DataView(result.buffer),
243
- map = mapper(options);
244
- const read = (at) => [0, 1, 2].map((i) => view.getFloat32(at + i * 4, true));
245
- const write = (at, values) =>
246
- values.forEach((v, i) => view.setFloat32(at + i * 4, finite(v), true));
247
- const unit = (v) => {
248
- const n = Math.hypot(...v);
249
- return n > 1e-9 ? v.map((x) => x / n) : v;
250
- };
123
+ const info = inspectLegacyAssembly(bytes), result = bytes.slice();
124
+ if (!/^[a-f0-9]{64}$/.test(options.geometryKey || '')) invalid('missing geometry cache key');
125
+ const view = new DataView(result.buffer), map = mapper(options);
126
+ const read = at => [0, 1, 2].map(i => view.getFloat32(at + i * 4, true));
127
+ const write = (at, values) => values.forEach((v, i) => view.setFloat32(at + i * 4, finite(v), true));
128
+ const unit = v => { const n = Math.hypot(...v); return n > 1e-9 ? v.map(x => x / n) : v; };
251
129
  function transform(part, mapping) {
252
130
  for (let i = 0; i < part.vertexCount; i++) {
253
- const at = part.vertexOffset + i * STRIDE,
254
- p = read(at),
255
- d = derivative(mapping, p);
131
+ const at = part.vertexOffset + i * STRIDE, p = read(at), d = derivative(mapping, p);
256
132
  // The two pose positions are DELTAS: preserve zero as zero and deform the
257
133
  // target position before subtracting the newly transformed base.
258
- for (const offset of [24, 48]) {
259
- const delta = read(at + offset),
260
- target = mapping(...p.map((v, k) => v + delta[k]));
261
- write(
262
- at + offset,
263
- target.map((v, k) => v - d.base[k]),
264
- );
265
- }
266
- for (const offset of [12, 36, 60, 84])
267
- write(at + offset, unit(d.normal(read(at + offset))));
134
+ for (const offset of [24, 48]) { const delta = read(at + offset), target = mapping(...p.map((v, k) => v + delta[k])); write(at + offset, target.map((v, k) => v - d.base[k])); }
135
+ for (const offset of [12, 36, 60, 84]) write(at + offset, unit(d.normal(read(at + offset))));
268
136
  write(at, d.base);
269
137
  }
270
138
  for (let i = 0; i < part.furCount; i++) {
271
- const at = part.furOffset + i * 64,
272
- d = derivative(mapping, read(at));
273
- write(at, d.base);
274
- write(at + 12, unit(d.normal(read(at + 12))));
275
- write(at + 24, unit(d.vector(read(at + 24))));
139
+ const at = part.furOffset + i * 64, d = derivative(mapping, read(at));
140
+ write(at, d.base); write(at + 12, unit(d.normal(read(at + 12)))); write(at + 24, unit(d.vector(read(at + 24))));
276
141
  }
277
142
  result.set(encoder.encode(options.geometryKey), part.nameOffset);
278
143
  }
279
144
  transform(info.body, map);
280
145
  for (const attachment of options.attachments || []) {
281
146
  const part = info.parts[attachment.part];
282
- if (!part || part.part === 0) invalid("attachment index");
283
- const scale = attachment.scale || [1, 1, 1],
284
- origin = attachment.origin || [0, 0, 0],
285
- translate = attachment.translate || [0, 0, 0];
286
- if (
287
- [scale, origin, translate].some(
288
- (v) => v.length !== 3 || v.some((x) => !Number.isFinite(x)),
289
- ) ||
290
- scale.some((x) => x <= 0)
291
- )
292
- invalid("attachment transform");
293
- transform(part, (...p) =>
294
- p.map((v, k) => (v - origin[k]) * scale[k] + origin[k] + translate[k]),
295
- );
147
+ if (!part || part.part === 0) invalid('attachment index');
148
+ const scale = attachment.scale || [1, 1, 1], origin = attachment.origin || [0, 0, 0], translate = attachment.translate || [0, 0, 0];
149
+ if ([scale, origin, translate].some(v => v.length !== 3 || v.some(x => !Number.isFinite(x))) || scale.some(x => x <= 0)) invalid('attachment transform');
150
+ transform(part, (...p) => p.map((v, k) => (v - origin[k]) * scale[k] + origin[k] + translate[k]));
296
151
  // Independent transformed parts must not alias the body's cache identity.
297
152
  const key = options.attachmentKeys?.[attachment.part];
298
- if (!/^[a-f0-9]{64}$/.test(key || "")) invalid("attachment cache key");
153
+ if (!/^[a-f0-9]{64}$/.test(key || '')) invalid('attachment cache key');
299
154
  result.set(encoder.encode(key), part.nameOffset);
300
155
  }
301
156
  // Keep original extents while admitting moved accessories and the new body.
302
- const transformed = [
303
- info.body,
304
- ...(options.attachments || []).map((a) => info.parts[a.part]),
305
- ];
306
- for (const part of transformed)
307
- for (let i = 0; i < part.vertexCount; i++) {
308
- const p = read(part.vertexOffset + i * STRIDE);
309
- for (let k = 0; k < 3; k++) {
310
- view.setFloat32(
311
- 4 + k * 4,
312
- Math.min(view.getFloat32(4 + k * 4, true), p[k]),
313
- true,
314
- );
315
- view.setFloat32(
316
- 16 + k * 4,
317
- Math.max(view.getFloat32(16 + k * 4, true), p[k]),
318
- true,
319
- );
320
- }
157
+ const transformed = [info.body, ...(options.attachments || []).map(a => info.parts[a.part])];
158
+ for (const part of transformed) for (let i = 0; i < part.vertexCount; i++) {
159
+ const p = read(part.vertexOffset + i * STRIDE);
160
+ for (let k = 0; k < 3; k++) {
161
+ view.setFloat32(4 + k * 4, Math.min(view.getFloat32(4 + k * 4, true), p[k]), true);
162
+ view.setFloat32(16 + k * 4, Math.max(view.getFloat32(16 + k * 4, true), p[k]), true);
321
163
  }
164
+ }
322
165
  return result;
323
166
  }
@@ -2,14 +2,6 @@
2
2
  // the recovered engine, packaged meshes and native manifests remain intact.
3
3
  import createModule from './orbit-characters.mjs';
4
4
  import { decodeAppearance, encodeAppearance } from './appearance-codec.mjs';
5
- import { composePresetBody, composeCatalogParts } from './catalog-parts.mjs';
6
- import {
7
- fitAuthoredEyeSpacing,
8
- fitAuthoredHeadwear,
9
- shapeFaceLayout,
10
- authoredAssemblyRecords,
11
- } from './authored-parts.mjs';
12
- import { sha256 } from './sha256.mjs';
13
5
  import {
14
6
  deformLegacyAssembly,
15
7
  inspectLegacyAssembly,
@@ -18,8 +10,13 @@ import {
18
10
  const engine = createModule({
19
11
  locateFile: (file) => new URL(file, import.meta.url).href,
20
12
  });
21
- const digest = (value) =>
22
- sha256(typeof value === 'string' ? new TextEncoder().encode(value) : value);
13
+ const digest = async (value) =>
14
+ Array.from(
15
+ new Uint8Array(
16
+ await crypto.subtle.digest('SHA-256', typeof value === 'string' ? new TextEncoder().encode(value) : value),
17
+ ),
18
+ (byte) => byte.toString(16).padStart(2, '0'),
19
+ ).join('');
23
20
  // Cache only identities, never the large prepared buffers. The same face with
24
21
  // no accessories tells us exactly which meshes belong to eyes and eyewear.
25
22
  const faceIdentities = new Map();
@@ -28,12 +25,8 @@ async function accessoryTransforms(module, data, source) {
28
25
  // The editor selects one accessory. Preserve any unknown multi-accessory
29
26
  // state rather than guessing which of its pieces should move together.
30
27
  if (appearance.accessories.length !== 1) return {};
31
- const translate = legacyAttachmentOffset(
32
- data.points,
33
- appearance.accessories[0],
34
- );
35
- if (!translate || translate.every((value) => Math.abs(value) < 1e-6))
36
- return {};
28
+ const translate = legacyAttachmentOffset(data.points, appearance.accessories[0]);
29
+ if (!translate || translate.every(value => Math.abs(value) < 1e-6)) return {};
37
30
  appearance.accessories = [];
38
31
  appearance.accessoryColors = {};
39
32
  if (appearance.model) appearance.model.accessories = {};
@@ -41,35 +34,22 @@ async function accessoryTransforms(module, data, source) {
41
34
  const key = `${await digest(bytes)}:${data.quality}:${data.activities}`;
42
35
  let names = faceIdentities.get(key);
43
36
  if (!names) {
44
- const base = module.orbitPrepareAssembly(
45
- bytes,
46
- data.quality,
47
- `${data.key}:attachment-base`,
48
- data.activities,
49
- );
50
- if (!base.bytes)
51
- throw new Error(base.error || 'Accessory reference preparation failed');
52
- names = new Set(
53
- inspectLegacyAssembly(base.bytes)
54
- .parts.slice(1)
55
- .map((part) => part.name),
56
- );
37
+ const base = module.orbitPrepareAssembly(bytes, data.quality, `${data.key}:attachment-base`, data.activities);
38
+ if (!base.bytes) throw new Error(base.error || 'Accessory reference preparation failed');
39
+ names = new Set(inspectLegacyAssembly(base.bytes).parts.slice(1).map(part => part.name));
57
40
  faceIdentities.set(key, names);
58
- if (faceIdentities.size > 32)
59
- faceIdentities.delete(faceIdentities.keys().next().value);
41
+ if (faceIdentities.size > 32) faceIdentities.delete(faceIdentities.keys().next().value);
60
42
  }
61
43
  // Do not move anything unless every original face mesh survives unchanged.
62
- const current = new Set(source.parts.slice(1).map((part) => part.name));
63
- if ([...names].some((name) => !current.has(name))) return {};
64
- const attachments = source.parts
65
- .slice(1)
66
- .filter((part) => !names.has(part.name))
67
- .map((part) => ({ part: part.part, translate }));
44
+ const current = new Set(source.parts.slice(1).map(part => part.name));
45
+ if ([...names].some(name => !current.has(name))) return {};
46
+ const attachments = source.parts.slice(1)
47
+ .filter(part => !names.has(part.name))
48
+ .map(part => ({ part: part.part, translate }));
68
49
  const attachmentKeys = {};
69
- for (const attachment of attachments)
70
- attachmentKeys[attachment.part] = await digest(
71
- JSON.stringify([source.parts[attachment.part].name, translate]),
72
- );
50
+ for (const attachment of attachments) attachmentKeys[attachment.part] = await digest(
51
+ JSON.stringify([source.parts[attachment.part].name, translate]),
52
+ );
73
53
  return { attachments, attachmentKeys };
74
54
  }
75
55
  let queue = Promise.resolve();
@@ -83,12 +63,7 @@ self.onmessage = ({ data }) => {
83
63
  data.key,
84
64
  data.activities,
85
65
  );
86
- // Native preparation may return a view of temporary storage. Subsequent
87
- // face/donor reference calls must never overwrite the target scene.
88
- if (result.bytes) result.bytes = result.bytes.slice();
89
- if (result.bytes && data.authoredParts?.bodyPreset)
90
- result.bytes = await composePresetBody(module, result.bytes, data);
91
- if (result.bytes && data.points) {
66
+ if (result.bytes) {
92
67
  const source = inspectLegacyAssembly(result.bytes);
93
68
  const geometryKey = await digest(
94
69
  JSON.stringify([source.body.name, data.points]),
@@ -96,43 +71,9 @@ self.onmessage = ({ data }) => {
96
71
  result.bytes = deformLegacyAssembly(result.bytes, {
97
72
  points: data.points,
98
73
  geometryKey,
99
- ...(await accessoryTransforms(module, data, source)),
74
+ ...await accessoryTransforms(module, data, source),
100
75
  });
101
76
  }
102
- if (result.bytes && data.authoredParts)
103
- data.faceLayout = shapeFaceLayout(
104
- authoredAssemblyRecords(result.bytes),
105
- data,
106
- module,
107
- );
108
- if (
109
- result.bytes &&
110
- (data.authoredParts?.shape === 'clippo' ||
111
- data.authoredParts?.eyes === 'clippo')
112
- ) {
113
- const { composeClippoAssembly } = await import('./clippo-geometry.mjs');
114
- result.bytes = await composeClippoAssembly(module, result.bytes, data);
115
- }
116
- if (result.bytes && data.authoredParts) {
117
- result.bytes = await composeCatalogParts(module, result.bytes, data, [
118
- 'eyes',
119
- 'accessory',
120
- ]);
121
- if (data.authoredParts.eyes === 'cyclops') {
122
- const { composeCyclopsAssembly } =
123
- await import('./cyclops-geometry.mjs');
124
- result.bytes = await composeCyclopsAssembly(
125
- module,
126
- result.bytes,
127
- data,
128
- );
129
- }
130
- result.bytes = await composeCatalogParts(module, result.bytes, data, [
131
- 'eyewear',
132
- ]);
133
- result.bytes = await fitAuthoredEyeSpacing(result.bytes, data);
134
- result.bytes = await fitAuthoredHeadwear(result.bytes, data);
135
- }
136
77
  self.postMessage(
137
78
  { id: data.id, ...result },
138
79
  result.bytes ? [result.bytes.buffer] : [],
@@ -1,10 +1,21 @@
1
1
  import { renderBudget } from './render-budget.mjs';
2
- import { characterPixels } from './resolution.mjs';
3
2
 
4
3
  const snapshots = new Map();
5
4
  const waiting = new Map();
6
- // Presentation lifetime is independent of the rendering lease. Offscreen and
7
- // paused avatars retain real engine pixels until they need rendering again.
5
+ const pixelsFor = (canvas) =>
6
+ Math.max(
7
+ 64,
8
+ Math.min(
9
+ 512,
10
+ Math.round(
11
+ Math.min(canvas.clientWidth, canvas.clientHeight) *
12
+ Math.min(devicePixelRatio || 1, 2),
13
+ ),
14
+ ),
15
+ );
16
+
17
+ // Presentation lifetime is independent of the GPU lease. Overflow avatars keep
18
+ // their real engine pixels and can reclaim a lease through pointer/command input.
8
19
  export function createManagedAvatar(canvas, initial, callbacks, factory) {
9
20
  let props = initial,
10
21
  controller,
@@ -31,7 +42,7 @@ export function createManagedAvatar(canvas, initial, callbacks, factory) {
31
42
  props.config.character,
32
43
  props.legacy,
33
44
  props.config.lookAt,
34
- characterPixels(canvas),
45
+ pixelsFor(canvas),
35
46
  ]);
36
47
  const priority = () =>
37
48
  pendingWork || pendingReaction || performance.now() < urgentUntil
@@ -46,6 +57,7 @@ export function createManagedAvatar(canvas, initial, callbacks, factory) {
46
57
  visible: (visible || props.portrait) && !document.hidden,
47
58
  still: still(),
48
59
  priority: still() ? 0 : priority(),
60
+ interactive: !!props.interactive,
49
61
  needsPaint: !ready,
50
62
  });
51
63
  function promote(duration = 5000) {
@@ -56,8 +68,7 @@ export function createManagedAvatar(canvas, initial, callbacks, factory) {
56
68
  refresh();
57
69
  }
58
70
  function commands() {
59
- const state = controller?.diagnostics?.();
60
- if (disposed || still() || !ready || !state?.ready || state.pending) return;
71
+ if (!controller?.diagnostics?.()?.ready) return;
61
72
  if (pendingWork)
62
73
  promote(
63
74
  2500 * Math.max(1, Math.min(10, props.workingCycles || 1)) + 1000,
@@ -108,13 +119,9 @@ export function createManagedAvatar(canvas, initial, callbacks, factory) {
108
119
  for (const receive of waiting.get(cacheKey) ?? []) receive();
109
120
  }
110
121
  callbacks.onPaint?.();
111
- // Scope replacements and in-place edits may settle without another
112
- // onReady event. Deliver retained commands only after their paint.
113
- if (pendingWork || pendingReaction) queueMicrotask(commands);
114
122
  },
115
123
  });
116
124
  controller = created;
117
- if (!disposed) callbacks.onPreparationStart?.();
118
125
  created.update(props, true);
119
126
  return created;
120
127
  } finally {
@@ -172,8 +179,7 @@ export function createManagedAvatar(canvas, initial, callbacks, factory) {
172
179
  pendingReaction = false;
173
180
  }
174
181
  appearance();
175
- const state = controller?.diagnostics?.();
176
- const queueCommands = !ready || !state?.ready || !!state.pending;
182
+ const queueCommands = !controller?.diagnostics?.()?.ready;
177
183
  if (!still() && (work || reaction)) {
178
184
  if (queueCommands) {
179
185
  pendingWork ||= work;