@oxy.so/bloom 7.1.3 → 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
@@ -2,9 +2,7 @@
2
2
 
3
3
  Serve this directory unchanged from a static URL and pass runtime.mjs's URL to
4
4
  AgentAvatarProvider. The runtime loads lazily when an avatar becomes visible.
5
- Serve .mjs as JavaScript and .wasm as application/wasm. Version the static
6
- directory as a unit: changing only the runtime.mjs query does not invalidate
7
- its relative imports under immutable HTTP caching.
5
+ Serve .mjs as JavaScript and .wasm as application/wasm.
8
6
 
9
7
  The four generated engine files are preserved byte for byte from the supplied
10
8
  local beta. integrity.json records their SHA-256 hashes. The complete supplied
@@ -16,97 +14,18 @@ runtime.mjs is an adapter for the original engine, retaining its appearance,
16
14
  materials and animations. It owns loading, selection, input, display scaling and
17
15
  resource lifetime. There is no replacement mesh renderer in the product.
18
16
 
19
- All visible moving characters share one physical WebGL2 context, a packed canvas,
20
- and one animation clock with a 30fps per-character target. Each character retains its own state and private
21
- render targets. shared-surface.mjs maps its default framebuffer into a tile,
22
- resolves MSAA before translation, and clips output copies to that tile. Only the
23
- last lease destroys the physical context. shared-programs.mjs pools shaders and
24
- linked programs, preserving independent uniforms and Emscripten object metadata.
25
- Identical uniform values are not resent when changing logical program owners.
26
- shared-textures.mjs shares the exact bytes and parameters of the two 512px
27
- mipmapped RGBA8 assets and the 576×960 RGBA8 atlas. Mutation or framebuffer
28
- attachment first gives that logical texture independent storage. Other textures,
29
- mutable buffers and private render targets are not deduplicated. Small avatars
30
- use compact meshes independent of device pixel ratio. Fur and original materials
31
- remain intact. resolution.mjs selects backing size by CSS size and DPR, activating
32
- the original engine's existing size-dependent fur LOD.
17
+ Beta characters share one engine per document and all avatars share a 30fps
18
+ clock. Legacy contours isolate rendering modules to keep native geometry caches
19
+ from returning another silhouette; one worker prepares their scenes. Unused
20
+ modules are released. Static portrait preparation is serialized, cached (up to 64 images), and
21
+ releases its graphics surfaces after painting. Offscreen/background characters
22
+ stop rendering; pause and reduced motion freeze a complete visible frame. The
23
+ binary's initial reduced-motion flag can produce transparent frames; the adapter
24
+ freezes its own clock after painting instead.
33
25
 
34
- Physical Pixel 8a measurements and exact asset fingerprints are recorded in
35
- docs/benchmarks/avatar-continuous-renderer.json (same-character controllers and
36
- pose transitions, before shared authored parts). All 24/48 avatars move with
37
- one context and no graphics errors. Median paint rates are 4.42/2.29 Hz per avatar;
38
- all-ready latency is 14.85/24.80 seconds with warm HTTP caches. The 30 fps target
39
- is not achieved. Earlier snapshots are retained for comparison; see
40
- docs/agent-avatar.mdx for workload and measurement limitations.
41
-
42
- Original and migrated contours share a rendering WASM module and one preparation
43
- worker, which owns a separate WASM instance. Geometry identities include contour and attachment transforms. The adapter
44
- scopes the recovered engine's deferred preparation dispatcher to its character;
45
- that boundary is specific to the integrity-pinned glue. Its native global batch
46
- can combine jobs from different characters, so wrapping callbacks alone is not
47
- sufficient: the preparation lease spans mutation, deferred jobs and first paint. Cold preparation is
48
- serialized; painted moving neighbours remain resident. Static portraits are
49
- cached (up to 64 images), and release graphics surfaces after painting. Exact contour preparations use an independent 8 MiB/16-entry LRU cache, including appearance, quality, activity and contour and authored-part identity; returned buffers are copied. The preparation owner receives a render turn every batch until its first complete paint. Recent interactions receive alternate-batch priority without starving the remaining round-robin queue.
50
- Offscreen/background characters release their leases. Pause and reduced motion
51
- freeze a complete visible frame. The binary's initial reduced-motion flag can
52
- produce transparent frames; the adapter freezes its clock after painting instead.
53
-
54
- Shared submissions contain at most eight avatars and an 8 ms CPU allowance. A WebGL fence is polled asynchronously before error checks and presentation, allowing the page to process input while the GPU works. Lifecycle and native input operations wait for this boundary; DOM pointer capture remains synchronous. Diagnostics separate CPU submission/presentation from elapsed GPU waiting and task dispatch. This improves responsiveness without claiming faster GPU throughput.
55
-
56
- React and Work retain one native Character, its prepared meshes and animation
57
- clock. controller-mode.mjs verifies the original WASM SHA-256 before capturing
58
- its memory through the supported instantiateWasm hook. A JS hash fallback keeps
59
- this check working on insecure HTTP WebView origins. The adapter validates the
60
- Embind handle, native type marker, live instance, bounds and boolean controller
61
- field on every access; memory growth never leaves a stale typed view. Its ABI
62
- layout is specific to the pinned WASM and must be re-proved for another binary.
63
- Activity support prepares the original props up front. A constructor without
64
- activity support rejects Restore/Start; incremental preparation is not assumed.
65
- Stop completion reads the original current phase/activity/episode, since both
66
- copyActivityRestore and hasPendingUpdate can misrepresent outro completion.
67
- Appearance changes retain any simultaneous Work/React request until the new
68
- scene is painted; pausing or disposal cancels those requests.
69
- Queued reactions retain the original Busy semantics until the suspended reaction
70
- finishes. verify-avatar-controller-continuity.mjs checks the complete runtime
71
- switches, native identity, unchanged geometry, original outro and real pixels.
72
- pose-transition.mjs interpolates reflected transform/skin uniform blocks and
73
- camera for 180 ms from the last presented pose. It preserves material/discrete
74
- bytes and original animation of newly added props. No bitmap crossfade or
75
- replacement mesh renderer is involved.
76
-
77
- authored-parts.mjs exposes eyes:todd and accessory:felipe_beret across all 21
78
- shapes. The worker copies complete native records from lime_frog and blue_beret,
79
- retaining materials and animation deltas, fits them after contour deformation,
80
- and removes the generic fitting references. Original activity data remains intact; the aggregate scene bounds expand to
81
- include the fitted parts. Virtual IDs never reach native catalog selection. Cache keys include
82
- these parts; verify-avatar-authored-parts.mjs checks all 21 real rendered shapes,
83
- original/migrated Work and React, generic selections and cleanup.
84
-
85
- clippo-geometry.mjs adds a smooth open tube and an independently selectable
86
- Clippo eye/brow set as native prepared records. It generates capped tube meshes,
87
- retains the original eye deformation fields and uses the recovered smooth
88
- material in the same renderer. No raster cutout or additional WebGL context is
89
- used. character-recipe.mjs resolves the clippo preset to its virtual shape/eyes
90
- and grey paint; explicit selections override those defaults. Runtime capabilities
91
- retain the caller's raw saved key, including when an explicit default selection
92
- leaves the prepared appearance unchanged. Editor chips are static SVG artwork.
93
-
94
- catalog-parts.mjs fits actual original eye, eyewear and accessory meshes across
95
- all bodies. Named presets supply editable defaults; changing a part retains the
96
- original body. Opaque sunglasses recover their hidden-eye fitting references
97
- without replacing the body. Donor records have an 8 MiB/24-entry bounded cache.
98
- verify-avatar-catalog-parts.mjs checks 312 combinations, exact requested mesh
99
- labels, preserved body vertices and the cache bound.
100
-
101
- cyclops-geometry.mjs adds one native white eyeball, a teal iris, pupil and glint;
102
- its layered meshes retain native blink/look deformation. Each shape owns eye
103
- positions and size, shared by every eye style; historical character.eyeSpacing
104
- values are ignored. Todd's actual body is also independently selectable
105
- as shape:todd. verify-avatar-cyclops.mjs checks actual eye colors, movement and
106
- cleanup; verify-avatar-customization.mjs exercises all eye styles on Todd in the
107
- real editor, shape changes and accessories. appearance-codec.mjs
108
- validates explicit RGB through the original engine. Untouched presets keep their
109
- bytes; unnamed original paint is reported from its actual material.
26
+ Current editing supports the beta's original catalog combinations and actual
27
+ availability constraints. appearance-codec.mjs validates explicit RGB and optional eye
28
+ transform edits through the original engine. Untouched presets keep their bytes.
110
29
 
111
30
  legacy-geometry.mjs transforms copies of prepared scene assemblies: existing
112
31
  Bloom contours deform the body, pose deltas, normals and fur roots. The packaged
@@ -120,31 +39,17 @@ diamond and heart recipes reuse the original beta shapes instead of duplicates.
120
39
  Alien avatars are removed from the visible catalog, with saved-data compatibility. The
121
40
  migrated bodies use the original catalog eyes at their authored proportions and
122
41
  animations; old expression/eye transform fields no longer alter the 3D face.
123
- All visible bodies accept native catalog eyes, eyewear and accessories through
124
- original donor assemblies. Fitting uses the actual target face/crown anchors;
125
- Clippo headwear is scaled to its narrow crown. A bounded reference cache retains
126
- original material/animation data, and spacing moves the eye groups without
127
- stretching lenses. `verify-avatar-catalog-parts.mjs` checks all 312 native
128
- body/part combinations, exact requested labels, unchanged body vertices and
129
- cache bounds without creating a WebGL context. Intact presets retain authored
130
- signature reactions. Independent piece edits retain the body's original
131
- signature. Native shapes use their corresponding authored signature; distinct
132
- migrated contours and Clippo use Todd's signature as the common default.
133
- The adapter never substitutes the original Wave/maracas reaction. A pinned,
134
- validated controller scope borrows only the reaction identity during the original
135
- native dispatch and restores it immediately; saved appearance and materials
136
- remain independent. Named bodies also retain their original fitted Work payload.
42
+ Migrated bodies support original eyewear and accessories. Headwear, Bulb, bow
43
+ and Tuft receive rigid contour-relative translations; eyewear and headphones
44
+ keep original placement. A bounded reference cache identifies attachment meshes
45
+ without changing eyes, materials or fur vectors. Intact presets retain authored
46
+ signature reactions; edited and migrated bodies fall back to the original Wave.
137
47
  Mascots and hidden faces still use SVG.
138
48
  Original material/grain effects become the beta's 3D surface.
139
49
 
140
50
  Browser gates (with Storybook running) are scripts/verify-character-avatars.mjs
141
- and scripts/verify-legacy-avatars.mjs, plus verify-avatar-render-budget.mjs and
142
- verify-avatar-shared-renderer.mjs for crowd lifetime and actual 24/48-avatar motion.
143
- verify-avatar-shared-surface.mjs checks atlas pixels, MSAA and failed allocation;
144
- verify-avatar-shared-programs.mjs and verify-avatar-shared-textures.mjs compare
145
- actual frame pixels against the original commands and verify independent changes.
146
- verify-avatar-runtime-errors.mjs injects a GL failure before publication. verify-avatar-deferred-input.mjs forces a delayed GPU fence and checks real mouse capture, reactions, appearance edits, resizing and disposal during that wait. They inspect painted pixels, interaction,
51
+ and scripts/verify-legacy-avatars.mjs. They inspect painted pixels, interaction,
147
52
  activity, transitions and resource cleanup, not just preparation callbacks.
148
53
 
149
54
  The optional native bridge uses separate WebViews, which cannot share the web
150
- engine/context caches. A physical Android WebView is checked separately from the native multi-WebView bridge.
55
+ engine/context caches. Physical-device validation remains pending.
@@ -1,429 +1,76 @@
1
- import {
2
- NATIVE_PARTS,
3
- ORIGINAL_PRESETS,
4
- MIGRATED_SHAPE_IDS,
5
- } from './character-recipe.mjs';
6
1
  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';
13
2
 
14
- let controller;
15
3
  const pool = new Map();
16
- const authoredFacades = new Map();
17
4
  const pending = new Map();
18
- const prepared = createPreparationCache();
19
- const originalScope = Object.freeze({ points: undefined });
20
- let modulePromise,
21
- originalFacade,
22
- currentScope,
23
- worker,
5
+ let worker,
24
6
  idleTimer,
25
- characters = 0,
26
7
  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
-
149
8
  function preparationWorker() {
150
- clearTimeout(idleTimer);
151
9
  if (worker) return worker;
152
10
  worker = new Worker(new URL('./legacy-worker.mjs', import.meta.url), {
153
11
  type: 'module',
154
12
  });
155
13
  worker.onmessage = ({ data }) => {
156
- void finishRequest(data.id, data);
14
+ const entry = pending.get(data.id);
15
+ if (!entry) return;
16
+ pending.delete(data.id);
17
+ entry.complete(data);
157
18
  };
158
19
  worker.onerror = () => {
159
- const failed = [...pending.keys()];
20
+ const failed = [...pending.values()];
21
+ pending.clear();
160
22
  worker?.terminate();
161
23
  worker = undefined;
162
- for (const id of failed)
163
- void finishRequest(id, {
164
- error: 'Character preparation failed',
24
+ for (const entry of failed)
25
+ entry.complete({
26
+ error: 'Legacy character preparation failed',
165
27
  milliseconds: 0,
166
28
  });
167
29
  };
168
30
  return worker;
169
31
  }
170
32
 
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) {
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) => {
193
50
  module.orbitCompletePreparation(
194
51
  id,
195
- null,
196
- 'Character preparation requires a scoped call',
197
- 0,
52
+ result.bytes ?? null,
53
+ result.error || '',
54
+ result.milliseconds || 0,
198
55
  );
199
- return;
200
- }
201
- const request = ++sequence;
202
- const cacheKeyExact = preparationKey({
56
+ },
57
+ });
58
+ try {
59
+ preparationWorker().postMessage({
60
+ id: request,
203
61
  appearance,
204
62
  quality,
205
63
  key: cacheKey,
206
64
  activities,
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;
65
+ points,
294
66
  });
295
- return methods.get(key);
296
- },
67
+ } catch (error) {
68
+ pending.delete(request);
69
+ module.orbitCompletePreparation(id, null, String(error), 0);
70
+ }
71
+ };
72
+ return module;
297
73
  });
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
- };
427
74
  pool.set(key, entry);
428
75
  }
429
76
  entry.references++;
@@ -431,11 +78,20 @@ export async function acquireLegacyEngine(points, parts) {
431
78
  const release = () => {
432
79
  if (released) return;
433
80
  released = true;
434
- if (--entry.references === 0) pool.delete(key);
435
- stopIdleWorker();
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);
436
92
  };
437
93
  try {
438
- return { module: await entry.facade, release };
94
+ return { module: await entry.module, release };
439
95
  } catch (error) {
440
96
  release();
441
97
  throw error;
@@ -443,15 +99,7 @@ export async function acquireLegacyEngine(points, parts) {
443
99
  }
444
100
 
445
101
  export const legacyEngineStats = () => ({
446
- // Historical field counts leased contour entries; sharedModules is the
447
- // actual WASM count, including the instance used by original characters.
448
102
  modules: pool.size,
449
- sharedModules: modulePromise ? 1 : 0,
450
- characters,
451
- preparationWaiters: preparationQueue.length,
452
- preparationActive: !!activePreparation,
453
- dispatchTimers: dispatchTimers.size,
454
103
  pending: pending.size,
455
104
  worker: !!worker,
456
- preparationCache: prepared.stats(),
457
105
  });