@oxy.so/bloom 7.1.4 → 7.2.0
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 +19 -114
- package/assets/character-runtime/legacy-engine.mjs +54 -406
- package/assets/character-runtime/legacy-geometry.mjs +85 -242
- package/assets/character-runtime/legacy-worker.mjs +23 -82
- package/assets/character-runtime/managed-avatar.mjs +18 -12
- package/assets/character-runtime/render-budget.mjs +11 -6
- package/assets/character-runtime/runtime.mjs +295 -744
- package/docs/adoption-matrix.mdx +12 -11
- package/docs/agent-avatar.mdx +8 -45
- package/docs/agent-creator.mdx +3 -13
- package/docs/app-shell.mdx +26 -2
- package/docs/appearance.mdx +16 -1
- package/docs/content-panel.mdx +4 -0
- package/docs/mail-thread.mdx +8 -0
- package/docs/page-footer.mdx +60 -0
- package/docs/sidebar.mdx +1 -1
- 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 +1 -2
- package/lib/commonjs/agent-avatar/CharacterAvatar.js.map +1 -1
- package/lib/commonjs/agent-avatar/config-character.js +0 -4
- package/lib/commonjs/agent-avatar/config-character.js.map +1 -1
- package/lib/commonjs/agent-avatar/legacy-recipe.js +6 -19
- package/lib/commonjs/agent-avatar/legacy-recipe.js.map +1 -1
- package/lib/commonjs/agent-creator/AgentCreatorBase.js +107 -67
- package/lib/commonjs/agent-creator/AgentCreatorBase.js.map +1 -1
- package/lib/commonjs/agent-creator/CharacterControls.js +5 -23
- package/lib/commonjs/agent-creator/CharacterControls.js.map +1 -1
- package/lib/commonjs/agent-creator/EmotionPicker.js +1 -8
- package/lib/commonjs/agent-creator/EmotionPicker.js.map +1 -1
- package/lib/commonjs/agent-creator/ShapeSilhouette.js +1 -8
- package/lib/commonjs/agent-creator/ShapeSilhouette.js.map +1 -1
- package/lib/commonjs/agent-creator/constants.js +17 -16
- 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/app-shell/AppShell.js +3 -2
- package/lib/commonjs/app-shell/AppShell.js.map +1 -1
- package/lib/commonjs/app-shell/AppShellBars.js +8 -5
- package/lib/commonjs/app-shell/AppShellBars.js.map +1 -1
- package/lib/commonjs/app-shell/AppShellEngine.js +81 -36
- package/lib/commonjs/app-shell/AppShellEngine.js.map +1 -1
- package/lib/commonjs/appearance/BloomScope.js +8 -3
- package/lib/commonjs/appearance/BloomScope.js.map +1 -1
- package/lib/commonjs/appearance/context.js.map +1 -1
- package/lib/commonjs/content-panel/ContentPanel.js +34 -23
- package/lib/commonjs/content-panel/ContentPanel.js.map +1 -1
- package/lib/commonjs/content-panel/ContentPanel.web.js +39 -17
- package/lib/commonjs/content-panel/ContentPanel.web.js.map +1 -1
- package/lib/commonjs/design-tokens/shapes.js +2 -2
- package/lib/commonjs/design-tokens/shapes.js.map +1 -1
- package/lib/commonjs/design-tokens/tokens.json +2 -2
- package/lib/commonjs/index.js +939 -927
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +939 -927
- package/lib/commonjs/index.web.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/commonjs/mail-thread/MailMessage.js +8 -14
- package/lib/commonjs/mail-thread/MailMessage.js.map +1 -1
- package/lib/commonjs/mail-thread/MailThread.js +6 -2
- package/lib/commonjs/mail-thread/MailThread.js.map +1 -1
- package/lib/commonjs/mail-thread/shared.js +2 -1
- package/lib/commonjs/mail-thread/shared.js.map +1 -1
- package/lib/commonjs/page-footer/PageFooter.js +137 -0
- package/lib/commonjs/page-footer/PageFooter.js.map +1 -0
- package/lib/commonjs/page-footer/PageFooterProvider.js +22 -0
- package/lib/commonjs/page-footer/PageFooterProvider.js.map +1 -0
- package/lib/commonjs/page-footer/context.js +11 -0
- package/lib/commonjs/page-footer/context.js.map +1 -0
- package/lib/commonjs/page-footer/index.js +27 -0
- package/lib/commonjs/page-footer/index.js.map +1 -0
- package/lib/commonjs/page-footer/types.js +6 -0
- package/lib/commonjs/page-footer/types.js.map +1 -0
- package/lib/commonjs/page-footer/use-page-footer-inset.js +19 -0
- package/lib/commonjs/page-footer/use-page-footer-inset.js.map +1 -0
- package/lib/commonjs/sidebar/Sidebar.js +6 -5
- package/lib/commonjs/sidebar/Sidebar.js.map +1 -1
- package/lib/commonjs/styles/panel-chrome.js +15 -1
- package/lib/commonjs/styles/panel-chrome.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 +1 -2
- package/lib/module/agent-avatar/CharacterAvatar.js.map +1 -1
- package/lib/module/agent-avatar/config-character.js +0 -4
- package/lib/module/agent-avatar/config-character.js.map +1 -1
- package/lib/module/agent-avatar/legacy-recipe.js +6 -19
- package/lib/module/agent-avatar/legacy-recipe.js.map +1 -1
- package/lib/module/agent-creator/AgentCreatorBase.js +109 -69
- package/lib/module/agent-creator/AgentCreatorBase.js.map +1 -1
- package/lib/module/agent-creator/CharacterControls.js +6 -24
- package/lib/module/agent-creator/CharacterControls.js.map +1 -1
- package/lib/module/agent-creator/EmotionPicker.js +1 -8
- package/lib/module/agent-creator/EmotionPicker.js.map +1 -1
- package/lib/module/agent-creator/ShapeSilhouette.js +1 -8
- package/lib/module/agent-creator/ShapeSilhouette.js.map +1 -1
- package/lib/module/agent-creator/constants.js +16 -8
- 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/app-shell/AppShell.js +3 -2
- package/lib/module/app-shell/AppShell.js.map +1 -1
- package/lib/module/app-shell/AppShellBars.js +8 -5
- package/lib/module/app-shell/AppShellBars.js.map +1 -1
- package/lib/module/app-shell/AppShellEngine.js +81 -36
- package/lib/module/app-shell/AppShellEngine.js.map +1 -1
- package/lib/module/appearance/BloomScope.js +8 -3
- package/lib/module/appearance/BloomScope.js.map +1 -1
- package/lib/module/appearance/context.js.map +1 -1
- package/lib/module/content-panel/ContentPanel.js +35 -24
- package/lib/module/content-panel/ContentPanel.js.map +1 -1
- package/lib/module/content-panel/ContentPanel.web.js +41 -19
- package/lib/module/content-panel/ContentPanel.web.js.map +1 -1
- package/lib/module/design-tokens/shapes.js +2 -2
- package/lib/module/design-tokens/shapes.js.map +1 -1
- package/lib/module/design-tokens/tokens.json +2 -2
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -0
- package/lib/module/index.web.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/module/mail-thread/MailMessage.js +8 -14
- package/lib/module/mail-thread/MailMessage.js.map +1 -1
- package/lib/module/mail-thread/MailThread.js +6 -2
- package/lib/module/mail-thread/MailThread.js.map +1 -1
- package/lib/module/mail-thread/shared.js +2 -1
- package/lib/module/mail-thread/shared.js.map +1 -1
- package/lib/module/page-footer/PageFooter.js +132 -0
- package/lib/module/page-footer/PageFooter.js.map +1 -0
- package/lib/module/page-footer/PageFooterProvider.js +18 -0
- package/lib/module/page-footer/PageFooterProvider.js.map +1 -0
- package/lib/module/page-footer/context.js +7 -0
- package/lib/module/page-footer/context.js.map +1 -0
- package/lib/module/page-footer/index.js +6 -0
- package/lib/module/page-footer/index.js.map +1 -0
- package/lib/module/page-footer/types.js +4 -0
- package/lib/module/page-footer/types.js.map +1 -0
- package/lib/module/page-footer/use-page-footer-inset.js +16 -0
- package/lib/module/page-footer/use-page-footer-inset.js.map +1 -0
- package/lib/module/sidebar/Sidebar.js +6 -5
- package/lib/module/sidebar/Sidebar.js.map +1 -1
- package/lib/module/styles/panel-chrome.js +14 -1
- package/lib/module/styles/panel-chrome.js.map +1 -1
- package/lib/typescript/commonjs/agent-avatar/CharacterAvatar.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-avatar/config-character.d.ts +0 -4
- package/lib/typescript/commonjs/agent-avatar/config-character.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts +10 -2
- 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 +1 -2
- package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts +0 -1
- 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/constants.d.ts +15 -7
- 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/commonjs/app-shell/AppShellBars.d.ts +4 -0
- package/lib/typescript/commonjs/app-shell/AppShellBars.d.ts.map +1 -1
- package/lib/typescript/commonjs/app-shell/AppShellEngine.d.ts.map +1 -1
- package/lib/typescript/commonjs/app-shell/types.d.ts +11 -3
- package/lib/typescript/commonjs/app-shell/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/appearance/BloomScope.d.ts +1 -1
- package/lib/typescript/commonjs/appearance/BloomScope.d.ts.map +1 -1
- package/lib/typescript/commonjs/appearance/context.d.ts +2 -2
- package/lib/typescript/commonjs/appearance/context.d.ts.map +1 -1
- package/lib/typescript/commonjs/appearance/types.d.ts +4 -1
- package/lib/typescript/commonjs/appearance/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/content-panel/ContentPanel.d.ts +3 -4
- package/lib/typescript/commonjs/content-panel/ContentPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/content-panel/ContentPanel.web.d.ts +1 -1
- package/lib/typescript/commonjs/content-panel/ContentPanel.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/content-panel/types.d.ts +2 -2
- package/lib/typescript/commonjs/design-tokens/shapes.d.ts +2 -2
- package/lib/typescript/commonjs/index.d.ts +1 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +1 -0
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-thread/MailMessage.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-thread/MailThread.d.ts +1 -1
- package/lib/typescript/commonjs/mail-thread/MailThread.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-thread/shared.d.ts +1 -1
- package/lib/typescript/commonjs/mail-thread/shared.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-thread/types.d.ts +2 -0
- package/lib/typescript/commonjs/mail-thread/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/page-footer/PageFooter.d.ts +7 -0
- package/lib/typescript/commonjs/page-footer/PageFooter.d.ts.map +1 -0
- package/lib/typescript/commonjs/page-footer/PageFooterProvider.d.ts +4 -0
- package/lib/typescript/commonjs/page-footer/PageFooterProvider.d.ts.map +1 -0
- package/lib/typescript/commonjs/page-footer/context.d.ts +3 -0
- package/lib/typescript/commonjs/page-footer/context.d.ts.map +1 -0
- package/lib/typescript/commonjs/page-footer/index.d.ts +5 -0
- package/lib/typescript/commonjs/page-footer/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/page-footer/types.d.ts +26 -0
- package/lib/typescript/commonjs/page-footer/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/page-footer/use-page-footer-inset.d.ts +7 -0
- package/lib/typescript/commonjs/page-footer/use-page-footer-inset.d.ts.map +1 -0
- package/lib/typescript/commonjs/sidebar/Sidebar.d.ts.map +1 -1
- package/lib/typescript/commonjs/sidebar/types.d.ts +1 -1
- package/lib/typescript/commonjs/styles/panel-chrome.d.ts +5 -0
- package/lib/typescript/commonjs/styles/panel-chrome.d.ts.map +1 -1
- package/lib/typescript/module/agent-avatar/CharacterAvatar.d.ts.map +1 -1
- package/lib/typescript/module/agent-avatar/config-character.d.ts +0 -4
- package/lib/typescript/module/agent-avatar/config-character.d.ts.map +1 -1
- package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts +10 -2
- 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 +1 -2
- package/lib/typescript/module/agent-creator/CharacterControls.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/EmotionPicker.d.ts +0 -1
- 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/constants.d.ts +15 -7
- 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/lib/typescript/module/app-shell/AppShellBars.d.ts +4 -0
- package/lib/typescript/module/app-shell/AppShellBars.d.ts.map +1 -1
- package/lib/typescript/module/app-shell/AppShellEngine.d.ts.map +1 -1
- package/lib/typescript/module/app-shell/types.d.ts +11 -3
- package/lib/typescript/module/app-shell/types.d.ts.map +1 -1
- package/lib/typescript/module/appearance/BloomScope.d.ts +1 -1
- package/lib/typescript/module/appearance/BloomScope.d.ts.map +1 -1
- package/lib/typescript/module/appearance/context.d.ts +2 -2
- package/lib/typescript/module/appearance/context.d.ts.map +1 -1
- package/lib/typescript/module/appearance/types.d.ts +4 -1
- package/lib/typescript/module/appearance/types.d.ts.map +1 -1
- package/lib/typescript/module/content-panel/ContentPanel.d.ts +3 -4
- package/lib/typescript/module/content-panel/ContentPanel.d.ts.map +1 -1
- package/lib/typescript/module/content-panel/ContentPanel.web.d.ts +1 -1
- package/lib/typescript/module/content-panel/ContentPanel.web.d.ts.map +1 -1
- package/lib/typescript/module/content-panel/types.d.ts +2 -2
- package/lib/typescript/module/design-tokens/shapes.d.ts +2 -2
- package/lib/typescript/module/index.d.ts +1 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +1 -0
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/mail-thread/MailMessage.d.ts.map +1 -1
- package/lib/typescript/module/mail-thread/MailThread.d.ts +1 -1
- package/lib/typescript/module/mail-thread/MailThread.d.ts.map +1 -1
- package/lib/typescript/module/mail-thread/shared.d.ts +1 -1
- package/lib/typescript/module/mail-thread/shared.d.ts.map +1 -1
- package/lib/typescript/module/mail-thread/types.d.ts +2 -0
- package/lib/typescript/module/mail-thread/types.d.ts.map +1 -1
- package/lib/typescript/module/page-footer/PageFooter.d.ts +7 -0
- package/lib/typescript/module/page-footer/PageFooter.d.ts.map +1 -0
- package/lib/typescript/module/page-footer/PageFooterProvider.d.ts +4 -0
- package/lib/typescript/module/page-footer/PageFooterProvider.d.ts.map +1 -0
- package/lib/typescript/module/page-footer/context.d.ts +3 -0
- package/lib/typescript/module/page-footer/context.d.ts.map +1 -0
- package/lib/typescript/module/page-footer/index.d.ts +5 -0
- package/lib/typescript/module/page-footer/index.d.ts.map +1 -0
- package/lib/typescript/module/page-footer/types.d.ts +26 -0
- package/lib/typescript/module/page-footer/types.d.ts.map +1 -0
- package/lib/typescript/module/page-footer/use-page-footer-inset.d.ts +7 -0
- package/lib/typescript/module/page-footer/use-page-footer-inset.d.ts.map +1 -0
- package/lib/typescript/module/sidebar/Sidebar.d.ts.map +1 -1
- package/lib/typescript/module/sidebar/types.d.ts +1 -1
- package/lib/typescript/module/styles/panel-chrome.d.ts +5 -0
- package/lib/typescript/module/styles/panel-chrome.d.ts.map +1 -1
- package/package.json +18 -1
- package/src/__tests__/support/adoption-matrix.ts +683 -0
- package/src/__tests__/support/card-surface.ts +25 -0
- package/src/__tests__/support/collision-fixture-barrel.ts +20 -0
- package/src/__tests__/support/commerce-harness.tsx +97 -0
- package/src/__tests__/support/composite.ts +68 -0
- package/src/__tests__/support/constructed-style-sheets.ts +68 -0
- package/src/__tests__/support/messages-in.ts +10 -0
- package/src/__tests__/support/press-host.ts +30 -0
- package/src/__tests__/support/rendered-style.ts +99 -0
- package/src/__tests__/support/unread-hook-fixture.ts +33 -0
- package/src/__tests__/support/worklet-capture-fixture.tsx +28 -0
- package/src/agent-avatar/AgentAvatar.tsx +1 -1
- package/src/agent-avatar/CharacterAvatar.tsx +1 -4
- package/src/agent-avatar/__tests__/artwork-golden.json +774 -0
- package/src/agent-avatar/config-character.ts +6 -39
- package/src/agent-avatar/legacy-recipe.ts +6 -34
- package/src/agent-creator/AgentCreatorBase.tsx +152 -121
- package/src/agent-creator/CharacterControls.tsx +14 -39
- package/src/agent-creator/EmotionPicker.tsx +16 -55
- package/src/agent-creator/ShapeSilhouette.tsx +1 -16
- package/src/agent-creator/__tests__/support/character-runtime-fixture.tsx +26 -0
- package/src/agent-creator/constants.ts +14 -47
- package/src/agent-creator/messages.ts +1 -2
- package/src/app-shell/AppShell.tsx +2 -2
- package/src/app-shell/AppShellBars.tsx +6 -4
- package/src/app-shell/AppShellEngine.tsx +51 -18
- package/src/app-shell/types.ts +11 -3
- package/src/appearance/BloomScope.tsx +5 -2
- package/src/appearance/context.ts +2 -2
- package/src/appearance/types.ts +4 -1
- package/src/content-panel/ContentPanel.tsx +32 -25
- package/src/content-panel/ContentPanel.web.tsx +32 -23
- package/src/content-panel/types.ts +2 -2
- package/src/design-tokens/shapes.ts +1 -1
- package/src/design-tokens/tokens.json +2 -2
- package/src/index.ts +1 -0
- package/src/index.web.ts +1 -0
- 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/mail-thread/MailMessage.tsx +9 -11
- package/src/mail-thread/MailThread.tsx +3 -1
- package/src/mail-thread/shared.ts +2 -1
- package/src/mail-thread/types.ts +2 -0
- package/src/page-footer/PageFooter.tsx +96 -0
- package/src/page-footer/PageFooterProvider.tsx +9 -0
- package/src/page-footer/context.ts +6 -0
- package/src/page-footer/index.ts +4 -0
- package/src/page-footer/types.ts +26 -0
- package/src/page-footer/use-page-footer-inset.ts +17 -0
- package/src/sidebar/Sidebar.tsx +5 -4
- package/src/sidebar/types.ts +1 -1
- package/src/styles/panel-chrome.ts +9 -1
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +9346 -0
- package/src/theme/__tests__/__fixtures__/tonal-glass-primary-failures.json +69 -0
- package/src/theme/__tests__/fixtures/color-engine-golden.json +1 -0
- package/assets/character-runtime/authored-parts.mjs +0 -754
- package/assets/character-runtime/catalog-parts.mjs +0 -362
- package/assets/character-runtime/character-recipe.mjs +0 -247
- package/assets/character-runtime/clippo-geometry.mjs +0 -679
- package/assets/character-runtime/controller-mode.mjs +0 -208
- package/assets/character-runtime/cyclops-geometry.mjs +0 -180
- package/assets/character-runtime/migrated-contours.mjs +0 -2067
- package/assets/character-runtime/migrated-faces.mjs +0 -59
- package/assets/character-runtime/pose-transition.mjs +0 -523
- package/assets/character-runtime/preparation-cache.mjs +0 -60
- package/assets/character-runtime/render-errors.mjs +0 -180
- package/assets/character-runtime/resolution.mjs +0 -10
- package/assets/character-runtime/sha256.mjs +0 -44
- package/assets/character-runtime/shared-programs.mjs +0 -534
- package/assets/character-runtime/shared-surface.mjs +0 -490
- package/assets/character-runtime/shared-textures.mjs +0 -420
- package/assets/character-runtime/thumbnails/eyes/todd.png +0 -0
- package/lib/commonjs/agent-avatar/character-colors.js +0 -19
- package/lib/commonjs/agent-avatar/character-colors.js.map +0 -1
- package/lib/commonjs/agent-avatar/character-shapes.js +0 -106
- package/lib/commonjs/agent-avatar/character-shapes.js.map +0 -1
- package/lib/commonjs/agent-creator/ClippoArtwork.js +0 -63
- package/lib/commonjs/agent-creator/ClippoArtwork.js.map +0 -1
- package/lib/commonjs/agent-creator/CyclopsArtwork.js +0 -45
- package/lib/commonjs/agent-creator/CyclopsArtwork.js.map +0 -1
- package/lib/commonjs/agent-creator/ToddArtwork.js +0 -29
- package/lib/commonjs/agent-creator/ToddArtwork.js.map +0 -1
- package/lib/module/agent-avatar/character-colors.js +0 -15
- package/lib/module/agent-avatar/character-colors.js.map +0 -1
- package/lib/module/agent-avatar/character-shapes.js +0 -97
- package/lib/module/agent-avatar/character-shapes.js.map +0 -1
- package/lib/module/agent-creator/ClippoArtwork.js +0 -59
- package/lib/module/agent-creator/ClippoArtwork.js.map +0 -1
- package/lib/module/agent-creator/CyclopsArtwork.js +0 -41
- package/lib/module/agent-creator/CyclopsArtwork.js.map +0 -1
- package/lib/module/agent-creator/ToddArtwork.js +0 -25
- package/lib/module/agent-creator/ToddArtwork.js.map +0 -1
- package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts +0 -13
- package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts.map +0 -1
- package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts +0 -19
- package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts.map +0 -1
- package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts +0 -6
- package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts.map +0 -1
- package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts +0 -5
- package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts.map +0 -1
- package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts +0 -6
- package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts.map +0 -1
- package/lib/typescript/module/agent-avatar/character-colors.d.ts +0 -13
- package/lib/typescript/module/agent-avatar/character-colors.d.ts.map +0 -1
- package/lib/typescript/module/agent-avatar/character-shapes.d.ts +0 -19
- package/lib/typescript/module/agent-avatar/character-shapes.d.ts.map +0 -1
- package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts +0 -6
- package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts.map +0 -1
- package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts +0 -5
- package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts.map +0 -1
- package/lib/typescript/module/agent-creator/ToddArtwork.d.ts +0 -6
- package/lib/typescript/module/agent-creator/ToddArtwork.d.ts.map +0 -1
- package/src/agent-avatar/character-colors.ts +0 -12
- package/src/agent-avatar/character-shapes.ts +0 -122
- package/src/agent-creator/ClippoArtwork.tsx +0 -49
- package/src/agent-creator/CyclopsArtwork.tsx +0 -27
- package/src/agent-creator/ToddArtwork.tsx +0 -24
|
@@ -1,147 +1,48 @@
|
|
|
1
1
|
// Optional renderer adapter. The generated engine and data alongside it remain unmodified.
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
decodeAppearance,
|
|
6
|
-
} from './appearance-codec.mjs';
|
|
7
|
-
import {
|
|
8
|
-
acquireLegacyEngine,
|
|
9
|
-
legacyEngineStats,
|
|
10
|
-
getCharacterEngine,
|
|
11
|
-
acquireCharacterPreparation,
|
|
12
|
-
} from './legacy-engine.mjs';
|
|
2
|
+
import createModule from './orbit-characters.mjs';
|
|
3
|
+
import { customizeAppearance, encodeAppearance } from './appearance-codec.mjs';
|
|
4
|
+
import { acquireLegacyEngine, legacyEngineStats } from './legacy-engine.mjs';
|
|
13
5
|
import { createManagedAvatar, managedAvatarStats } from './managed-avatar.mjs';
|
|
14
|
-
import {
|
|
15
|
-
acquireSharedSurface,
|
|
16
|
-
sharedSurfaceStats,
|
|
17
|
-
renderSharedBatchDeferred,
|
|
18
|
-
waitForSharedRender,
|
|
19
|
-
sharedRenderPending,
|
|
20
|
-
} from './shared-surface.mjs';
|
|
21
|
-
import { characterPixels } from './resolution.mjs';
|
|
22
|
-
import {
|
|
23
|
-
characterRecipe,
|
|
24
|
-
authoredPartsFor,
|
|
25
|
-
bodySignatureFor,
|
|
26
|
-
bodySelectionDefaults,
|
|
27
|
-
NATIVE_PARTS,
|
|
28
|
-
ORIGINAL_PRESETS,
|
|
29
|
-
MIGRATED_SHAPE_IDS,
|
|
30
|
-
isLegacyRecipe,
|
|
31
|
-
withCharacterGeometry,
|
|
32
|
-
} from './character-recipe.mjs';
|
|
33
|
-
import { originalBodyColor, originalPalettePaint } from './catalog-parts.mjs';
|
|
34
6
|
|
|
7
|
+
let modulePromise;
|
|
8
|
+
let serial = 0;
|
|
35
9
|
let frame = 0;
|
|
36
|
-
let
|
|
10
|
+
let lastFrame = -Infinity;
|
|
37
11
|
let nextClient = 0;
|
|
38
12
|
let lastRenderMilliseconds = 0;
|
|
39
|
-
let lastRenderWaitMilliseconds = 0;
|
|
40
|
-
let renderCost = 8;
|
|
41
|
-
let attentionTurn = false;
|
|
42
13
|
const clients = new Set();
|
|
43
14
|
const category = { shape: 0, color: 1, eyes: 2, eyewear: 3, accessory: 4 };
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
15
|
+
function engine() {
|
|
16
|
+
return (modulePromise ??= createModule({
|
|
17
|
+
locateFile: (file) => new URL(file, import.meta.url).href,
|
|
18
|
+
}).catch((error) => {
|
|
19
|
+
modulePromise = undefined;
|
|
20
|
+
throw error;
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
48
23
|
function schedule() {
|
|
49
|
-
if (!frame &&
|
|
24
|
+
if (!frame && clients.size && !document.hidden)
|
|
50
25
|
frame = requestAnimationFrame(tick);
|
|
51
26
|
}
|
|
52
|
-
|
|
27
|
+
function tick(time) {
|
|
53
28
|
frame = 0;
|
|
54
29
|
if (document.hidden) return;
|
|
55
|
-
|
|
56
|
-
|
|
30
|
+
if (time - lastFrame >= 1000 / 30 - 1) {
|
|
31
|
+
lastFrame = time;
|
|
57
32
|
const batch = [...clients];
|
|
58
33
|
const started = performance.now();
|
|
59
|
-
const presentations = [];
|
|
60
|
-
// Await the GPU without blocking the page. Bound CPU work and batch size
|
|
61
|
-
// separately: driver wait time must not collapse every batch to one draw.
|
|
62
|
-
const batchLimit = Math.min(8, Math.max(1, Math.ceil(8 / renderCost)));
|
|
63
|
-
let submittedAt = started,
|
|
64
|
-
presentationCost = 0;
|
|
65
|
-
const touched = [];
|
|
66
|
-
// Preparation is globally serialized by the recovered engine. Its owner
|
|
67
|
-
// must get a chance to commit and paint every frame; waiting behind every
|
|
68
|
-
// resident avatar makes startup latency grow quadratically with the crowd.
|
|
69
|
-
const preparing = batch.find((client) => client.preparing());
|
|
70
|
-
// Give the most recent interaction a responsive cadence, but reserve
|
|
71
|
-
// alternate turns for the round-robin crowd so no avatar is starved.
|
|
72
|
-
const attended = batch
|
|
73
|
-
.filter((client) => client.attention(time) > 0)
|
|
74
|
-
.sort((a, b) => b.attention(time) - a.attention(time))[0];
|
|
75
|
-
const preferred = preparing ?? (attentionTurn ? attended : undefined);
|
|
76
|
-
attentionTurn = !attentionTurn;
|
|
77
|
-
const ordered = preferred ? [preferred] : [];
|
|
78
34
|
for (let i = 0; i < batch.length; i++) {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (client !== preferred)
|
|
87
|
-
nextClient = (batch.indexOf(client) + 1) % batch.length;
|
|
88
|
-
// Rate-limit each character, not the whole document. Crowds can use
|
|
89
|
-
// intervening browser frames without forcing every avatar down to 30/N.
|
|
90
|
-
if (
|
|
91
|
-
clients.has(client) &&
|
|
92
|
-
(client === preparing ||
|
|
93
|
-
time - (client.lastTick ?? -Infinity) >= 1000 / 30 - 1)
|
|
94
|
-
) {
|
|
95
|
-
client.lastTick = time;
|
|
96
|
-
touched.push(client);
|
|
97
|
-
const present = client.render(time / 1000);
|
|
98
|
-
if (present) presentations.push(present);
|
|
99
|
-
}
|
|
100
|
-
// One native render cannot be interrupted, but a crowded page must not
|
|
101
|
-
// submit every remaining avatar after it has spent its frame allowance.
|
|
102
|
-
if (
|
|
103
|
-
presentations.length >= batchLimit ||
|
|
104
|
-
performance.now() - started >= 8
|
|
105
|
-
)
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
submittedAt = performance.now();
|
|
109
|
-
},
|
|
110
|
-
() => {
|
|
111
|
-
const began = performance.now();
|
|
112
|
-
for (const present of presentations) present();
|
|
113
|
-
presentationCost = performance.now() - began;
|
|
114
|
-
},
|
|
115
|
-
);
|
|
116
|
-
} catch (error) {
|
|
117
|
-
// A context-level error cannot be attributed after a shared submission.
|
|
118
|
-
// Discard every pending image and report it to every touched character.
|
|
119
|
-
presentations.length = 0;
|
|
120
|
-
for (const client of touched) {
|
|
121
|
-
try {
|
|
122
|
-
client.fail(error);
|
|
123
|
-
} catch (callbackError) {
|
|
124
|
-
queueMicrotask(() => {
|
|
125
|
-
throw callbackError;
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
lastRenderMilliseconds = submittedAt - started + presentationCost;
|
|
131
|
-
lastRenderWaitMilliseconds = Math.max(
|
|
132
|
-
0,
|
|
133
|
-
performance.now() - started - lastRenderMilliseconds,
|
|
134
|
-
);
|
|
135
|
-
if (presentations.length) {
|
|
136
|
-
const measured = lastRenderMilliseconds / presentations.length;
|
|
137
|
-
// React immediately to a slower GPU; recover capacity gradually to avoid
|
|
138
|
-
// alternating huge batches and long blocked browser frames.
|
|
139
|
-
renderCost = Math.max(measured, renderCost * 0.9 + measured * 0.1, 0.1);
|
|
35
|
+
const index = nextClient % batch.length;
|
|
36
|
+
nextClient = (index + 1) % batch.length;
|
|
37
|
+
const client = batch[index];
|
|
38
|
+
if (clients.has(client)) client.render(time / 1000);
|
|
39
|
+
// One native render cannot be interrupted, but a crowded page must not
|
|
40
|
+
// submit every remaining avatar after it has spent its frame allowance.
|
|
41
|
+
if (performance.now() - started >= 8) break;
|
|
140
42
|
}
|
|
141
|
-
|
|
142
|
-
rendering = false;
|
|
143
|
-
schedule();
|
|
43
|
+
lastRenderMilliseconds = performance.now() - started;
|
|
144
44
|
}
|
|
45
|
+
schedule();
|
|
145
46
|
}
|
|
146
47
|
function visibility() {
|
|
147
48
|
for (const client of instances) client.visibility();
|
|
@@ -149,7 +50,7 @@ function visibility() {
|
|
|
149
50
|
}
|
|
150
51
|
const instances = new Set();
|
|
151
52
|
|
|
152
|
-
/** Original engine and shared animation clock;
|
|
53
|
+
/** Original engine and shared animation clock; contour caches are isolated by legacy-engine. */
|
|
153
54
|
export async function createAvatar(canvas, initial, callbacks = {}) {
|
|
154
55
|
if (!initial.config.character && !initial.legacy)
|
|
155
56
|
throw new Error('A recovered character recipe is required');
|
|
@@ -157,7 +58,9 @@ export async function createAvatar(canvas, initial, callbacks = {}) {
|
|
|
157
58
|
return createManagedAvatar(canvas, initial, callbacks, createCharacter);
|
|
158
59
|
}
|
|
159
60
|
function createCharacter(canvas, initial, callbacks) {
|
|
160
|
-
return
|
|
61
|
+
return initial.legacy
|
|
62
|
+
? createLegacyCharacter(canvas, initial, callbacks)
|
|
63
|
+
: createLiveCharacter(canvas, initial, callbacks);
|
|
161
64
|
}
|
|
162
65
|
|
|
163
66
|
async function createLiveCharacter(
|
|
@@ -167,41 +70,34 @@ async function createLiveCharacter(
|
|
|
167
70
|
isCurrent = () => true,
|
|
168
71
|
previousImage = null,
|
|
169
72
|
) {
|
|
170
|
-
const authoredParts = authoredPartsFor(initial);
|
|
171
73
|
const lease = initial.legacy?.points
|
|
172
|
-
? await acquireLegacyEngine(initial.legacy.points
|
|
74
|
+
? await acquireLegacyEngine(initial.legacy.points)
|
|
173
75
|
: null;
|
|
174
|
-
const module = lease?.module ?? (await
|
|
175
|
-
let releasePreparation = await acquireCharacterPreparation();
|
|
176
|
-
await waitForSharedRender();
|
|
76
|
+
const module = lease?.module ?? (await engine());
|
|
177
77
|
if (!isCurrent() || !canvas.isConnected) {
|
|
178
|
-
releasePreparation();
|
|
179
78
|
lease?.release();
|
|
180
79
|
return { update() {}, dispose() {}, diagnostics: () => ({ ready: false }) };
|
|
181
80
|
}
|
|
81
|
+
const id = `bloom-character-${++serial}`;
|
|
182
82
|
const output = canvas.getContext('2d');
|
|
183
83
|
if (!output) {
|
|
184
|
-
releasePreparation();
|
|
185
84
|
lease?.release();
|
|
186
85
|
throw new Error('Character output canvas is unavailable');
|
|
187
86
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
87
|
+
const makeSurface = () => {
|
|
88
|
+
const node = document.createElement('canvas');
|
|
89
|
+
node.id = id;
|
|
90
|
+
node.style.display = 'none';
|
|
91
|
+
node.setAttribute('aria-hidden', 'true');
|
|
92
|
+
canvas.parentElement.appendChild(node);
|
|
93
|
+
return node;
|
|
94
|
+
};
|
|
95
|
+
let surface = makeSurface();
|
|
197
96
|
let character;
|
|
198
97
|
try {
|
|
199
|
-
character =
|
|
200
|
-
activities: supportsActivities,
|
|
201
|
-
});
|
|
98
|
+
character = new module.Character(`#${id}`, 128, 128);
|
|
202
99
|
} catch (error) {
|
|
203
|
-
|
|
204
|
-
surfaceLease.release();
|
|
100
|
+
surface.remove();
|
|
205
101
|
lease?.release();
|
|
206
102
|
throw error;
|
|
207
103
|
}
|
|
@@ -222,9 +118,6 @@ async function createLiveCharacter(
|
|
|
222
118
|
episode = 0n,
|
|
223
119
|
activity = 0;
|
|
224
120
|
let workTimer,
|
|
225
|
-
workRequest = 0,
|
|
226
|
-
stoppingWork = false,
|
|
227
|
-
queuedReaction = false,
|
|
228
121
|
workDuration,
|
|
229
122
|
lastActivityResult,
|
|
230
123
|
previousWork,
|
|
@@ -232,64 +125,7 @@ async function createLiveCharacter(
|
|
|
232
125
|
let pointerId, pointerStart, previousReaction;
|
|
233
126
|
let lastReaction = null,
|
|
234
127
|
lastReactionKind = null;
|
|
235
|
-
|
|
236
|
-
attentionUntil = 0;
|
|
237
|
-
const attend = (duration) => {
|
|
238
|
-
attentionStarted = performance.now();
|
|
239
|
-
attentionUntil = attentionStarted + duration;
|
|
240
|
-
};
|
|
241
|
-
// hasPendingUpdate also includes reactions whose clock is suspended while
|
|
242
|
-
// Work owns the character. Geometry completion must use preparation revisions,
|
|
243
|
-
// otherwise that old reaction holds the global preparation lease forever.
|
|
244
|
-
const scenePrepared = () => {
|
|
245
|
-
const state = character.preparationStats();
|
|
246
|
-
return (
|
|
247
|
-
!state.pending &&
|
|
248
|
-
!state.failed &&
|
|
249
|
-
state.generation === state.committedGeneration
|
|
250
|
-
);
|
|
251
|
-
};
|
|
252
|
-
let acquiringPreparation = false;
|
|
253
|
-
const preparationQueue = [];
|
|
254
|
-
function finishPreparation(force = false) {
|
|
255
|
-
if (!force && (acquiringPreparation || preparationQueue.length)) return;
|
|
256
|
-
const release = releasePreparation;
|
|
257
|
-
releasePreparation = undefined;
|
|
258
|
-
release?.();
|
|
259
|
-
}
|
|
260
|
-
function prepare(operation) {
|
|
261
|
-
if (disposed) return;
|
|
262
|
-
if (releasePreparation && !sharedRenderPending()) {
|
|
263
|
-
operation();
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
preparationQueue.push(operation);
|
|
267
|
-
if (acquiringPreparation) return;
|
|
268
|
-
acquiringPreparation = true;
|
|
269
|
-
const admission = releasePreparation
|
|
270
|
-
? Promise.resolve(releasePreparation)
|
|
271
|
-
: acquireCharacterPreparation();
|
|
272
|
-
void admission.then(async (release) => {
|
|
273
|
-
await waitForSharedRender();
|
|
274
|
-
acquiringPreparation = false;
|
|
275
|
-
if (disposed) {
|
|
276
|
-
preparationQueue.length = 0;
|
|
277
|
-
release();
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
releasePreparation = release;
|
|
281
|
-
try {
|
|
282
|
-
for (const next of preparationQueue.splice(0)) next();
|
|
283
|
-
if (scenePrepared()) finishPreparation();
|
|
284
|
-
wake();
|
|
285
|
-
} catch (error) {
|
|
286
|
-
finishPreparation();
|
|
287
|
-
callbacks.onError?.(error);
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
character.setQuality(1); // Resize selects mesh detail using CSS size, independent of DPR.
|
|
128
|
+
character.setQuality(initial.portrait ? 1 : 0); // Original automatic quality; compact static thumbnails.
|
|
293
129
|
// Readiness means painted pixels, not merely an engine submission.
|
|
294
130
|
const play = (kind) => {
|
|
295
131
|
if (activity === kind) return;
|
|
@@ -310,39 +146,91 @@ async function createLiveCharacter(
|
|
|
310
146
|
// A queued still must finish if its selector moves it offscreen mid-prepare;
|
|
311
147
|
// otherwise one clipped slot blocks every visible thumbnail until timeout.
|
|
312
148
|
const active = () => (visible || props.portrait) && !document.hidden;
|
|
313
|
-
let wakeQueued = false;
|
|
314
149
|
const wake = () => {
|
|
315
150
|
if (disposed) return;
|
|
316
|
-
if (sharedRenderPending()) {
|
|
317
|
-
if (!wakeQueued) {
|
|
318
|
-
wakeQueued = true;
|
|
319
|
-
void waitForSharedRender().then(() => {
|
|
320
|
-
wakeQueued = false;
|
|
321
|
-
wake();
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
151
|
character.setActive(active());
|
|
327
152
|
if (active() && (dirty || !ready || animate())) clients.add(client);
|
|
328
153
|
else clients.delete(client);
|
|
329
154
|
schedule();
|
|
330
155
|
};
|
|
331
156
|
const client = {
|
|
332
|
-
preparing: () => Boolean(releasePreparation),
|
|
333
|
-
attention: (time) => (time < attentionUntil ? attentionStarted : 0),
|
|
334
157
|
visibility: wake,
|
|
335
|
-
fail(error) {
|
|
336
|
-
clients.delete(client);
|
|
337
|
-
finishPreparation(true);
|
|
338
|
-
callbacks.onError?.(error);
|
|
339
|
-
},
|
|
340
158
|
render(time) {
|
|
341
159
|
try {
|
|
342
|
-
const submitted =
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
160
|
+
const submitted = character.render(time);
|
|
161
|
+
if (submitted) {
|
|
162
|
+
if (
|
|
163
|
+
!fit &&
|
|
164
|
+
!character.preparationStats().pending &&
|
|
165
|
+
!character.hasPendingUpdate()
|
|
166
|
+
) {
|
|
167
|
+
measurement.width = canvas.width;
|
|
168
|
+
measurement.height = canvas.height;
|
|
169
|
+
measure.drawImage(surface, 0, 0, canvas.width, canvas.height);
|
|
170
|
+
const { data, width, height } = measure.getImageData(
|
|
171
|
+
0,
|
|
172
|
+
0,
|
|
173
|
+
canvas.width,
|
|
174
|
+
canvas.height,
|
|
175
|
+
);
|
|
176
|
+
let minX = width,
|
|
177
|
+
minY = height,
|
|
178
|
+
maxX = -1,
|
|
179
|
+
maxY = -1;
|
|
180
|
+
for (let y = 0; y < height; y++)
|
|
181
|
+
for (let x = 0; x < width; x++) {
|
|
182
|
+
if (data[(y * width + x) * 4 + 3] > 24) {
|
|
183
|
+
minX = Math.min(minX, x);
|
|
184
|
+
maxX = Math.max(maxX, x);
|
|
185
|
+
minY = Math.min(minY, y);
|
|
186
|
+
maxY = Math.max(maxY, y);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (maxX >= minX) {
|
|
190
|
+
const extent = Math.max(maxX - minX + 1, maxY - minY + 1) * 1.22;
|
|
191
|
+
fit = [
|
|
192
|
+
(minX + maxX) / 2 - extent / 2,
|
|
193
|
+
(minY + maxY) / 2 - extent / 2,
|
|
194
|
+
extent,
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Retain the previous portrait while a changed shape is preparing.
|
|
199
|
+
if (!fit) return;
|
|
200
|
+
output.clearRect(0, 0, canvas.width, canvas.height);
|
|
201
|
+
if (fit)
|
|
202
|
+
output.drawImage(
|
|
203
|
+
surface,
|
|
204
|
+
fit[0],
|
|
205
|
+
fit[1],
|
|
206
|
+
fit[2],
|
|
207
|
+
fit[2],
|
|
208
|
+
0,
|
|
209
|
+
0,
|
|
210
|
+
canvas.width,
|
|
211
|
+
canvas.height,
|
|
212
|
+
);
|
|
213
|
+
else output.drawImage(surface, 0, 0, canvas.width, canvas.height);
|
|
214
|
+
}
|
|
215
|
+
if (submitted && fit && transitionImage) {
|
|
216
|
+
// Geometry preparation keeps the previous image; blend only after the
|
|
217
|
+
// new original-engine surface has pixels. Reduced motion skips it.
|
|
218
|
+
transitionStarted ??= time;
|
|
219
|
+
const progress = animate()
|
|
220
|
+
? Math.min(1, (time - transitionStarted) / 0.18)
|
|
221
|
+
: 1;
|
|
222
|
+
if (progress < 1) {
|
|
223
|
+
output.globalAlpha = 1 - progress;
|
|
224
|
+
output.drawImage(
|
|
225
|
+
transitionImage,
|
|
226
|
+
0,
|
|
227
|
+
0,
|
|
228
|
+
canvas.width,
|
|
229
|
+
canvas.height,
|
|
230
|
+
);
|
|
231
|
+
output.globalAlpha = 1;
|
|
232
|
+
} else transitionImage = null;
|
|
233
|
+
}
|
|
346
234
|
const renderError = character.renderError();
|
|
347
235
|
if (renderError || character.preparationStats().failed)
|
|
348
236
|
throw new Error(
|
|
@@ -350,293 +238,113 @@ async function createLiveCharacter(
|
|
|
350
238
|
character.preparationError() ||
|
|
351
239
|
'Character preparation failed',
|
|
352
240
|
);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
try {
|
|
365
|
-
if (submitted) {
|
|
366
|
-
if (!fit && scenePrepared()) {
|
|
367
|
-
measurement.width = canvas.width;
|
|
368
|
-
measurement.height = canvas.height;
|
|
369
|
-
surfaceLease.copy(
|
|
370
|
-
measure,
|
|
371
|
-
0,
|
|
372
|
-
0,
|
|
373
|
-
canvas.width,
|
|
374
|
-
canvas.height,
|
|
375
|
-
0,
|
|
376
|
-
0,
|
|
377
|
-
canvas.width,
|
|
378
|
-
canvas.height,
|
|
379
|
-
);
|
|
380
|
-
const { data, width, height } = measure.getImageData(
|
|
381
|
-
0,
|
|
382
|
-
0,
|
|
383
|
-
canvas.width,
|
|
384
|
-
canvas.height,
|
|
385
|
-
);
|
|
386
|
-
let minX = width,
|
|
387
|
-
minY = height,
|
|
388
|
-
maxX = -1,
|
|
389
|
-
maxY = -1;
|
|
390
|
-
for (let y = 0; y < height; y++)
|
|
391
|
-
for (let x = 0; x < width; x++) {
|
|
392
|
-
if (data[(y * width + x) * 4 + 3] > 24) {
|
|
393
|
-
minX = Math.min(minX, x);
|
|
394
|
-
maxX = Math.max(maxX, x);
|
|
395
|
-
minY = Math.min(minY, y);
|
|
396
|
-
maxY = Math.max(maxY, y);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
if (maxX >= minX) {
|
|
400
|
-
const extent =
|
|
401
|
-
Math.max(maxX - minX + 1, maxY - minY + 1) * 1.22;
|
|
402
|
-
fit = [
|
|
403
|
-
(minX + maxX) / 2 - extent / 2,
|
|
404
|
-
(minY + maxY) / 2 - extent / 2,
|
|
405
|
-
extent,
|
|
406
|
-
];
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
// Retain the previous portrait while a changed shape is preparing.
|
|
410
|
-
if (!fit) return;
|
|
411
|
-
output.clearRect(0, 0, canvas.width, canvas.height);
|
|
412
|
-
if (fit)
|
|
413
|
-
surfaceLease.copy(
|
|
414
|
-
output,
|
|
415
|
-
fit[0],
|
|
416
|
-
fit[1],
|
|
417
|
-
fit[2],
|
|
418
|
-
fit[2],
|
|
419
|
-
0,
|
|
420
|
-
0,
|
|
421
|
-
canvas.width,
|
|
422
|
-
canvas.height,
|
|
423
|
-
);
|
|
424
|
-
else surfaceLease.copy(output);
|
|
425
|
-
}
|
|
426
|
-
if (submitted && fit && transitionImage) {
|
|
427
|
-
// Geometry preparation keeps the previous image; blend only after the
|
|
428
|
-
// new original-engine surface has pixels. Reduced motion skips it.
|
|
429
|
-
transitionStarted ??= time;
|
|
430
|
-
const progress = animate()
|
|
431
|
-
? Math.min(1, (time - transitionStarted) / 0.18)
|
|
432
|
-
: 1;
|
|
433
|
-
if (progress < 1) {
|
|
434
|
-
output.globalAlpha = 1 - progress;
|
|
435
|
-
output.drawImage(
|
|
436
|
-
transitionImage,
|
|
437
|
-
0,
|
|
438
|
-
0,
|
|
439
|
-
canvas.width,
|
|
440
|
-
canvas.height,
|
|
441
|
-
);
|
|
442
|
-
output.globalAlpha = 1;
|
|
443
|
-
} else transitionImage = null;
|
|
444
|
-
}
|
|
445
|
-
if (
|
|
446
|
-
submitted &&
|
|
447
|
-
fit &&
|
|
448
|
-
scenePrepared() &&
|
|
449
|
-
fingerprint === pendingFingerprint
|
|
450
|
-
) {
|
|
451
|
-
dirty = false;
|
|
452
|
-
finishPreparation();
|
|
453
|
-
if (!ready) {
|
|
454
|
-
ready = true;
|
|
455
|
-
callbacks.onReady?.();
|
|
456
|
-
}
|
|
457
|
-
callbacks.onPaint?.(!transitionImage);
|
|
458
|
-
if (activityMode && workDuration && !workTimer) {
|
|
459
|
-
const request = workRequest;
|
|
460
|
-
workTimer = setTimeout(() => {
|
|
461
|
-
if (request !== workRequest) return;
|
|
462
|
-
workTimer = undefined;
|
|
463
|
-
workDuration = undefined;
|
|
464
|
-
prepare(() => {
|
|
465
|
-
if (request !== workRequest) return;
|
|
466
|
-
play(0);
|
|
467
|
-
stoppingWork = true;
|
|
468
|
-
});
|
|
469
|
-
dirty = true;
|
|
470
|
-
wake();
|
|
471
|
-
}, workDuration);
|
|
472
|
-
}
|
|
473
|
-
if (!animate()) clients.delete(client);
|
|
474
|
-
}
|
|
475
|
-
} catch (error) {
|
|
476
|
-
clients.delete(client);
|
|
477
|
-
finishPreparation();
|
|
478
|
-
callbacks.onError?.(error);
|
|
241
|
+
if (
|
|
242
|
+
submitted &&
|
|
243
|
+
fit &&
|
|
244
|
+
!character.preparationStats().pending &&
|
|
245
|
+
!character.hasPendingUpdate() &&
|
|
246
|
+
fingerprint === pendingFingerprint
|
|
247
|
+
) {
|
|
248
|
+
dirty = false;
|
|
249
|
+
if (!ready) {
|
|
250
|
+
ready = true;
|
|
251
|
+
callbacks.onReady?.();
|
|
479
252
|
}
|
|
480
|
-
|
|
253
|
+
callbacks.onPaint?.(!transitionImage);
|
|
254
|
+
if (activityMode && workDuration && !workTimer) {
|
|
255
|
+
workTimer = setTimeout(() => {
|
|
256
|
+
workTimer = undefined;
|
|
257
|
+
workDuration = undefined;
|
|
258
|
+
play(0);
|
|
259
|
+
switchMode(false);
|
|
260
|
+
dirty = true;
|
|
261
|
+
wake();
|
|
262
|
+
}, workDuration);
|
|
263
|
+
}
|
|
264
|
+
if (!animate()) clients.delete(client);
|
|
265
|
+
}
|
|
481
266
|
} catch (error) {
|
|
482
267
|
clients.delete(client);
|
|
483
|
-
finishPreparation();
|
|
484
268
|
callbacks.onError?.(error);
|
|
485
269
|
}
|
|
486
270
|
},
|
|
487
271
|
};
|
|
488
|
-
let currentQuality, currentScale;
|
|
489
272
|
const resize = () => {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
273
|
+
const css = Math.max(1, Math.min(canvas.clientWidth, canvas.clientHeight));
|
|
274
|
+
const pixels = Math.max(
|
|
275
|
+
64,
|
|
276
|
+
Math.min(512, Math.round(css * Math.min(devicePixelRatio || 1, 2))),
|
|
493
277
|
);
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
canvas.
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const css = Math.max(
|
|
504
|
-
1,
|
|
505
|
-
Math.min(canvas.clientWidth, canvas.clientHeight),
|
|
506
|
-
);
|
|
507
|
-
const pixels = characterPixels(canvas);
|
|
508
|
-
if (canvas.width !== pixels || canvas.height !== pixels) {
|
|
509
|
-
canvas.width = pixels;
|
|
510
|
-
canvas.height = pixels;
|
|
511
|
-
fit = null;
|
|
512
|
-
}
|
|
513
|
-
const quality = props.portrait || css <= 128 ? 1 : 2;
|
|
514
|
-
if (currentQuality !== quality) {
|
|
515
|
-
character.setQuality(quality);
|
|
516
|
-
currentQuality = quality;
|
|
517
|
-
}
|
|
518
|
-
character.resize(pixels, pixels);
|
|
519
|
-
if (currentScale !== pixels / css) {
|
|
520
|
-
character.setDisplayScale(pixels / css);
|
|
521
|
-
currentScale = pixels / css;
|
|
522
|
-
}
|
|
523
|
-
dirty = true;
|
|
524
|
-
wake();
|
|
525
|
-
});
|
|
526
|
-
};
|
|
527
|
-
const bindController = (next = false) => {
|
|
528
|
-
if (!supportsActivities) return;
|
|
529
|
-
const result = character.applyActivity({
|
|
530
|
-
command: 3,
|
|
531
|
-
sequence: 0n,
|
|
532
|
-
episodeId: 0n,
|
|
533
|
-
episodeHighWater: 0n,
|
|
534
|
-
activity: 0,
|
|
535
|
-
outcome: 0,
|
|
536
|
-
entry: 1,
|
|
537
|
-
});
|
|
538
|
-
if (result !== 0)
|
|
539
|
-
throw new Error(`Character activity initialization failed: ${result}`);
|
|
540
|
-
module.controllerMode(character, next);
|
|
278
|
+
if (canvas.width !== pixels || canvas.height !== pixels) {
|
|
279
|
+
canvas.width = pixels;
|
|
280
|
+
canvas.height = pixels;
|
|
281
|
+
fit = null;
|
|
282
|
+
}
|
|
283
|
+
character.resize(pixels, pixels);
|
|
284
|
+
character.setDisplayScale(pixels / css);
|
|
285
|
+
dirty = true;
|
|
286
|
+
wake();
|
|
541
287
|
};
|
|
542
288
|
const switchMode = (next, resetAppearance = false) => {
|
|
543
|
-
if (resetAppearance)
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
character = surfaceLease.create(module, 128, 128, {
|
|
550
|
-
activities: supportsActivities,
|
|
551
|
-
});
|
|
552
|
-
currentQuality = currentScale = undefined;
|
|
553
|
-
sequence = episode = 0n;
|
|
554
|
-
activity = 0;
|
|
555
|
-
character.setQuality(1);
|
|
556
|
-
character.restore(state);
|
|
557
|
-
character.setReducedMotion(false);
|
|
558
|
-
bindController(next);
|
|
559
|
-
resize();
|
|
560
|
-
} else {
|
|
561
|
-
if (activityMode === next) return;
|
|
562
|
-
surfaceLease.beginTransition(180);
|
|
563
|
-
module.controllerMode(character, next);
|
|
564
|
-
}
|
|
289
|
+
if (activityMode === next && !resetAppearance) return;
|
|
290
|
+
const state = new Uint8Array(character.state());
|
|
291
|
+
character.delete();
|
|
292
|
+
surface.remove();
|
|
293
|
+
surface = makeSurface();
|
|
294
|
+
character = new module.Character(`#${id}`, 128, 128, { activities: next });
|
|
565
295
|
activityMode = next;
|
|
296
|
+
fit = null;
|
|
297
|
+
sequence = 0n;
|
|
298
|
+
episode = 0n;
|
|
299
|
+
activity = 0;
|
|
300
|
+
ready = false;
|
|
566
301
|
dirty = true;
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
if (
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
302
|
+
character.setQuality(props.portrait ? 1 : 0);
|
|
303
|
+
character.restore(state);
|
|
304
|
+
// This recovered engine paints a transparent frame when reduced motion is
|
|
305
|
+
// enabled before its first appearance. Freeze our clock after a painted
|
|
306
|
+
// frame instead, preserving both initial visibility and reduced motion.
|
|
307
|
+
character.setReducedMotion(false);
|
|
308
|
+
|
|
309
|
+
if (next)
|
|
310
|
+
character.applyActivity({
|
|
311
|
+
command: 3,
|
|
312
|
+
sequence: 0n,
|
|
313
|
+
episodeId: 0n,
|
|
314
|
+
episodeHighWater: 0n,
|
|
315
|
+
activity: 0,
|
|
316
|
+
outcome: 0,
|
|
317
|
+
entry: 1,
|
|
318
|
+
});
|
|
319
|
+
resize();
|
|
579
320
|
};
|
|
580
321
|
const react = () => {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
wake();
|
|
594
|
-
};
|
|
595
|
-
if (sharedRenderPending()) void waitForSharedRender().then(apply);
|
|
596
|
-
else apply();
|
|
322
|
+
if (!ready || !animate()) return;
|
|
323
|
+
if (activityMode) return;
|
|
324
|
+
lastReactionKind = 2;
|
|
325
|
+
lastReaction = character.playReaction(lastReactionKind);
|
|
326
|
+
// Authored signatures belong to named presets. Catalog edits and migrated
|
|
327
|
+
// bodies use the same engine's Wave when that signature is unsupported.
|
|
328
|
+
if (lastReaction === 2) {
|
|
329
|
+
lastReactionKind = 1;
|
|
330
|
+
lastReaction = character.playReaction(lastReactionKind);
|
|
331
|
+
}
|
|
332
|
+
dirty = true;
|
|
333
|
+
wake();
|
|
597
334
|
};
|
|
598
335
|
const work = () => {
|
|
599
|
-
|
|
600
|
-
// including requests made while the previous GPU batch is still in flight.
|
|
601
|
-
const request = ++workRequest;
|
|
336
|
+
if (!animate()) return;
|
|
602
337
|
clearTimeout(workTimer);
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
stoppingWork = false;
|
|
608
|
-
switchMode(true);
|
|
609
|
-
play(1);
|
|
610
|
-
workDuration = 2200 * Math.max(1, Math.min(10, props.workingCycles || 1));
|
|
611
|
-
attend(workDuration + 1000);
|
|
612
|
-
});
|
|
613
|
-
};
|
|
614
|
-
let appearanceTimer, pendingFingerprint, lastCapabilities;
|
|
615
|
-
const reportCapabilities = (available, selected) => {
|
|
616
|
-
lastCapabilities = {
|
|
617
|
-
key: JSON.stringify(
|
|
618
|
-
isLegacyRecipe(props) ? characterRecipe(props) : props.config.character,
|
|
619
|
-
),
|
|
620
|
-
available,
|
|
621
|
-
selected,
|
|
622
|
-
};
|
|
623
|
-
callbacks.onCapabilities?.(lastCapabilities);
|
|
338
|
+
switchMode(true);
|
|
339
|
+
play(1);
|
|
340
|
+
workTimer = undefined;
|
|
341
|
+
workDuration = 2200 * Math.max(1, Math.min(10, props.workingCycles || 1));
|
|
624
342
|
};
|
|
343
|
+
let appearanceTimer, pendingFingerprint;
|
|
625
344
|
const applyAppearance = (value) => {
|
|
626
345
|
const recipe = value;
|
|
627
|
-
const nextFingerprint =
|
|
346
|
+
const nextFingerprint = JSON.stringify(recipe);
|
|
628
347
|
if (nextFingerprint !== fingerprint) {
|
|
629
|
-
const defaultPreset = ORIGINAL_PRESETS.includes(recipe.preset)
|
|
630
|
-
? recipe.preset
|
|
631
|
-
: authoredParts.bodyPreset;
|
|
632
|
-
const sourcePaint = defaultPreset
|
|
633
|
-
? originalPalettePaint(module, defaultPreset)
|
|
634
|
-
: null;
|
|
635
|
-
const customizedParts = Object.keys(authoredParts).length > 0;
|
|
636
|
-
const implicitBodyColor =
|
|
637
|
-
customizedParts && !recipe.bodyColor && !recipe.selections?.color
|
|
638
|
-
? sourcePaint?.bodyColor
|
|
639
|
-
: undefined;
|
|
640
348
|
// Catalog selection owns the engine's part transitions. Restoring the
|
|
641
349
|
// named preset on every selection needlessly resets the whole character.
|
|
642
350
|
const removedOverride = Object.keys(appliedRecipe?.selections ?? {}).some(
|
|
@@ -648,74 +356,28 @@ async function createLiveCharacter(
|
|
|
648
356
|
removedOverride ||
|
|
649
357
|
(appliedRecipe.bodyColor && !recipe.bodyColor)
|
|
650
358
|
) {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
model: null,
|
|
668
|
-
rig: null,
|
|
669
|
-
hereCharacter: null,
|
|
670
|
-
})
|
|
671
|
-
: module.presetAppearance(recipe.preset);
|
|
672
|
-
if (
|
|
673
|
-
!props.legacy &&
|
|
674
|
-
recipe.preset !== 'clippo' &&
|
|
675
|
-
recipe.preset !== 'legacy' &&
|
|
676
|
-
!authoredParts.bodyPreset &&
|
|
677
|
-
Object.keys(authoredParts).length
|
|
678
|
-
) {
|
|
679
|
-
// Selecting the preset's existing shape/backing eye is a native no-op,
|
|
680
|
-
// so select() never clears HERE's material overrides. Donor pieces
|
|
681
|
-
// need an editable controller while retaining all authored defaults.
|
|
682
|
-
const editable = decodeAppearance(base);
|
|
683
|
-
editable.hereCharacter = null;
|
|
684
|
-
base = encodeAppearance(editable);
|
|
685
|
-
}
|
|
359
|
+
const base = props.legacy
|
|
360
|
+
? encodeAppearance({
|
|
361
|
+
version: 1,
|
|
362
|
+
shape: 'circle',
|
|
363
|
+
color: 'blue',
|
|
364
|
+
eyes: 'oval',
|
|
365
|
+
eyewear: 'none',
|
|
366
|
+
accessories: [],
|
|
367
|
+
accessoryColors: {},
|
|
368
|
+
constrained: 0,
|
|
369
|
+
depth: 0.5,
|
|
370
|
+
model: null,
|
|
371
|
+
rig: null,
|
|
372
|
+
hereCharacter: null,
|
|
373
|
+
})
|
|
374
|
+
: module.presetAppearance(recipe.preset);
|
|
686
375
|
if (character.restore(base))
|
|
687
376
|
throw new Error('Invalid character appearance');
|
|
688
377
|
}
|
|
689
378
|
for (const key of Object.keys(category)) {
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
authoredParts[key] ??
|
|
693
|
-
recipe.selections?.[key];
|
|
694
|
-
// Virtual choices replace copied prepared meshes in the worker. The
|
|
695
|
-
// original engine still selects its own valid backing geometry.
|
|
696
|
-
const value =
|
|
697
|
-
key === 'shape' && MIGRATED_SHAPE_IDS.includes(selected)
|
|
698
|
-
? 'circle'
|
|
699
|
-
: key === 'shape' && selected === 'todd'
|
|
700
|
-
? 'circle'
|
|
701
|
-
: key === 'shape' && selected === 'clippo'
|
|
702
|
-
? 'circle'
|
|
703
|
-
: key === 'eyes' && authoredParts.eyes
|
|
704
|
-
? ['oval', 'round_inset', 'dots', ...NATIVE_PARTS.eyes].find(
|
|
705
|
-
(id) => character.isAvailable(category.eyes, id),
|
|
706
|
-
)
|
|
707
|
-
: key === 'accessory' && authoredParts.accessory
|
|
708
|
-
? 'none'
|
|
709
|
-
: key === 'eyewear' && authoredParts.eyewear
|
|
710
|
-
? 'none'
|
|
711
|
-
: selected;
|
|
712
|
-
if (!value) {
|
|
713
|
-
if (key === 'eyes' && authoredParts.eyes)
|
|
714
|
-
throw new Error(
|
|
715
|
-
`No compatible fitting reference for ${selected} eyes`,
|
|
716
|
-
);
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
379
|
+
const value = recipe.selections?.[key];
|
|
380
|
+
if (!value) continue;
|
|
719
381
|
if (key === 'accessory' && value === 'none') {
|
|
720
382
|
for (const item of module.catalog(4))
|
|
721
383
|
if (character.isSelected(4, item.id)) character.select(4, item.id);
|
|
@@ -728,10 +390,8 @@ async function createLiveCharacter(
|
|
|
728
390
|
throw new Error(`Unsupported character selection: ${key}`);
|
|
729
391
|
}
|
|
730
392
|
const patch =
|
|
731
|
-
|
|
732
|
-
(recipe.bodyColor
|
|
733
|
-
? { bodyColor: recipe.bodyColor ?? implicitBodyColor }
|
|
734
|
-
: null);
|
|
393
|
+
props.legacy?.patch ??
|
|
394
|
+
(recipe.bodyColor ? { bodyColor: recipe.bodyColor } : null);
|
|
735
395
|
if (patch) {
|
|
736
396
|
// One authored preset uses an unnamed color. Enter the engine's normal
|
|
737
397
|
// editable palette before replacing that paint with an explicit RGB.
|
|
@@ -769,107 +429,34 @@ async function createLiveCharacter(
|
|
|
769
429
|
const selected = {};
|
|
770
430
|
for (const [key, index] of Object.entries(category)) {
|
|
771
431
|
for (const item of module.catalog(index)) {
|
|
772
|
-
available[`${key}:${item.id}`] =
|
|
773
|
-
|
|
774
|
-
|
|
432
|
+
available[`${key}:${item.id}`] = character.isAvailable(
|
|
433
|
+
index,
|
|
434
|
+
item.id,
|
|
435
|
+
);
|
|
775
436
|
if (character.isSelected(index, item.id)) selected[key] = item.id;
|
|
776
437
|
}
|
|
777
438
|
}
|
|
778
|
-
if (authoredParts.bodyPreset)
|
|
779
|
-
Object.assign(
|
|
780
|
-
selected,
|
|
781
|
-
bodySelectionDefaults(
|
|
782
|
-
authoredParts.bodyPreset,
|
|
783
|
-
decodeAppearance(module.presetAppearance(authoredParts.bodyPreset)),
|
|
784
|
-
),
|
|
785
|
-
);
|
|
786
|
-
for (const key of ['eyes', 'eyewear', 'accessory'])
|
|
787
|
-
if (authoredParts[key]) selected[key] = authoredParts[key];
|
|
788
|
-
available['shape:todd'] = true;
|
|
789
|
-
for (const id of MIGRATED_SHAPE_IDS) available[`shape:${id}`] = true;
|
|
790
|
-
if (MIGRATED_SHAPE_IDS.includes(recipe.selections?.shape))
|
|
791
|
-
selected.shape = recipe.selections.shape;
|
|
792
|
-
available['eyes:cyclops'] = true;
|
|
793
|
-
if (
|
|
794
|
-
recipe.selections?.shape === 'todd' ||
|
|
795
|
-
authoredParts.bodyPreset === 'lime_frog' ||
|
|
796
|
-
(recipe.preset === 'lime_frog' && !recipe.selections?.shape)
|
|
797
|
-
)
|
|
798
|
-
selected.shape = 'todd';
|
|
799
|
-
available['shape:clippo'] = true;
|
|
800
|
-
available['eyes:clippo'] = true;
|
|
801
|
-
available['eyes:todd'] = true;
|
|
802
|
-
if (authoredParts.shape) selected.shape = authoredParts.shape;
|
|
803
|
-
available['accessory:felipe_beret'] = true;
|
|
804
|
-
if (
|
|
805
|
-
authoredParts.eyes ||
|
|
806
|
-
(recipe.preset === 'lime_frog' && !recipe.selections?.eyes)
|
|
807
|
-
)
|
|
808
|
-
selected.eyes = authoredParts.eyes ?? 'todd';
|
|
809
|
-
if (
|
|
810
|
-
authoredParts.accessory ||
|
|
811
|
-
(recipe.preset === 'blue_beret' && !recipe.selections?.accessory)
|
|
812
|
-
)
|
|
813
|
-
selected.accessory = authoredParts.accessory ?? 'felipe_beret';
|
|
814
439
|
available['accessory:none'] = true;
|
|
815
440
|
selected.accessory ??= 'none';
|
|
816
|
-
|
|
817
|
-
? recipe.preset
|
|
818
|
-
: authoredParts.bodyPreset;
|
|
819
|
-
if (
|
|
820
|
-
!recipe.bodyColor &&
|
|
821
|
-
!recipe.selections?.color &&
|
|
822
|
-
ORIGINAL_PRESETS.includes(originalPreset) &&
|
|
823
|
-
!isLegacyRecipe(props)
|
|
824
|
-
) {
|
|
825
|
-
const originalColor = decodeAppearance(
|
|
826
|
-
module.presetAppearance(originalPreset),
|
|
827
|
-
).color;
|
|
828
|
-
if (
|
|
829
|
-
!module
|
|
830
|
-
.catalog(category.color)
|
|
831
|
-
.some((item) => item.id === originalColor)
|
|
832
|
-
) {
|
|
833
|
-
delete selected.color;
|
|
834
|
-
selected.bodyColor = originalBodyColor(module, originalPreset);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
if (recipe.bodyColor) {
|
|
838
|
-
delete selected.color;
|
|
839
|
-
selected.bodyColor = recipe.bodyColor;
|
|
840
|
-
}
|
|
841
|
-
reportCapabilities(available, selected);
|
|
441
|
+
callbacks.onCapabilities?.({ key: fingerprint, available, selected });
|
|
842
442
|
dirty = true;
|
|
843
443
|
}
|
|
844
444
|
};
|
|
845
|
-
let deferredUpdate;
|
|
846
445
|
const update = (next, silent = false) => {
|
|
847
446
|
if (disposed) return;
|
|
848
|
-
if (sharedRenderPending()) {
|
|
849
|
-
const queued = Boolean(deferredUpdate);
|
|
850
|
-
deferredUpdate = [next, silent];
|
|
851
|
-
if (!queued)
|
|
852
|
-
void waitForSharedRender().then(() => {
|
|
853
|
-
const value = deferredUpdate;
|
|
854
|
-
deferredUpdate = undefined;
|
|
855
|
-
if (value && !disposed) update(...value);
|
|
856
|
-
});
|
|
857
|
-
return;
|
|
858
|
-
}
|
|
859
447
|
props = next;
|
|
860
|
-
const recipe =
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
)
|
|
872
|
-
reportCapabilities(lastCapabilities.available, lastCapabilities.selected);
|
|
448
|
+
const recipe = props.legacy
|
|
449
|
+
? {
|
|
450
|
+
preset: 'legacy',
|
|
451
|
+
selections: {
|
|
452
|
+
shape: props.legacy.shape ?? 'circle',
|
|
453
|
+
eyes: props.legacy.eyes ?? 'oval',
|
|
454
|
+
...props.legacy.selections,
|
|
455
|
+
},
|
|
456
|
+
...props.legacy.patch,
|
|
457
|
+
}
|
|
458
|
+
: props.config.character;
|
|
459
|
+
const nextFingerprint = JSON.stringify(recipe);
|
|
873
460
|
// Dragging the shared color picker may emit dozens of values per second.
|
|
874
461
|
// Prepare only its latest value while retaining the existing painted frame.
|
|
875
462
|
if (pendingFingerprint !== nextFingerprint) {
|
|
@@ -883,19 +470,13 @@ async function createLiveCharacter(
|
|
|
883
470
|
appearanceTimer = setTimeout(() => {
|
|
884
471
|
if (disposed) return;
|
|
885
472
|
try {
|
|
886
|
-
|
|
887
|
-
if (pendingFingerprint === nextFingerprint)
|
|
888
|
-
applyAppearance(recipe);
|
|
889
|
-
});
|
|
473
|
+
applyAppearance(recipe);
|
|
890
474
|
wake();
|
|
891
475
|
} catch (error) {
|
|
892
476
|
callbacks.onError?.(error);
|
|
893
477
|
}
|
|
894
478
|
}, 80);
|
|
895
|
-
} else
|
|
896
|
-
prepare(() => {
|
|
897
|
-
if (pendingFingerprint === nextFingerprint) applyAppearance(recipe);
|
|
898
|
-
});
|
|
479
|
+
} else applyAppearance(recipe);
|
|
899
480
|
}
|
|
900
481
|
// The recovered engine needs its first appearance rendered with this off.
|
|
901
482
|
// Our scheduler freezes the completed frame instead.
|
|
@@ -939,18 +520,6 @@ async function createLiveCharacter(
|
|
|
939
520
|
dirty = true;
|
|
940
521
|
wake();
|
|
941
522
|
};
|
|
942
|
-
function input(operation) {
|
|
943
|
-
const apply = () => {
|
|
944
|
-
if (disposed) return;
|
|
945
|
-
try {
|
|
946
|
-
operation();
|
|
947
|
-
} catch (error) {
|
|
948
|
-
client.fail(error);
|
|
949
|
-
}
|
|
950
|
-
};
|
|
951
|
-
if (sharedRenderPending()) void waitForSharedRender().then(apply);
|
|
952
|
-
else apply();
|
|
953
|
-
}
|
|
954
523
|
function pointer(event) {
|
|
955
524
|
if (!props.interactive || !animate()) return;
|
|
956
525
|
const phase = {
|
|
@@ -961,11 +530,8 @@ async function createLiveCharacter(
|
|
|
961
530
|
}[event.type];
|
|
962
531
|
if (event.type === 'pointerdown') {
|
|
963
532
|
if (event.button !== 0 || pointerId != null || !event.isPrimary) return;
|
|
964
|
-
attend(3000);
|
|
965
533
|
pointerStart = [event.clientX, event.clientY];
|
|
966
534
|
pointerId = event.pointerId;
|
|
967
|
-
// Capture while the physical pointer is still down. GPU completion may
|
|
968
|
-
// arrive after pointerup, when setPointerCapture would throw instead.
|
|
969
535
|
canvas.setPointerCapture(pointerId);
|
|
970
536
|
}
|
|
971
537
|
if (pointerId != null && pointerId !== event.pointerId) return;
|
|
@@ -975,41 +541,38 @@ async function createLiveCharacter(
|
|
|
975
541
|
localY = (event.clientY - r.top) / r.height;
|
|
976
542
|
const x = fit ? (fit[0] + localX * fit[2]) / canvas.width : localX;
|
|
977
543
|
const y = fit ? (fit[1] + localY * fit[2]) / canvas.height : localY;
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
phase
|
|
982
|
-
pointerStart &&
|
|
983
|
-
Math.hypot(
|
|
984
|
-
event.clientX - pointerStart[0],
|
|
985
|
-
event.clientY - pointerStart[1],
|
|
986
|
-
) < 6;
|
|
987
|
-
input(() => {
|
|
988
|
-
if (activityMode && phase === 1) character.setReadyGaze(x, y, at);
|
|
989
|
-
if (!activityMode) character.pointer(phase, id, x, y, at);
|
|
990
|
-
if (tap) react();
|
|
991
|
-
dirty = true;
|
|
992
|
-
wake();
|
|
993
|
-
});
|
|
544
|
+
if (activityMode && phase === 1)
|
|
545
|
+
character.setReadyGaze(x, y, performance.now() / 1000);
|
|
546
|
+
if (!activityMode)
|
|
547
|
+
character.pointer(phase, event.pointerId, x, y, performance.now() / 1000);
|
|
994
548
|
if (phase === 2 || phase === 3) {
|
|
995
|
-
if (
|
|
549
|
+
if (
|
|
550
|
+
phase === 2 &&
|
|
551
|
+
pointerStart &&
|
|
552
|
+
Math.hypot(
|
|
553
|
+
event.clientX - pointerStart[0],
|
|
554
|
+
event.clientY - pointerStart[1],
|
|
555
|
+
) < 6
|
|
556
|
+
)
|
|
557
|
+
react();
|
|
558
|
+
if (canvas.hasPointerCapture(event.pointerId))
|
|
559
|
+
canvas.releasePointerCapture(event.pointerId);
|
|
996
560
|
pointerId = undefined;
|
|
997
|
-
pointerStart = undefined;
|
|
998
561
|
}
|
|
562
|
+
dirty = true;
|
|
563
|
+
wake();
|
|
999
564
|
}
|
|
1000
565
|
const cancel = () => {
|
|
1001
|
-
|
|
566
|
+
if (pointerId != null)
|
|
567
|
+
character.pointer(3, pointerId, 0.5, 0.5, performance.now() / 1000);
|
|
1002
568
|
pointerId = undefined;
|
|
1003
569
|
pointerStart = undefined;
|
|
1004
|
-
if (id != null)
|
|
1005
|
-
input(() => character.pointer(3, id, 0.5, 0.5, performance.now() / 1000));
|
|
1006
570
|
};
|
|
1007
571
|
const leave = () => {
|
|
1008
|
-
if (pointerId == null)
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
});
|
|
572
|
+
if (pointerId == null) {
|
|
573
|
+
if (activityMode) character.clearReadyGaze(performance.now() / 1000);
|
|
574
|
+
else character.pointer(1, 0, 0.5, 0.5, performance.now() / 1000);
|
|
575
|
+
}
|
|
1013
576
|
};
|
|
1014
577
|
canvas.addEventListener('lostpointercapture', cancel);
|
|
1015
578
|
canvas.addEventListener('pointerleave', leave);
|
|
@@ -1039,14 +602,9 @@ async function createLiveCharacter(
|
|
|
1039
602
|
canvas.removeEventListener('lostpointercapture', cancel);
|
|
1040
603
|
canvas.removeEventListener('pointerleave', leave);
|
|
1041
604
|
window.removeEventListener('blur', cancel);
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
surfaceLease.release();
|
|
1046
|
-
lease?.release();
|
|
1047
|
-
};
|
|
1048
|
-
if (sharedRenderPending()) void waitForSharedRender().then(release);
|
|
1049
|
-
else release();
|
|
605
|
+
character.delete();
|
|
606
|
+
surface.remove();
|
|
607
|
+
lease?.release();
|
|
1050
608
|
if (!instances.size) {
|
|
1051
609
|
document.removeEventListener('visibilitychange', visibility);
|
|
1052
610
|
cancelAnimationFrame(frame);
|
|
@@ -1056,7 +614,6 @@ async function createLiveCharacter(
|
|
|
1056
614
|
try {
|
|
1057
615
|
update(initial);
|
|
1058
616
|
resize();
|
|
1059
|
-
bindController();
|
|
1060
617
|
} catch (error) {
|
|
1061
618
|
dispose();
|
|
1062
619
|
throw error;
|
|
@@ -1071,21 +628,18 @@ async function createLiveCharacter(
|
|
|
1071
628
|
lastReactionKind,
|
|
1072
629
|
lastActivityResult,
|
|
1073
630
|
activityMode,
|
|
1074
|
-
stoppingWork,
|
|
1075
|
-
queuedReaction,
|
|
1076
631
|
ready,
|
|
1077
632
|
pending:
|
|
1078
633
|
dirty ||
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
!scenePrepared(),
|
|
634
|
+
Boolean(character.preparationStats().pending) ||
|
|
635
|
+
character.hasPendingUpdate(),
|
|
1082
636
|
}),
|
|
1083
637
|
};
|
|
1084
638
|
}
|
|
1085
639
|
|
|
1086
640
|
// A geometry cache must never serve a different silhouette under the same
|
|
1087
|
-
// appearance key. Keep the old painted image while
|
|
1088
|
-
async function
|
|
641
|
+
// appearance key. Keep the old painted image while switching contour modules.
|
|
642
|
+
async function createLegacyCharacter(canvas, initial, callbacks) {
|
|
1089
643
|
let controller,
|
|
1090
644
|
shapeKey,
|
|
1091
645
|
revision = 0,
|
|
@@ -1093,12 +647,8 @@ async function createScopedCharacter(canvas, initial, callbacks) {
|
|
|
1093
647
|
latest = initial;
|
|
1094
648
|
const update = async (next, silent = false) => {
|
|
1095
649
|
if (disposed) return;
|
|
1096
|
-
next = withCharacterGeometry(next);
|
|
1097
650
|
latest = next;
|
|
1098
|
-
const key = JSON.stringify(
|
|
1099
|
-
next.legacy?.points ?? null,
|
|
1100
|
-
authoredPartsFor(next),
|
|
1101
|
-
]);
|
|
651
|
+
const key = JSON.stringify(next.legacy.points ?? null);
|
|
1102
652
|
if (shapeKey === key) {
|
|
1103
653
|
controller?.update(next, silent);
|
|
1104
654
|
return;
|
|
@@ -1158,8 +708,7 @@ async function createScopedCharacter(canvas, initial, callbacks) {
|
|
|
1158
708
|
},
|
|
1159
709
|
react: () => controller?.react(),
|
|
1160
710
|
work: () => controller?.work(),
|
|
1161
|
-
diagnostics: () =>
|
|
1162
|
-
controller?.diagnostics() ?? { ready: false, pending: !disposed },
|
|
711
|
+
diagnostics: () => controller?.diagnostics(),
|
|
1163
712
|
};
|
|
1164
713
|
}
|
|
1165
714
|
|
|
@@ -1174,7 +723,16 @@ function createPortrait(canvas, initial, callbacks) {
|
|
|
1174
723
|
cancel;
|
|
1175
724
|
const update = (props) => {
|
|
1176
725
|
if (disposed) return;
|
|
1177
|
-
const pixels =
|
|
726
|
+
const pixels = Math.max(
|
|
727
|
+
64,
|
|
728
|
+
Math.min(
|
|
729
|
+
512,
|
|
730
|
+
Math.round(
|
|
731
|
+
Math.min(canvas.clientWidth, canvas.clientHeight) *
|
|
732
|
+
Math.min(devicePixelRatio || 1, 2),
|
|
733
|
+
),
|
|
734
|
+
),
|
|
735
|
+
);
|
|
1178
736
|
const key = JSON.stringify([
|
|
1179
737
|
props.config.character,
|
|
1180
738
|
props.legacy,
|
|
@@ -1220,18 +778,6 @@ function createPortrait(canvas, initial, callbacks) {
|
|
|
1220
778
|
canvas,
|
|
1221
779
|
{ ...props, paused: true, interactive: false },
|
|
1222
780
|
{
|
|
1223
|
-
onPreparationStart() {
|
|
1224
|
-
if (finished || !current()) return;
|
|
1225
|
-
// Waiting for another avatar's preparation is not engine work.
|
|
1226
|
-
// Start the deadline only after this controller is constructed.
|
|
1227
|
-
timer = setTimeout(() => {
|
|
1228
|
-
if (current())
|
|
1229
|
-
callbacks.onError?.(
|
|
1230
|
-
new Error('Character preview preparation timed out'),
|
|
1231
|
-
);
|
|
1232
|
-
finish();
|
|
1233
|
-
}, 20000);
|
|
1234
|
-
},
|
|
1235
781
|
onCapabilities(value) {
|
|
1236
782
|
capabilities = value;
|
|
1237
783
|
if (current()) callbacks.onCapabilities?.(value);
|
|
@@ -1259,7 +805,14 @@ function createPortrait(canvas, initial, callbacks) {
|
|
|
1259
805
|
if (finished || !current()) {
|
|
1260
806
|
controller.dispose();
|
|
1261
807
|
finish();
|
|
1262
|
-
}
|
|
808
|
+
} else
|
|
809
|
+
timer = setTimeout(() => {
|
|
810
|
+
if (current())
|
|
811
|
+
callbacks.onError?.(
|
|
812
|
+
new Error('Character preview preparation timed out'),
|
|
813
|
+
);
|
|
814
|
+
finish();
|
|
815
|
+
}, 20000);
|
|
1263
816
|
await completion;
|
|
1264
817
|
} catch (error) {
|
|
1265
818
|
if (current()) callbacks.onError?.(error);
|
|
@@ -1288,12 +841,10 @@ const registryKey = Symbol.for('bloom.character.runtimes');
|
|
|
1288
841
|
export function runtimeStats() {
|
|
1289
842
|
return {
|
|
1290
843
|
renderMilliseconds: lastRenderMilliseconds,
|
|
1291
|
-
renderWaitMilliseconds: lastRenderWaitMilliseconds,
|
|
1292
844
|
budget: managedAvatarStats(),
|
|
1293
|
-
surface: sharedSurfaceStats(),
|
|
1294
845
|
instances: instances.size,
|
|
1295
846
|
active: clients.size,
|
|
1296
|
-
scheduled: !!frame
|
|
847
|
+
scheduled: !!frame,
|
|
1297
848
|
cachedPortraits: portraits.size,
|
|
1298
849
|
legacy: legacyEngineStats(),
|
|
1299
850
|
};
|