@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.
- package/assets/character-runtime/README.md +114 -19
- package/assets/character-runtime/authored-parts.mjs +754 -0
- package/assets/character-runtime/catalog-parts.mjs +362 -0
- package/assets/character-runtime/character-recipe.mjs +247 -0
- package/assets/character-runtime/clippo-geometry.mjs +679 -0
- package/assets/character-runtime/controller-mode.mjs +208 -0
- package/assets/character-runtime/cyclops-geometry.mjs +180 -0
- package/assets/character-runtime/legacy-engine.mjs +406 -54
- package/assets/character-runtime/legacy-geometry.mjs +242 -85
- package/assets/character-runtime/legacy-worker.mjs +82 -23
- package/assets/character-runtime/managed-avatar.mjs +12 -17
- package/assets/character-runtime/migrated-contours.mjs +2067 -0
- package/assets/character-runtime/migrated-faces.mjs +59 -0
- package/assets/character-runtime/pose-transition.mjs +523 -0
- package/assets/character-runtime/preparation-cache.mjs +60 -0
- package/assets/character-runtime/render-budget.mjs +5 -3
- package/assets/character-runtime/render-errors.mjs +180 -0
- package/assets/character-runtime/resolution.mjs +10 -0
- package/assets/character-runtime/runtime.mjs +744 -295
- package/assets/character-runtime/sha256.mjs +44 -0
- package/assets/character-runtime/shared-programs.mjs +534 -0
- package/assets/character-runtime/shared-surface.mjs +490 -0
- package/assets/character-runtime/shared-textures.mjs +420 -0
- package/assets/character-runtime/thumbnails/eyes/todd.png +0 -0
- package/docs/agent-avatar.mdx +45 -8
- package/docs/agent-creator.mdx +13 -3
- package/lib/commonjs/agent-avatar/AgentAvatar.js +1 -1
- package/lib/commonjs/agent-avatar/AgentAvatar.js.map +1 -1
- package/lib/commonjs/agent-avatar/CharacterAvatar.js +2 -1
- package/lib/commonjs/agent-avatar/CharacterAvatar.js.map +1 -1
- package/lib/commonjs/agent-avatar/character-colors.js +19 -0
- package/lib/commonjs/agent-avatar/character-colors.js.map +1 -0
- package/lib/commonjs/agent-avatar/character-shapes.js +106 -0
- package/lib/commonjs/agent-avatar/character-shapes.js.map +1 -0
- package/lib/commonjs/agent-avatar/config-character.js +4 -0
- package/lib/commonjs/agent-avatar/config-character.js.map +1 -1
- package/lib/commonjs/agent-avatar/legacy-recipe.js +19 -6
- package/lib/commonjs/agent-avatar/legacy-recipe.js.map +1 -1
- package/lib/commonjs/agent-creator/AgentCreatorBase.js +67 -107
- package/lib/commonjs/agent-creator/AgentCreatorBase.js.map +1 -1
- package/lib/commonjs/agent-creator/CharacterControls.js +23 -5
- package/lib/commonjs/agent-creator/CharacterControls.js.map +1 -1
- package/lib/commonjs/agent-creator/ClippoArtwork.js +63 -0
- package/lib/commonjs/agent-creator/ClippoArtwork.js.map +1 -0
- package/lib/commonjs/agent-creator/CyclopsArtwork.js +45 -0
- package/lib/commonjs/agent-creator/CyclopsArtwork.js.map +1 -0
- package/lib/commonjs/agent-creator/EmotionPicker.js +8 -1
- package/lib/commonjs/agent-creator/EmotionPicker.js.map +1 -1
- package/lib/commonjs/agent-creator/ShapeSilhouette.js +8 -1
- package/lib/commonjs/agent-creator/ShapeSilhouette.js.map +1 -1
- package/lib/commonjs/agent-creator/ToddArtwork.js +29 -0
- package/lib/commonjs/agent-creator/ToddArtwork.js.map +1 -0
- package/lib/commonjs/agent-creator/constants.js +16 -17
- package/lib/commonjs/agent-creator/constants.js.map +1 -1
- package/lib/commonjs/agent-creator/messages.js +1 -1
- package/lib/commonjs/agent-creator/messages.js.map +1 -1
- package/lib/commonjs/locale/translations/ar.js +1 -1
- package/lib/commonjs/locale/translations/ar.js.map +1 -1
- package/lib/commonjs/locale/translations/bn.js +1 -1
- package/lib/commonjs/locale/translations/bn.js.map +1 -1
- package/lib/commonjs/locale/translations/ca.js +1 -1
- package/lib/commonjs/locale/translations/ca.js.map +1 -1
- package/lib/commonjs/locale/translations/de.js +1 -1
- package/lib/commonjs/locale/translations/de.js.map +1 -1
- package/lib/commonjs/locale/translations/es.js +1 -1
- package/lib/commonjs/locale/translations/es.js.map +1 -1
- package/lib/commonjs/locale/translations/fr.js +1 -1
- package/lib/commonjs/locale/translations/fr.js.map +1 -1
- package/lib/commonjs/locale/translations/hi.js +1 -1
- package/lib/commonjs/locale/translations/hi.js.map +1 -1
- package/lib/commonjs/locale/translations/id.js +1 -1
- package/lib/commonjs/locale/translations/id.js.map +1 -1
- package/lib/commonjs/locale/translations/it.js +1 -1
- package/lib/commonjs/locale/translations/it.js.map +1 -1
- package/lib/commonjs/locale/translations/ja.js +1 -1
- package/lib/commonjs/locale/translations/ja.js.map +1 -1
- package/lib/commonjs/locale/translations/pt.js +1 -1
- package/lib/commonjs/locale/translations/pt.js.map +1 -1
- package/lib/commonjs/locale/translations/ru.js +1 -1
- package/lib/commonjs/locale/translations/ru.js.map +1 -1
- package/lib/commonjs/locale/translations/tr.js +1 -1
- package/lib/commonjs/locale/translations/tr.js.map +1 -1
- package/lib/commonjs/locale/translations/zh.js +1 -1
- package/lib/commonjs/locale/translations/zh.js.map +1 -1
- package/lib/module/agent-avatar/AgentAvatar.js +1 -1
- package/lib/module/agent-avatar/AgentAvatar.js.map +1 -1
- package/lib/module/agent-avatar/CharacterAvatar.js +2 -1
- package/lib/module/agent-avatar/CharacterAvatar.js.map +1 -1
- package/lib/module/agent-avatar/character-colors.js +15 -0
- package/lib/module/agent-avatar/character-colors.js.map +1 -0
- package/lib/module/agent-avatar/character-shapes.js +97 -0
- package/lib/module/agent-avatar/character-shapes.js.map +1 -0
- package/lib/module/agent-avatar/config-character.js +4 -0
- package/lib/module/agent-avatar/config-character.js.map +1 -1
- package/lib/module/agent-avatar/legacy-recipe.js +19 -6
- package/lib/module/agent-avatar/legacy-recipe.js.map +1 -1
- package/lib/module/agent-creator/AgentCreatorBase.js +69 -109
- package/lib/module/agent-creator/AgentCreatorBase.js.map +1 -1
- package/lib/module/agent-creator/CharacterControls.js +24 -6
- package/lib/module/agent-creator/CharacterControls.js.map +1 -1
- package/lib/module/agent-creator/ClippoArtwork.js +59 -0
- package/lib/module/agent-creator/ClippoArtwork.js.map +1 -0
- package/lib/module/agent-creator/CyclopsArtwork.js +41 -0
- package/lib/module/agent-creator/CyclopsArtwork.js.map +1 -0
- package/lib/module/agent-creator/EmotionPicker.js +8 -1
- package/lib/module/agent-creator/EmotionPicker.js.map +1 -1
- package/lib/module/agent-creator/ShapeSilhouette.js +8 -1
- package/lib/module/agent-creator/ShapeSilhouette.js.map +1 -1
- package/lib/module/agent-creator/ToddArtwork.js +25 -0
- package/lib/module/agent-creator/ToddArtwork.js.map +1 -0
- package/lib/module/agent-creator/constants.js +8 -16
- package/lib/module/agent-creator/constants.js.map +1 -1
- package/lib/module/agent-creator/messages.js +1 -1
- package/lib/module/agent-creator/messages.js.map +1 -1
- package/lib/module/locale/translations/ar.js +1 -1
- package/lib/module/locale/translations/ar.js.map +1 -1
- package/lib/module/locale/translations/bn.js +1 -1
- package/lib/module/locale/translations/bn.js.map +1 -1
- package/lib/module/locale/translations/ca.js +1 -1
- package/lib/module/locale/translations/ca.js.map +1 -1
- package/lib/module/locale/translations/de.js +1 -1
- package/lib/module/locale/translations/de.js.map +1 -1
- package/lib/module/locale/translations/es.js +1 -1
- package/lib/module/locale/translations/es.js.map +1 -1
- package/lib/module/locale/translations/fr.js +1 -1
- package/lib/module/locale/translations/fr.js.map +1 -1
- package/lib/module/locale/translations/hi.js +1 -1
- package/lib/module/locale/translations/hi.js.map +1 -1
- package/lib/module/locale/translations/id.js +1 -1
- package/lib/module/locale/translations/id.js.map +1 -1
- package/lib/module/locale/translations/it.js +1 -1
- package/lib/module/locale/translations/it.js.map +1 -1
- package/lib/module/locale/translations/ja.js +1 -1
- package/lib/module/locale/translations/ja.js.map +1 -1
- package/lib/module/locale/translations/pt.js +1 -1
- package/lib/module/locale/translations/pt.js.map +1 -1
- package/lib/module/locale/translations/ru.js +1 -1
- package/lib/module/locale/translations/ru.js.map +1 -1
- package/lib/module/locale/translations/tr.js +1 -1
- package/lib/module/locale/translations/tr.js.map +1 -1
- package/lib/module/locale/translations/zh.js +1 -1
- package/lib/module/locale/translations/zh.js.map +1 -1
- package/lib/typescript/commonjs/agent-avatar/CharacterAvatar.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts +13 -0
- package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts.map +1 -0
- package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts +19 -0
- package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts.map +1 -0
- package/lib/typescript/commonjs/agent-avatar/config-character.d.ts +4 -0
- package/lib/typescript/commonjs/agent-avatar/config-character.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts +2 -10
- package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/AgentCreatorBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts +2 -1
- package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts +6 -0
- package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts.map +1 -0
- package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts +5 -0
- package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts.map +1 -0
- package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts +1 -0
- package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/ShapeSilhouette.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts +6 -0
- package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts.map +1 -0
- package/lib/typescript/commonjs/agent-creator/constants.d.ts +7 -15
- package/lib/typescript/commonjs/agent-creator/constants.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/messages.d.ts.map +1 -1
- package/lib/typescript/module/agent-avatar/CharacterAvatar.d.ts.map +1 -1
- package/lib/typescript/module/agent-avatar/character-colors.d.ts +13 -0
- package/lib/typescript/module/agent-avatar/character-colors.d.ts.map +1 -0
- package/lib/typescript/module/agent-avatar/character-shapes.d.ts +19 -0
- package/lib/typescript/module/agent-avatar/character-shapes.d.ts.map +1 -0
- package/lib/typescript/module/agent-avatar/config-character.d.ts +4 -0
- package/lib/typescript/module/agent-avatar/config-character.d.ts.map +1 -1
- package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts +2 -10
- package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/AgentCreatorBase.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/CharacterControls.d.ts +2 -1
- package/lib/typescript/module/agent-creator/CharacterControls.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts +6 -0
- package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts.map +1 -0
- package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts +5 -0
- package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts.map +1 -0
- package/lib/typescript/module/agent-creator/EmotionPicker.d.ts +1 -0
- package/lib/typescript/module/agent-creator/EmotionPicker.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/ShapeSilhouette.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/ToddArtwork.d.ts +6 -0
- package/lib/typescript/module/agent-creator/ToddArtwork.d.ts.map +1 -0
- package/lib/typescript/module/agent-creator/constants.d.ts +7 -15
- package/lib/typescript/module/agent-creator/constants.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/messages.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agent-avatar/AgentAvatar.tsx +1 -1
- package/src/agent-avatar/CharacterAvatar.tsx +4 -1
- package/src/agent-avatar/character-colors.ts +12 -0
- package/src/agent-avatar/character-shapes.ts +122 -0
- package/src/agent-avatar/config-character.ts +39 -6
- package/src/agent-avatar/legacy-recipe.ts +34 -6
- package/src/agent-creator/AgentCreatorBase.tsx +121 -152
- package/src/agent-creator/CharacterControls.tsx +39 -14
- package/src/agent-creator/ClippoArtwork.tsx +49 -0
- package/src/agent-creator/CyclopsArtwork.tsx +27 -0
- package/src/agent-creator/EmotionPicker.tsx +55 -16
- package/src/agent-creator/ShapeSilhouette.tsx +16 -1
- package/src/agent-creator/ToddArtwork.tsx +24 -0
- package/src/agent-creator/constants.ts +47 -14
- package/src/agent-creator/messages.ts +2 -1
- package/src/locale/translations/ar.ts +1 -1
- package/src/locale/translations/bn.ts +1 -1
- package/src/locale/translations/ca.ts +1 -1
- package/src/locale/translations/de.ts +1 -1
- package/src/locale/translations/es.ts +1 -1
- package/src/locale/translations/fr.ts +1 -1
- package/src/locale/translations/hi.ts +1 -1
- package/src/locale/translations/id.ts +1 -1
- package/src/locale/translations/it.ts +1 -1
- package/src/locale/translations/ja.ts +1 -1
- package/src/locale/translations/pt.ts +1 -1
- package/src/locale/translations/ru.ts +1 -1
- package/src/locale/translations/tr.ts +1 -1
- package/src/locale/translations/zh.ts +1 -1
- package/src/__tests__/support/adoption-matrix.ts +0 -683
- package/src/__tests__/support/card-surface.ts +0 -25
- package/src/__tests__/support/collision-fixture-barrel.ts +0 -20
- package/src/__tests__/support/commerce-harness.tsx +0 -97
- package/src/__tests__/support/composite.ts +0 -68
- package/src/__tests__/support/constructed-style-sheets.ts +0 -68
- package/src/__tests__/support/messages-in.ts +0 -10
- package/src/__tests__/support/press-host.ts +0 -30
- package/src/__tests__/support/rendered-style.ts +0 -99
- package/src/__tests__/support/unread-hook-fixture.ts +0 -33
- package/src/__tests__/support/worklet-capture-fixture.tsx +0 -28
- package/src/agent-avatar/__tests__/artwork-golden.json +0 -774
- package/src/agent-creator/__tests__/support/character-runtime-fixture.tsx +0 -26
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +0 -9346
- package/src/theme/__tests__/__fixtures__/tonal-glass-primary-failures.json +0 -69
- package/src/theme/__tests__/fixtures/color-engine-golden.json +0 -1
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
// Exact-content sharing for the original engine's two immutable 512px assets
|
|
2
|
+
// and its 576x960 RGBA8 atlas. Only these observed storage layouts are admitted.
|
|
3
|
+
// All other textures retain independent native storage. Render targets never
|
|
4
|
+
// share. Logical objects keep Emscripten's mutable metadata (.name) independent.
|
|
5
|
+
export function createSharedTexturePool(gl) {
|
|
6
|
+
const textures = new WeakMap(),
|
|
7
|
+
physicalEntries = new WeakMap(),
|
|
8
|
+
live = new Set(),
|
|
9
|
+
entries = new Set();
|
|
10
|
+
const cache = new Map(),
|
|
11
|
+
bindings = new Map(),
|
|
12
|
+
methods = new Map();
|
|
13
|
+
const unpackNames = [
|
|
14
|
+
'UNPACK_ALIGNMENT',
|
|
15
|
+
'UNPACK_FLIP_Y_WEBGL',
|
|
16
|
+
'UNPACK_PREMULTIPLY_ALPHA_WEBGL',
|
|
17
|
+
'UNPACK_COLORSPACE_CONVERSION_WEBGL',
|
|
18
|
+
'UNPACK_ROW_LENGTH',
|
|
19
|
+
'UNPACK_IMAGE_HEIGHT',
|
|
20
|
+
'UNPACK_SKIP_PIXELS',
|
|
21
|
+
'UNPACK_SKIP_ROWS',
|
|
22
|
+
'UNPACK_SKIP_IMAGES',
|
|
23
|
+
];
|
|
24
|
+
const unpack = new Map(
|
|
25
|
+
unpackNames.map((name) => [gl[name], gl.getParameter(gl[name])]),
|
|
26
|
+
);
|
|
27
|
+
let unit = gl.getParameter(gl.ACTIVE_TEXTURE),
|
|
28
|
+
unpackBuffer = null,
|
|
29
|
+
disposed = false;
|
|
30
|
+
let hits = 0,
|
|
31
|
+
copies = 0;
|
|
32
|
+
const key = (target) => `${unit}:${target}`;
|
|
33
|
+
const current = (target) => bindings.get(key(target));
|
|
34
|
+
function equalBytes(a, b) {
|
|
35
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
36
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
function removeCache(entry) {
|
|
40
|
+
if (!entry.key) return;
|
|
41
|
+
const bucket = cache.get(entry.key);
|
|
42
|
+
bucket?.delete(entry);
|
|
43
|
+
if (!bucket?.size) cache.delete(entry.key);
|
|
44
|
+
entry.key = null;
|
|
45
|
+
entry.snapshot = null;
|
|
46
|
+
}
|
|
47
|
+
function release(entry, record) {
|
|
48
|
+
entry.refs.delete(record);
|
|
49
|
+
if (entry.refs.size) return;
|
|
50
|
+
removeCache(entry);
|
|
51
|
+
entries.delete(entry);
|
|
52
|
+
physicalEntries.delete(entry.real);
|
|
53
|
+
gl.deleteTexture(entry.real);
|
|
54
|
+
}
|
|
55
|
+
function own(real, record) {
|
|
56
|
+
const entry = { real, refs: new Set([record]), key: null, snapshot: null };
|
|
57
|
+
entries.add(entry);
|
|
58
|
+
physicalEntries.set(real, entry);
|
|
59
|
+
return entry;
|
|
60
|
+
}
|
|
61
|
+
function rebind(record) {
|
|
62
|
+
for (const [binding, value] of bindings) {
|
|
63
|
+
if (value !== record) continue;
|
|
64
|
+
const [at, target] = binding.split(':').map(Number);
|
|
65
|
+
gl.activeTexture(at);
|
|
66
|
+
gl.bindTexture(target, record.entry.real);
|
|
67
|
+
}
|
|
68
|
+
gl.activeTexture(unit);
|
|
69
|
+
}
|
|
70
|
+
function makePrivate(record) {
|
|
71
|
+
if (!record || record.deleted) return;
|
|
72
|
+
const previous = record.entry;
|
|
73
|
+
if (previous.refs.size === 1) {
|
|
74
|
+
removeCache(previous);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Only a completed, fully captured candidate can have multiple owners.
|
|
78
|
+
const previousBinding = gl.getParameter(gl.TEXTURE_BINDING_2D);
|
|
79
|
+
const real = gl.createTexture();
|
|
80
|
+
gl.bindTexture(record.target, real);
|
|
81
|
+
gl.texStorage2D(...record.storage);
|
|
82
|
+
if (unpackBuffer) gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
|
|
83
|
+
for (const [parameter, value] of record.uploadUnpack)
|
|
84
|
+
gl.pixelStorei(parameter, value);
|
|
85
|
+
gl.texSubImage2D(
|
|
86
|
+
record.target,
|
|
87
|
+
0,
|
|
88
|
+
0,
|
|
89
|
+
0,
|
|
90
|
+
record.storage[3],
|
|
91
|
+
record.storage[4],
|
|
92
|
+
gl.RGBA,
|
|
93
|
+
gl.UNSIGNED_BYTE,
|
|
94
|
+
record.pixels,
|
|
95
|
+
);
|
|
96
|
+
if (unpackBuffer) gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, unpackBuffer);
|
|
97
|
+
for (const [parameter, value] of unpack) gl.pixelStorei(parameter, value);
|
|
98
|
+
for (const [parameter, value] of record.parameters)
|
|
99
|
+
gl.texParameteri(record.target, parameter, value);
|
|
100
|
+
if (record.mipmaps) gl.generateMipmap(record.target);
|
|
101
|
+
record.entry = own(real, record);
|
|
102
|
+
release(previous, record);
|
|
103
|
+
rebind(record);
|
|
104
|
+
gl.bindTexture(
|
|
105
|
+
record.target,
|
|
106
|
+
current(record.target)?.entry.real ?? previousBinding,
|
|
107
|
+
);
|
|
108
|
+
copies++;
|
|
109
|
+
}
|
|
110
|
+
function finish(record) {
|
|
111
|
+
if (
|
|
112
|
+
!record?.eligible ||
|
|
113
|
+
!record.pixels ||
|
|
114
|
+
(!record.mipmaps && record.storage[1] !== 1) ||
|
|
115
|
+
record.renderTarget
|
|
116
|
+
)
|
|
117
|
+
return;
|
|
118
|
+
const descriptor = JSON.stringify([
|
|
119
|
+
record.storage,
|
|
120
|
+
[...record.parameters].sort((a, b) => a[0] - b[0]),
|
|
121
|
+
[...record.uploadUnpack].sort((a, b) => a[0] - b[0]),
|
|
122
|
+
]);
|
|
123
|
+
const bucket = cache.get(descriptor);
|
|
124
|
+
let match;
|
|
125
|
+
for (const entry of bucket || [])
|
|
126
|
+
if (equalBytes(entry.snapshot.pixels, record.pixels)) {
|
|
127
|
+
match = entry;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
if (match) {
|
|
131
|
+
if (match === record.entry) return;
|
|
132
|
+
const previous = record.entry;
|
|
133
|
+
match.refs.add(record);
|
|
134
|
+
record.entry = match;
|
|
135
|
+
record.pixels = match.snapshot.pixels;
|
|
136
|
+
release(previous, record);
|
|
137
|
+
rebind(record);
|
|
138
|
+
hits++;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
removeCache(record.entry);
|
|
142
|
+
record.entry.key = descriptor;
|
|
143
|
+
let allocatedBytes = 0;
|
|
144
|
+
for (let level = 0; level < record.storage[1]; level++)
|
|
145
|
+
allocatedBytes +=
|
|
146
|
+
4 *
|
|
147
|
+
Math.max(1, record.storage[3] >> level) *
|
|
148
|
+
Math.max(1, record.storage[4] >> level);
|
|
149
|
+
record.entry.snapshot = { pixels: record.pixels, allocatedBytes };
|
|
150
|
+
const next = bucket || new Set();
|
|
151
|
+
next.add(record.entry);
|
|
152
|
+
cache.set(descriptor, next);
|
|
153
|
+
}
|
|
154
|
+
function unknownMutation(target, method, args) {
|
|
155
|
+
const record = current(target);
|
|
156
|
+
if (record) {
|
|
157
|
+
makePrivate(record);
|
|
158
|
+
record.eligible = false;
|
|
159
|
+
record.pixels = null;
|
|
160
|
+
}
|
|
161
|
+
return gl[method](...args);
|
|
162
|
+
}
|
|
163
|
+
const api = {
|
|
164
|
+
createTexture() {
|
|
165
|
+
const virtual = Object.create(WebGLTexture.prototype);
|
|
166
|
+
const record = {
|
|
167
|
+
virtual,
|
|
168
|
+
target: null,
|
|
169
|
+
entry: null,
|
|
170
|
+
storage: null,
|
|
171
|
+
pixels: null,
|
|
172
|
+
uploadUnpack: null,
|
|
173
|
+
parameters: new Map(),
|
|
174
|
+
mipmaps: false,
|
|
175
|
+
eligible: false,
|
|
176
|
+
renderTarget: false,
|
|
177
|
+
deleted: false,
|
|
178
|
+
};
|
|
179
|
+
record.entry = own(gl.createTexture(), record);
|
|
180
|
+
textures.set(virtual, record);
|
|
181
|
+
live.add(record);
|
|
182
|
+
return virtual;
|
|
183
|
+
},
|
|
184
|
+
bindTexture(target, texture) {
|
|
185
|
+
const record = textures.get(texture);
|
|
186
|
+
if (record?.deleted)
|
|
187
|
+
throw new Error('Cannot bind a deleted logical texture');
|
|
188
|
+
if (record && !record.target) record.target = target;
|
|
189
|
+
bindings.set(key(target), record || null);
|
|
190
|
+
return gl.bindTexture(target, record ? record.entry.real : texture);
|
|
191
|
+
},
|
|
192
|
+
activeTexture(value) {
|
|
193
|
+
unit = value;
|
|
194
|
+
return gl.activeTexture(value);
|
|
195
|
+
},
|
|
196
|
+
pixelStorei(parameter, value) {
|
|
197
|
+
if (unpack.has(parameter)) unpack.set(parameter, value);
|
|
198
|
+
return gl.pixelStorei(parameter, value);
|
|
199
|
+
},
|
|
200
|
+
bindBuffer(target, buffer) {
|
|
201
|
+
if (target === gl.PIXEL_UNPACK_BUFFER) unpackBuffer = buffer;
|
|
202
|
+
return gl.bindBuffer(target, buffer);
|
|
203
|
+
},
|
|
204
|
+
texStorage2D(...args) {
|
|
205
|
+
const record = current(args[0]);
|
|
206
|
+
if (record) {
|
|
207
|
+
makePrivate(record);
|
|
208
|
+
record.storage = [...args];
|
|
209
|
+
record.pixels = null;
|
|
210
|
+
record.mipmaps = false;
|
|
211
|
+
record.eligible =
|
|
212
|
+
args[0] === gl.TEXTURE_2D &&
|
|
213
|
+
args[2] === gl.RGBA8 &&
|
|
214
|
+
((args[1] === 10 && args[3] === 512 && args[4] === 512) ||
|
|
215
|
+
(args[1] === 1 && args[3] === 576 && args[4] === 960)) &&
|
|
216
|
+
!record.renderTarget;
|
|
217
|
+
}
|
|
218
|
+
return gl.texStorage2D(...args);
|
|
219
|
+
},
|
|
220
|
+
texSubImage2D(...args) {
|
|
221
|
+
const record = current(args[0]);
|
|
222
|
+
const full =
|
|
223
|
+
record?.eligible &&
|
|
224
|
+
args.length >= 9 &&
|
|
225
|
+
args[1] === 0 &&
|
|
226
|
+
args[2] === 0 &&
|
|
227
|
+
args[3] === 0 &&
|
|
228
|
+
args[4] === record.storage[3] &&
|
|
229
|
+
args[5] === record.storage[4] &&
|
|
230
|
+
args[6] === gl.RGBA &&
|
|
231
|
+
args[7] === gl.UNSIGNED_BYTE &&
|
|
232
|
+
args[8] instanceof Uint8Array &&
|
|
233
|
+
!unpackBuffer &&
|
|
234
|
+
[
|
|
235
|
+
'UNPACK_ROW_LENGTH',
|
|
236
|
+
'UNPACK_IMAGE_HEIGHT',
|
|
237
|
+
'UNPACK_SKIP_PIXELS',
|
|
238
|
+
'UNPACK_SKIP_ROWS',
|
|
239
|
+
'UNPACK_SKIP_IMAGES',
|
|
240
|
+
].every((name) => !unpack.get(gl[name]));
|
|
241
|
+
if (!full) return unknownMutation(args[0], 'texSubImage2D', args);
|
|
242
|
+
const offset = args[9] || 0,
|
|
243
|
+
length = args[4] * args[5] * 4;
|
|
244
|
+
if (
|
|
245
|
+
!Number.isInteger(offset) ||
|
|
246
|
+
offset < 0 ||
|
|
247
|
+
offset + length > args[8].length
|
|
248
|
+
)
|
|
249
|
+
return unknownMutation(args[0], 'texSubImage2D', args);
|
|
250
|
+
makePrivate(record);
|
|
251
|
+
const result = gl.texSubImage2D(...args);
|
|
252
|
+
record.pixels = args[8].slice(offset, offset + length); // Never identify mutable WASM memory by pointer.
|
|
253
|
+
record.uploadUnpack = new Map(unpack);
|
|
254
|
+
record.mipmaps = false;
|
|
255
|
+
finish(record);
|
|
256
|
+
return result;
|
|
257
|
+
},
|
|
258
|
+
texParameteri(target, parameter, value) {
|
|
259
|
+
const record = current(target);
|
|
260
|
+
if (record && record.parameters.get(parameter) !== value) {
|
|
261
|
+
makePrivate(record);
|
|
262
|
+
record.parameters.set(parameter, value);
|
|
263
|
+
}
|
|
264
|
+
const result = gl.texParameteri(target, parameter, value);
|
|
265
|
+
finish(record);
|
|
266
|
+
return result;
|
|
267
|
+
},
|
|
268
|
+
texParameterf(...args) {
|
|
269
|
+
return unknownMutation(args[0], 'texParameterf', args);
|
|
270
|
+
},
|
|
271
|
+
generateMipmap(target) {
|
|
272
|
+
const record = current(target);
|
|
273
|
+
if (record?.eligible && record.mipmaps) return;
|
|
274
|
+
if (record) makePrivate(record);
|
|
275
|
+
const result = gl.generateMipmap(target);
|
|
276
|
+
if (record) {
|
|
277
|
+
record.mipmaps = true;
|
|
278
|
+
finish(record);
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
},
|
|
282
|
+
framebufferTexture2D(target, attachment, textureTarget, texture, level) {
|
|
283
|
+
const record = textures.get(texture);
|
|
284
|
+
if (record) {
|
|
285
|
+
makePrivate(record);
|
|
286
|
+
record.renderTarget = true;
|
|
287
|
+
record.eligible = false;
|
|
288
|
+
record.pixels = null;
|
|
289
|
+
}
|
|
290
|
+
return gl.framebufferTexture2D(
|
|
291
|
+
target,
|
|
292
|
+
attachment,
|
|
293
|
+
textureTarget,
|
|
294
|
+
record ? record.entry.real : texture,
|
|
295
|
+
level,
|
|
296
|
+
);
|
|
297
|
+
},
|
|
298
|
+
framebufferTextureLayer(target, attachment, texture, level, layer) {
|
|
299
|
+
const record = textures.get(texture);
|
|
300
|
+
if (record) {
|
|
301
|
+
makePrivate(record);
|
|
302
|
+
record.renderTarget = true;
|
|
303
|
+
record.eligible = false;
|
|
304
|
+
record.pixels = null;
|
|
305
|
+
}
|
|
306
|
+
return gl.framebufferTextureLayer(
|
|
307
|
+
target,
|
|
308
|
+
attachment,
|
|
309
|
+
record ? record.entry.real : texture,
|
|
310
|
+
level,
|
|
311
|
+
layer,
|
|
312
|
+
);
|
|
313
|
+
},
|
|
314
|
+
getFramebufferAttachmentParameter(...args) {
|
|
315
|
+
const value = gl.getFramebufferAttachmentParameter(...args);
|
|
316
|
+
const entry =
|
|
317
|
+
value && typeof value === 'object'
|
|
318
|
+
? physicalEntries.get(value)
|
|
319
|
+
: undefined;
|
|
320
|
+
// FBO attachment always makes a texture private before it is attached.
|
|
321
|
+
return entry?.refs.size === 1
|
|
322
|
+
? entry.refs.values().next().value.virtual
|
|
323
|
+
: value;
|
|
324
|
+
},
|
|
325
|
+
deleteTexture(texture) {
|
|
326
|
+
if (!texture) return;
|
|
327
|
+
const record = textures.get(texture);
|
|
328
|
+
if (!record) return gl.deleteTexture(texture);
|
|
329
|
+
if (record.deleted) return;
|
|
330
|
+
record.deleted = true;
|
|
331
|
+
for (const [binding, value] of bindings)
|
|
332
|
+
if (value === record) {
|
|
333
|
+
const [at, target] = binding.split(':').map(Number);
|
|
334
|
+
gl.activeTexture(at);
|
|
335
|
+
gl.bindTexture(target, null);
|
|
336
|
+
bindings.set(binding, null);
|
|
337
|
+
}
|
|
338
|
+
gl.activeTexture(unit);
|
|
339
|
+
release(record.entry, record);
|
|
340
|
+
live.delete(record);
|
|
341
|
+
record.pixels = null;
|
|
342
|
+
},
|
|
343
|
+
isTexture(texture) {
|
|
344
|
+
const record = textures.get(texture);
|
|
345
|
+
return record
|
|
346
|
+
? !record.deleted && gl.isTexture(record.entry.real)
|
|
347
|
+
: gl.isTexture(texture);
|
|
348
|
+
},
|
|
349
|
+
getParameter(parameter) {
|
|
350
|
+
const targets = new Map([
|
|
351
|
+
[gl.TEXTURE_BINDING_2D, gl.TEXTURE_2D],
|
|
352
|
+
[gl.TEXTURE_BINDING_3D, gl.TEXTURE_3D],
|
|
353
|
+
[gl.TEXTURE_BINDING_CUBE_MAP, gl.TEXTURE_CUBE_MAP],
|
|
354
|
+
[gl.TEXTURE_BINDING_2D_ARRAY, gl.TEXTURE_2D_ARRAY],
|
|
355
|
+
]);
|
|
356
|
+
if (targets.has(parameter))
|
|
357
|
+
return (
|
|
358
|
+
current(targets.get(parameter))?.virtual || gl.getParameter(parameter)
|
|
359
|
+
);
|
|
360
|
+
return gl.getParameter(parameter);
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
const writes = new Set([
|
|
364
|
+
'texImage2D',
|
|
365
|
+
'copyTexImage2D',
|
|
366
|
+
'copyTexSubImage2D',
|
|
367
|
+
'compressedTexImage2D',
|
|
368
|
+
'compressedTexSubImage2D',
|
|
369
|
+
'texStorage3D',
|
|
370
|
+
'texImage3D',
|
|
371
|
+
'texSubImage3D',
|
|
372
|
+
'copyTexSubImage3D',
|
|
373
|
+
'compressedTexImage3D',
|
|
374
|
+
'compressedTexSubImage3D',
|
|
375
|
+
]);
|
|
376
|
+
const context = new Proxy(gl, {
|
|
377
|
+
get(target, property) {
|
|
378
|
+
if (Object.hasOwn(api, property)) return api[property];
|
|
379
|
+
if (methods.has(property)) return methods.get(property);
|
|
380
|
+
const value = Reflect.get(target, property, target);
|
|
381
|
+
if (typeof value !== 'function') return value;
|
|
382
|
+
if (!methods.has(property))
|
|
383
|
+
methods.set(
|
|
384
|
+
property,
|
|
385
|
+
writes.has(property)
|
|
386
|
+
? (...args) => unknownMutation(args[0], property, args)
|
|
387
|
+
: value.bind(target),
|
|
388
|
+
);
|
|
389
|
+
return methods.get(property);
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
return {
|
|
393
|
+
context,
|
|
394
|
+
stats: () => ({
|
|
395
|
+
logicalTextures: live.size,
|
|
396
|
+
physicalTextures: entries.size,
|
|
397
|
+
pooledTextures: [...cache.values()].reduce((n, v) => n + v.size, 0),
|
|
398
|
+
// RGBA8 storage across allocated levels, including each asset's base level.
|
|
399
|
+
mipBytesSaved: [...cache.values()].reduce(
|
|
400
|
+
(total, bucket) =>
|
|
401
|
+
total +
|
|
402
|
+
[...bucket].reduce(
|
|
403
|
+
(sum, entry) =>
|
|
404
|
+
sum + (entry.refs.size - 1) * entry.snapshot.allocatedBytes,
|
|
405
|
+
0,
|
|
406
|
+
),
|
|
407
|
+
0,
|
|
408
|
+
),
|
|
409
|
+
hits,
|
|
410
|
+
copies,
|
|
411
|
+
}),
|
|
412
|
+
dispose() {
|
|
413
|
+
if (disposed) return;
|
|
414
|
+
disposed = true;
|
|
415
|
+
for (const record of [...live]) api.deleteTexture(record.virtual);
|
|
416
|
+
methods.clear();
|
|
417
|
+
bindings.clear();
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
}
|
|
Binary file
|
package/docs/agent-avatar.mdx
CHANGED
|
@@ -26,33 +26,70 @@ Host the package's complete `assets/character-runtime/` directory. The provider
|
|
|
26
26
|
|
|
27
27
|
## Editing
|
|
28
28
|
|
|
29
|
-
The `character` recipe holds a `preset` and optional `selections` for shape, color, eyes, eyewear and accessory. Explicit selections override preset defaults. `none` removes eyewear or an accessory.
|
|
29
|
+
The `character` recipe holds a `preset` and optional `selections` for shape, color, eyes, eyewear and accessory. Explicit selections override preset defaults. `none` removes eyewear or an accessory. Changing shape preserves all selected face and accessory parts. Editing the eyes, eyewear, accessories or color keeps the named preset's original body until a new shape is explicitly selected.
|
|
30
30
|
|
|
31
|
-
The
|
|
31
|
+
All 21 shapes use that same recipe. The additional contours have ordinary shape IDs: `slender`, `pocket`, `petal`, `star`, `cloud`, `shield`, `pebble` and `squircle`. For example, `{ preset: 'lime_frog', selections: { shape: 'cloud', eyes: 'cyclops', color: 'blue', accessory: 'felipe_beret' } }` selects a blue cloud with one eye and Felipe's hat. A shape contributes geometry only; it never selects another preset, palette or set of parts. Named presets retain their own default colors, while switching bodies keeps the chosen palette color or explicit `bodyColor`. Historical paper/blob recipes retain their saved paint when first edited and are promoted into the same recipe format.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
The editor presents one shape catalog and one eye catalog through `ShapeArc` and `EmotionPicker`. Its eyes include the engine’s nine native styles, Todd’s authored eyes, Clippo’s eyes and a single-eye style. `eyes: 'todd'` and `accessory: 'felipe_beret'` select the actual eyes from Todd and the three-piece beret from Felipe across the body catalog. Their original meshes, materials and animation deltas are copied in the preparation worker, fitted to each body and included in cache identities. The editor uses a static Todd-eye thumbnail; the shape wheel still uses silhouettes. Historical recipes without an explicit eye selection default to oval eyes. Historical expression, eye-size, `eyeGap` and tilt fields remain readable without modifying a migrated face. The shared `GlossArt` palette and custom color picker edit either body source.
|
|
34
|
+
|
|
35
|
+
Each shape defines its own eye positions and size. Changing the eye style preserves that layout; changing shape adopts the new body's layout. Eye depth follows the actual body surface so shallow styles remain visible on bodies such as Todd. Eyewear fits the composed eyes, and a final rigid adjustment keeps crown accessories clear of eyes and frames. `selections.eyes: 'cyclops'` uses one large white eye with a teal iris, dark pupil and highlight at the shape's face center. There is no eye-placement control in AgentCreator. Historical `character.eyeSpacing` and `eyeGap` values remain readable but do not override the 3D layout. The **Universal Customization** and **Single Eye** stories exercise the shared editor and cross-body eye selections.
|
|
36
|
+
|
|
37
|
+
`selections.shape: 'todd'` selects Todd's original body independently of its eye style. Its selector silhouette is sampled from the original mesh; no avatar runs inside the shape wheel. Editing a named preset's face or accessories preserves its original body, while an explicit shape selection replaces that body and retains the selected parts.
|
|
38
|
+
|
|
39
|
+
`{ character: { preset: 'clippo' } }` selects Clippo’s grey open-wire body, large white eyes, dark pupils and asymmetric brows. `selections.shape: 'clippo'` and `selections.eyes: 'clippo'` are independent choices: other eyes can use the clip body, and other bodies can use Clippo’s face. Its tube is generated as a real hollow silhouette with rounded ends, without fur; it uses the same native character, lighting, interaction and work controller. Color, eyewear and headwear remain editable. The shape and eye wheels use static SVG thumbnails. Storybook’s **Clippo** story provides React/Work controls and cross-body examples.
|
|
40
|
+
|
|
41
|
+
Alien avatars have been removed from the available catalog. Saved aliens, mascots and hidden-face recipes retain compatibility rendering. The shape selector shows static silhouettes without eyes or accessories: original catalog PNGs and cached SVG contours for migrated bodies. Paper grain and material effects become the engine’s 3D surface. Migrated bodies accept the original eyewear and accessory catalog. Headwear, Bulb, bow and Tuft use rigid contour anchors; eyewear fits the final eye geometry and headphones use body anchors. Original meshes, materials and fur remain intact.
|
|
34
42
|
|
|
35
43
|
## Interaction and lifecycle
|
|
36
44
|
|
|
37
|
-
`interactive` enables the original engine's pointer tracking and touch/drag interaction. Change `reactionKey` from an accessible button to
|
|
45
|
+
`interactive` enables the original engine's pointer tracking and touch/drag interaction. Change `reactionKey` from an accessible button to play the authored signature selected by the current body. Named bodies retain their native profile, including Todd and Jojo, after face, color or accessory edits. Catalog shapes with an authored counterpart use its body profile. Bodies without a matching profile, including Clippo and distinct migrated contours, use Todd's original signature as their shared standard. Pointer/touch reactions use the same body profile and preserve the edited appearance. Shape changes retain the last painted frame while the new geometry prepares. Changing `workingKey` starts a working episode and then returns to the interactive mode; `workingCycles` controls its duration. The creator’s **React** and **Work** buttons preview these original animations for both original and migrated bodies. Work preserves the chosen body and fitted face and accessories. Both modes retain the same native character, prepared geometry and clock while resident. Activity props are prepared up front; a verified adapter selects the original controller. A 180 ms transition interpolates body/skin transforms and camera in 3D from the last presented pose; added work props follow their original animation. Work finishes its original outro before a queued reaction starts. Repeated requests invalidate stale stop timers. Pause-related appearance changes and offscreen reclamation have separate lifecycles.
|
|
38
46
|
|
|
39
47
|
`paused`, reduced motion and `motion: 0` stop animation after a complete appearance frame. `portrait` prepares a static thumbnail. The beta can report readiness while drawing transparency if initialized with its reduced-motion flag; the adapter waits for painted pixels and then freezes its clock instead.
|
|
40
48
|
|
|
41
|
-
Web
|
|
49
|
+
Web uses one physical WebGL2 context and a packed canvas shared by every visible, animated character. Each original or migrated avatar keeps its own animation, input, work state and render targets. `shared-surface.mjs` maps default-framebuffer operations into its region, resolves MSAA locally before the atlas copy, and clips presentation to prevent neighbouring avatars leaking into a fitted image. Deleting one character cannot destroy the shared context; the last lease releases it. The shared clock targets 30 fps with a round-robin submission allowance; this is a target, not a guarantee of 30 fps per avatar on every device.
|
|
50
|
+
|
|
51
|
+
Shaders and linked programs are shared by exact source and attribute bindings. Logical program handles retain independent uniforms and Emscripten metadata; switching skips values already present on the physical program. Small avatars (up to 128 CSS pixels) use the original compact mesh. The drawing resolution uses DPR at most 1 up to 64 CSS pixels, 1.5 up to 128, and 2 above that, with a 64-pixel floor and 512-pixel ceiling. The original engine derives its fur draw count from that drawing size, so reducing prepared fur arrays would not reduce its draw calls. Original fur and materials remain in use. The two 512-pixel mipmapped RGBA8 images and the 576×960 RGBA8 atlas share storage only when bytes and parameters match exactly; mutation or framebuffer attachment creates an independent copy. Private render targets, mutable geometry buffers and other textures remain per-character.
|
|
52
|
+
|
|
53
|
+
The scheduler submits at most eight avatars per batch within an 8 ms CPU allowance, then waits asynchronously on a WebGL fence before checking driver errors and copying images. This releases the JavaScript thread while the GPU finishes; it does not make that GPU work free. Native setup/probing and Emscripten’s own `GL.lastError` checks retain their behavior. A graphics error discards every pending image in that batch and reports failure to its touched characters. Appearance edits, resize and disposal wait for the batch to finish. Pointer capture happens synchronously on pointer-down; native input is queued in order. A single native render remains non-interruptible. Diagnostics separate `renderMilliseconds` (submission plus presentation CPU time) from `renderWaitMilliseconds` (elapsed wait and task dispatch, not a GPU timer).
|
|
54
|
+
|
|
55
|
+
Original and migrated bodies share one rendering WASM module and one preparation worker (the worker owns its own WASM instance). Prepared geometry identities include the contour and attachment transform. Cold preparation is admitted serially, while already painted moving avatars remain resident. Its owner receives a render turn every batch until its first complete paint, preventing the resident crowd from delaying the next preparation. The most recent interaction receives priority on alternate batches; the remaining turns use round-robin scheduling. Exact contour preparations are cached with an 8 MiB/16-entry LRU bound, keyed by appearance bytes, quality, activity mode, contour and authored parts. Cache hits receive independent buffer copies; native delivery still waits for the GPU boundary. Offscreen/background characters release their lease and retain their last complete 2D image. Static portraits use a bounded 64-image cache and release rendering resources after painting. Storybook’s **Many Avatars** story exercises 48 original/migrated avatars with React/Work controls and scrolling. **Shared Parts** shows Todd’s eyes and Felipe’s beret on all 21 shapes, with default-part, pause and animation controls.
|
|
56
|
+
|
|
57
|
+
### Physical-device measurements
|
|
58
|
+
|
|
59
|
+
The [current controller/transition benchmark](./benchmarks/avatar-continuous-renderer.json) uses a Pixel 8a (Mali-G715, Android 16, Android WebView 148), with all avatars visible at 48 CSS pixels and 64 backing pixels. Original presets and migrated contours alternate. After every avatar is ready, a three-second warmup precedes a roughly five-second sample. Output pixel hashes confirm that every avatar moves; page RAF cadence is measured separately from each avatar's paint rate. The artifact records the exact tested asset hashes; it predates the authored Todd/Felipe part compositor.
|
|
60
|
+
|
|
61
|
+
| Visible avatars | Median paints per avatar per second | Page RAF interval, p95 | Time until all are ready | WebGL contexts |
|
|
62
|
+
| --- | --- | --- | --- | --- |
|
|
63
|
+
| 24 | 4.42 | 25.1 ms | 14.85 s | 1 |
|
|
64
|
+
| 48 | 2.29 | 25.1 ms | 24.80 s | 1 |
|
|
65
|
+
|
|
66
|
+
These results remove context loss, but **do not deliver fluid 30 fps animation**. The [previous baseline](./benchmarks/avatar-shared-renderer.json) took 24.6/74.1 seconds until all avatars were ready, with mean paint rates of 3.76/2.59 Hz. Startup improves substantially; drawing throughput does not improve uniformly. An [intermediate snapshot](./benchmarks/avatar-deferred-renderer.json) isolates deferred GPU waiting before eager activity preparation. These are individual runs, not controlled repeated trials or sustained thermal measurements. Readiness uses a new document/engine and warm HTTP caches. Pixel-hash sampling adds readback overhead.
|
|
42
67
|
|
|
43
|
-
|
|
68
|
+
Current CPU submission/presentation p95 is 10.0/10.4 ms per batch at 24/48, excluding asynchronous GPU wait. Exact texture sharing avoids a calculated 109.85/224.47 MiB of duplicate requested mip storage. These are payload calculations, not total GPU memory measurements. Both runs have nine physical programs, all avatars moving, zero graphics errors and zero live contexts/characters/preparation jobs after cleanup. Interaction and same-character React/Work continuity are checked separately in browser gates.
|
|
44
69
|
|
|
45
70
|
In the repository, run the scheduler tests and the browser budget check (with Storybook running):
|
|
46
71
|
|
|
47
72
|
```bash
|
|
48
|
-
node --test scripts/test-avatar-render-budget.mjs
|
|
73
|
+
node --test scripts/test-avatar-render-budget.mjs scripts/test-avatar-preparation-scope.mjs scripts/test-avatar-render-errors.mjs scripts/test-avatar-preparation-cache.mjs scripts/test-avatar-preparation-delivery.mjs scripts/test-avatar-gl-dispatch.mjs scripts/test-avatar-managed-commands.mjs scripts/test-avatar-character-recipe.mjs scripts/test-avatar-clippo-geometry.mjs scripts/test-avatar-authored-parts.mjs scripts/test-avatar-controller-mode.mjs scripts/test-avatar-pose-transition.mjs
|
|
74
|
+
node scripts/verify-avatar-shared-surface.mjs
|
|
75
|
+
node scripts/verify-avatar-shared-programs.mjs
|
|
76
|
+
node scripts/verify-avatar-shared-textures.mjs
|
|
77
|
+
node scripts/verify-avatar-runtime-errors.mjs
|
|
78
|
+
node scripts/verify-avatar-preparation-scope.mjs
|
|
49
79
|
node scripts/verify-avatar-render-budget.mjs
|
|
80
|
+
node scripts/verify-avatar-shared-renderer.mjs
|
|
81
|
+
node scripts/verify-avatar-deferred-input.mjs
|
|
82
|
+
node scripts/verify-avatar-controller-continuity.mjs
|
|
83
|
+
node scripts/verify-avatar-pose-transition.mjs
|
|
84
|
+
node scripts/verify-avatar-authored-parts.mjs
|
|
85
|
+
node scripts/verify-avatar-clippo.mjs
|
|
86
|
+
node scripts/verify-avatar-editor.mjs
|
|
50
87
|
```
|
|
51
88
|
|
|
52
89
|
`className` and `style` apply to the outer layout node through Bloom's NativeWind wrapper. `label` defaults to the translated “Agent avatar”; `locale` overrides the scope and `accessibilityLabel` overrides the name. Prefer a meaningful name when the avatar identifies an agent.
|
|
53
90
|
|
|
54
91
|
## Native and fallback
|
|
55
92
|
|
|
56
|
-
The optional native bridge uses `react-native-webview` and requires an absolute runtime URL reachable by the device.
|
|
93
|
+
The optional native bridge uses `react-native-webview` and requires an absolute runtime URL reachable by the device. The web adapter is also exercised in a physical Android WebView; that does not validate a React Native multi-WebView screen. Each native WebView owns a separate document, so context sharing is **per document, not application-wide on native**. Separate WebViews cannot share engine memory, graphics contexts or the web document’s viewport reclamation.
|
|
57
94
|
|
|
58
95
|
An unavailable runtime or missing native peer displays the translated unavailable state. Consumers without a runtime provider retain the universal SVG renderer for procedural recipes. That renderer preserves the original contours, folds, expression springs and deterministic grain; it does not support the beta's new 3D parts.
|
package/docs/agent-creator.mdx
CHANGED
|
@@ -25,9 +25,17 @@ Mount under `BloomProvider` and give the editor a bounded height. Layout classes
|
|
|
25
25
|
|
|
26
26
|
## Appearance
|
|
27
27
|
|
|
28
|
-
The preview is 162px. With `AgentAvatarProvider`, one shape arc combines the eleven original engine shapes
|
|
28
|
+
The preview is 162px. With `AgentAvatarProvider`, one shape arc combines the eleven original engine shapes, Todd's original body, Clippo's wire body and eight distinct migrated bodies: pebble, squircle, slender, pocket, petal, star, cloud and shield. Equivalent circle, triangle, flower, diamond and heart recipes reuse the original geometry. `selections.shape: 'todd'` independently selects Todd's actual body with any eye style. Its static pink SVG thumbnail comes from projected intersections of the original body mesh, without loading an avatar renderer. Alien avatars have been removed from the available catalog and examples; saved alien recipes remain readable for compatibility.
|
|
29
29
|
|
|
30
|
-
The
|
|
30
|
+
Every shape writes the same `character.selections.shape` field. The eight added contour IDs are plain names such as `cloud` and `pebble`; selecting one keeps the current preset, named palette or custom RGB, eyes, eyewear and accessory. Crossing between contours and original geometry retains the mounted avatar. The renderer resolves the body geometry separately from the appearance. Historical saved recipes are promoted with their existing color instead of being reset to a different preset.
|
|
31
|
+
|
|
32
|
+
The eye orbit offers nine original 3D eye styles plus Todd, Clippo and Single eye for both body sources. Changing shape preserves the selected eyes, eyewear and accessory. Named presets provide their original defaults; choosing another eye style retains the preset's body. Historical recipes without an explicit eye selection default to oval eyes. Historical expression, eye-size, `eyeGap` and tilt settings remain readable; the engine owns the 3D face geometry and animation.
|
|
33
|
+
|
|
34
|
+
Known eye, color, eyewear and accessory choices stay available while a new appearance prepares. A pending capability response does not disable the controls or replace the current recipe with provisional defaults. Shape-comparison stories use one shared appearance across all bodies, including the same color, rather than giving old contours a separate flesh-colored default.
|
|
35
|
+
|
|
36
|
+
**Eye placement belongs to the shape.** Each body defines eye positions and size; every interchangeable eye style uses that layout. Changing shape automatically adopts its face layout without introducing another editor control. Historical `character.eyeSpacing` values remain readable for saved-recipe compatibility and are ignored by the renderer. Storybook's **Universal Customization** story starts with Todd and shows the saved recipe beside the existing editor.
|
|
37
|
+
|
|
38
|
+
**Single eye** (`selections.eyes: 'cyclops'`) selects a large white eye with a teal iris, dark pupil and highlight on any body. Its orbit thumbnail is static SVG artwork. It uses the selected shape’s face center and size. Storybook's **Single Eye** story shows it on the original and migrated shapes.
|
|
31
39
|
|
|
32
40
|
Upright eye chips orbit at a 116px radius; dragging or a mouse wheel rotates the orbit. Hover, focus, backgrounding and reduced motion pause automatic movement. The shape selector uses a 210px circular track, an unbounded index, spring, drag and wheel geometry. Choose a silhouette by pressing it; keyboard arrows, Home and End select shapes on web. BloomProvider’s direction reverses horizontal movement in RTL.
|
|
33
41
|
|
|
@@ -35,7 +43,9 @@ Nine glossy color presets and the shared custom picker edit both original and mi
|
|
|
35
43
|
|
|
36
44
|
The preview accepts pointer and touch reactions. **React** and **Work** buttons also support keyboard activation and preview the original engine’s animations on either body source. Working returns to the interactive preview after its episode. Eye chips and original shape chips use catalog PNG thumbnails. Migrated shape chips use cached static SVG silhouettes from their actual contours, matching the catalog’s flat pink artwork. The shape wheel renders no avatars or 3D canvases.
|
|
37
45
|
|
|
38
|
-
The
|
|
46
|
+
The named Clippo preset uses a grey open wire body, large ring eyes and asymmetric eyebrows. Its body and eyes are independent catalog choices; the Clippo eyes remain selected when choosing another original or migrated shape. Clippo chips use static SVG artwork, with an open stroked path for the body and no avatar renderer in the wheel. Saved configurations use `character.preset: 'clippo'` or `character.selections.shape` / `character.selections.eyes` with ID `clippo`.
|
|
47
|
+
|
|
48
|
+
The preset selector provides authored characters; eyewear and accessories use Bloom Select on both original and migrated bodies. Explicit selections survive switching between these bodies. Headwear, Bulb, bow and Tuft attach to migrated contour anchors; eyewear fits the final eye geometry and headphones use body anchors. Reactions follow the body's original native signature after customization; catalog shapes with an authored counterpart use its profile, while unmatched shapes, Clippo and distinct migrated contours use Todd's signature as the shared standard. Work keeps the chosen body and fitted parts. Catalog titles retain their original English names by default. Hosts can translate them through `labels.characterOption(category, id, title)`; control labels follow the editor locale. Consumers without a runtime provider retain compatibility rendering and controls for saved procedural recipes.
|
|
39
49
|
|
|
40
50
|
## Preferences and speech
|
|
41
51
|
|
|
@@ -19,7 +19,7 @@ const AgentAvatar = exports.AgentAvatar = /*#__PURE__*/(0, _react.memo)(function
|
|
|
19
19
|
const legacy = runtimeUrl && !(0, _legacyRecipe.legacyRecipeUnsupported)(props.config);
|
|
20
20
|
return beta || legacy ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_CharacterAvatar.CharacterAvatar, {
|
|
21
21
|
...props
|
|
22
|
-
}
|
|
22
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_ProceduralAvatar.ProceduralAvatar, {
|
|
23
23
|
...props
|
|
24
24
|
});
|
|
25
25
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","_CharacterAvatar","_ProceduralAvatar","_context","_legacyRecipe","_jsxRuntime","AgentAvatar","exports","memo","props","runtimeUrl","useContext","CharacterRuntimeContext","beta","config","character","preset","legacy","legacyRecipeUnsupported","jsx","CharacterAvatar","ProceduralAvatar"],"sourceRoot":"../../../src","sources":["agent-avatar/AgentAvatar.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,gBAAA,GAAAD,OAAA;AACA,IAAAE,iBAAA,GAAAF,OAAA;AACA,IAAAG,QAAA,GAAAH,OAAA;AACA,IAAAI,aAAA,GAAAJ,OAAA;AAA0D,IAAAK,WAAA,GAAAL,OAAA;AAG1D;AACO,MAAMM,WAAW,GAAAC,OAAA,CAAAD,WAAA,gBAAG,IAAAE,WAAI,EAAC,SAASF,WAAWA,CAACG,KAAuB,EAAE;EAC5E,MAAM;IAAEC;EAAW,CAAC,GAAG,IAAAC,iBAAU,EAACC,gCAAuB,CAAC;EAC1D,MAAMC,IAAI,GACRJ,KAAK,CAACK,MAAM,CAACC,SAAS,IAAIN,KAAK,CAACK,MAAM,CAACC,SAAS,CAACC,MAAM,KAAK,OAAO;EACrE,MAAMC,MAAM,GAAGP,UAAU,IAAI,CAAC,IAAAQ,qCAAuB,EAACT,KAAK,CAACK,MAAM,CAAC;EACnE,OAAOD,IAAI,IAAII,MAAM,gBACnB,IAAAZ,WAAA,CAAAc,GAAA,EAAClB,gBAAA,CAAAmB,eAAe;IAAA,
|
|
1
|
+
{"version":3,"names":["_react","require","_CharacterAvatar","_ProceduralAvatar","_context","_legacyRecipe","_jsxRuntime","AgentAvatar","exports","memo","props","runtimeUrl","useContext","CharacterRuntimeContext","beta","config","character","preset","legacy","legacyRecipeUnsupported","jsx","CharacterAvatar","ProceduralAvatar"],"sourceRoot":"../../../src","sources":["agent-avatar/AgentAvatar.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,gBAAA,GAAAD,OAAA;AACA,IAAAE,iBAAA,GAAAF,OAAA;AACA,IAAAG,QAAA,GAAAH,OAAA;AACA,IAAAI,aAAA,GAAAJ,OAAA;AAA0D,IAAAK,WAAA,GAAAL,OAAA;AAG1D;AACO,MAAMM,WAAW,GAAAC,OAAA,CAAAD,WAAA,gBAAG,IAAAE,WAAI,EAAC,SAASF,WAAWA,CAACG,KAAuB,EAAE;EAC5E,MAAM;IAAEC;EAAW,CAAC,GAAG,IAAAC,iBAAU,EAACC,gCAAuB,CAAC;EAC1D,MAAMC,IAAI,GACRJ,KAAK,CAACK,MAAM,CAACC,SAAS,IAAIN,KAAK,CAACK,MAAM,CAACC,SAAS,CAACC,MAAM,KAAK,OAAO;EACrE,MAAMC,MAAM,GAAGP,UAAU,IAAI,CAAC,IAAAQ,qCAAuB,EAACT,KAAK,CAACK,MAAM,CAAC;EACnE,OAAOD,IAAI,IAAII,MAAM,gBACnB,IAAAZ,WAAA,CAAAc,GAAA,EAAClB,gBAAA,CAAAmB,eAAe;IAAA,GAAKX;EAAK,CAAG,CAAC,gBAE9B,IAAAJ,WAAA,CAAAc,GAAA,EAACjB,iBAAA,CAAAmB,gBAAgB;IAAA,GAAKZ;EAAK,CAAG,CAC/B;AACH,CAAC,CAAC","ignoreList":[]}
|
|
@@ -15,6 +15,7 @@ var _characterHtml = require("./character-html.js");
|
|
|
15
15
|
var _characterRuntimeModule = require("./character-runtime-module.js");
|
|
16
16
|
var _characterWebview = require("./character-webview.js");
|
|
17
17
|
var _legacyRecipe = require("./legacy-recipe.js");
|
|
18
|
+
var _characterShapes = require("./character-shapes.js");
|
|
18
19
|
var _messages2 = require("./messages.js");
|
|
19
20
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
20
21
|
function WebCharacter({
|
|
@@ -171,7 +172,7 @@ function CharacterAvatar(props) {
|
|
|
171
172
|
};
|
|
172
173
|
}, []);
|
|
173
174
|
(0, _react.useEffect)(() => setFailed(false), [runtimeUrl, config.character]);
|
|
174
|
-
const legacy = (0, _react.useMemo)(() => !config.character || config.character.preset === 'bloom' ? (0, _legacyRecipe.legacyRecipe)(config) : undefined, [config]);
|
|
175
|
+
const legacy = (0, _react.useMemo)(() => !config.character || config.character.preset === 'bloom' || (0, _characterShapes.isMigratedCharacterShape)(config.character.selections?.shape) ? (0, _legacyRecipe.legacyRecipe)(config) : undefined, [config]);
|
|
175
176
|
const value = (0, _react.useMemo)(() => ({
|
|
176
177
|
config,
|
|
177
178
|
legacy,
|