@promptbook/remote-server 0.112.0-99 → 0.113.0-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.
Files changed (170) hide show
  1. package/README.md +76 -44
  2. package/esm/index.es.js +1801 -98
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/src/_packages/components.index.d.ts +4 -0
  5. package/esm/src/_packages/core.index.d.ts +22 -2
  6. package/esm/src/_packages/node.index.d.ts +40 -0
  7. package/esm/src/_packages/types.index.d.ts +20 -0
  8. package/esm/src/avatars/avatarAnimationScheduler.d.ts +4 -0
  9. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  10. package/esm/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
  11. package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
  12. package/esm/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
  13. package/esm/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
  14. package/esm/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
  15. package/esm/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
  16. package/esm/src/book-3.0/CliAgent.d.ts +66 -0
  17. package/esm/src/book-3.0/CliAgent.test.d.ts +1 -0
  18. package/esm/src/book-3.0/LiteAgent.d.ts +68 -0
  19. package/esm/src/book-3.0/LiteAgent.test.d.ts +1 -0
  20. package/esm/src/book-3.0/agentFolderPaths.d.ts +30 -0
  21. package/esm/src/book-3.0/cliAgentEnv.d.ts +33 -0
  22. package/esm/src/book-components/BookEditor/BookEditor.d.ts +6 -5
  23. package/esm/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
  24. package/esm/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
  25. package/esm/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
  26. package/esm/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +2 -0
  27. package/esm/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
  28. package/esm/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
  29. package/esm/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
  30. package/esm/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
  31. package/esm/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
  32. package/esm/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
  33. package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
  34. package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
  35. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  36. package/esm/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
  37. package/esm/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
  38. package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
  39. package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
  40. package/esm/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  41. package/esm/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
  42. package/esm/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
  43. package/esm/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
  44. package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
  45. package/esm/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
  46. package/esm/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
  47. package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
  48. package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
  49. package/esm/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
  50. package/esm/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
  51. package/esm/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
  52. package/esm/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
  53. package/esm/src/book-components/_common/Modal/Modal.d.ts +1 -1
  54. package/esm/src/book-components/icons/AboutIcon.d.ts +1 -1
  55. package/esm/src/book-components/icons/DownloadIcon.d.ts +1 -1
  56. package/esm/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
  57. package/esm/src/book-components/icons/FullscreenIcon.d.ts +1 -1
  58. package/esm/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
  59. package/esm/src/cli/cli-commands/agent/chat.d.ts +10 -0
  60. package/esm/src/cli/cli-commands/agent/exec.d.ts +10 -0
  61. package/esm/src/cli/cli-commands/agent/run.test.d.ts +1 -0
  62. package/esm/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
  63. package/esm/src/cli/cli-commands/agent.d.ts +14 -0
  64. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +23 -1
  65. package/esm/src/cli/cli-commands/agents-server/run.d.ts +6 -0
  66. package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
  67. package/esm/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
  68. package/esm/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
  69. package/esm/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
  70. package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
  71. package/esm/src/cli/cli-commands/coder/server.d.ts +13 -0
  72. package/esm/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
  73. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
  74. package/esm/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
  75. package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
  76. package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
  77. package/esm/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
  78. package/esm/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
  79. package/esm/src/commitments/index.d.ts +2 -1
  80. package/esm/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
  81. package/esm/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
  82. package/esm/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
  83. package/esm/src/utils/isTimingSafeEqualString.d.ts +25 -0
  84. package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
  85. package/esm/src/version.d.ts +1 -1
  86. package/package.json +2 -2
  87. package/umd/index.umd.js +1802 -96
  88. package/umd/index.umd.js.map +1 -1
  89. package/umd/src/_packages/components.index.d.ts +4 -0
  90. package/umd/src/_packages/core.index.d.ts +22 -2
  91. package/umd/src/_packages/node.index.d.ts +40 -0
  92. package/umd/src/_packages/types.index.d.ts +20 -0
  93. package/umd/src/avatars/avatarAnimationScheduler.d.ts +4 -0
  94. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  95. package/umd/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
  96. package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
  97. package/umd/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
  98. package/umd/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
  99. package/umd/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
  100. package/umd/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
  101. package/umd/src/book-3.0/CliAgent.d.ts +66 -0
  102. package/umd/src/book-3.0/CliAgent.test.d.ts +1 -0
  103. package/umd/src/book-3.0/LiteAgent.d.ts +68 -0
  104. package/umd/src/book-3.0/LiteAgent.test.d.ts +1 -0
  105. package/umd/src/book-3.0/agentFolderPaths.d.ts +30 -0
  106. package/umd/src/book-3.0/cliAgentEnv.d.ts +33 -0
  107. package/umd/src/book-components/BookEditor/BookEditor.d.ts +6 -5
  108. package/umd/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
  109. package/umd/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
  110. package/umd/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
  111. package/umd/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +2 -0
  112. package/umd/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
  113. package/umd/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
  114. package/umd/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
  115. package/umd/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
  116. package/umd/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
  117. package/umd/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
  118. package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
  119. package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
  120. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  121. package/umd/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
  122. package/umd/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
  123. package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
  124. package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
  125. package/umd/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  126. package/umd/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
  127. package/umd/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
  128. package/umd/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
  129. package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
  130. package/umd/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
  131. package/umd/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
  132. package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
  133. package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
  134. package/umd/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
  135. package/umd/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
  136. package/umd/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
  137. package/umd/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
  138. package/umd/src/book-components/_common/Modal/Modal.d.ts +1 -1
  139. package/umd/src/book-components/icons/AboutIcon.d.ts +1 -1
  140. package/umd/src/book-components/icons/DownloadIcon.d.ts +1 -1
  141. package/umd/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
  142. package/umd/src/book-components/icons/FullscreenIcon.d.ts +1 -1
  143. package/umd/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
  144. package/umd/src/cli/cli-commands/agent/chat.d.ts +10 -0
  145. package/umd/src/cli/cli-commands/agent/exec.d.ts +10 -0
  146. package/umd/src/cli/cli-commands/agent/run.test.d.ts +1 -0
  147. package/umd/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
  148. package/umd/src/cli/cli-commands/agent.d.ts +14 -0
  149. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +23 -1
  150. package/umd/src/cli/cli-commands/agents-server/run.d.ts +6 -0
  151. package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
  152. package/umd/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
  153. package/umd/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
  154. package/umd/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
  155. package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
  156. package/umd/src/cli/cli-commands/coder/server.d.ts +13 -0
  157. package/umd/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
  158. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
  159. package/umd/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
  160. package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
  161. package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
  162. package/umd/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
  163. package/umd/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
  164. package/umd/src/commitments/index.d.ts +2 -1
  165. package/umd/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
  166. package/umd/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
  167. package/umd/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
  168. package/umd/src/utils/isTimingSafeEqualString.d.ts +25 -0
  169. package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
  170. package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js CHANGED
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
2
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('path'), require('child_process'), require('colors'), require('spacetrim'), require('waitasecond'), require('http'), require('express'), require('socket.io'), require('fs/promises'), require('@mozilla/readability'), require('jsdom'), require('crypto-js'), require('crypto-js/enc-hex'), require('showdown'), require('express-openapi-validator'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('rxjs'), require('crypto'), require('moment'), require('mime-types'), require('crypto-js/sha256'), require('papaparse'), require('@openai/agents'), require('bottleneck'), require('openai')) :
3
3
  typeof define === 'function' && define.amd ? define(['exports', 'path', 'child_process', 'colors', 'spacetrim', 'waitasecond', 'http', 'express', 'socket.io', 'fs/promises', '@mozilla/readability', 'jsdom', 'crypto-js', 'crypto-js/enc-hex', 'showdown', 'express-openapi-validator', 'swagger-ui-express', 'react', 'react-dom/server', 'rxjs', 'crypto', 'moment', 'mime-types', 'crypto-js/sha256', 'papaparse', '@openai/agents', 'bottleneck', 'openai'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-remote-server"] = {}, global.path, global.child_process, global.colors, global._spaceTrim, global.waitasecond, global.http, global.express, global.socket_io, global.promises, global.readability, global.jsdom, global.cryptoJs, global.hexEncoder, global.showdown, global.OpenApiValidator, global.swaggerUi, global.react, global.server, global.rxjs, global.crypto, global.moment, global.mimeTypes, global.sha256, global.papaparse, global.agents, global.Bottleneck, global.OpenAI));
5
- })(this, (function (exports, path, child_process, colors, _spaceTrim, waitasecond, http, express, socket_io, promises, readability, jsdom, cryptoJs, hexEncoder, showdown, OpenApiValidator, swaggerUi, react, server, rxjs, crypto, moment, mimeTypes, sha256, papaparse, agents, Bottleneck, OpenAI) { 'use strict';
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-remote-server"] = {}, global.path, global.child_process, global.colors, global._spaceTrim, global.waitasecond, global.http, global.express, global.socket_io, global.promises, global.readability, global.jsdom, global.CryptoJS, global.hexEncoder, global.showdown, global.OpenApiValidator, global.swaggerUi, global.react, global.server, global.rxjs, null, global.moment, global.mimeTypes, global.sha256, global.papaparse, global.agents, global.Bottleneck, global.OpenAI));
5
+ })(this, (function (exports, path, child_process, colors, _spaceTrim, waitasecond, http, express, socket_io, promises, readability, jsdom, CryptoJS, hexEncoder, showdown, OpenApiValidator, swaggerUi, react, server, rxjs, crypto, moment, mimeTypes, sha256, papaparse, agents, Bottleneck, OpenAI) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
@@ -28,11 +28,14 @@
28
28
  var _spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(_spaceTrim);
29
29
  var http__default = /*#__PURE__*/_interopDefaultLegacy(http);
30
30
  var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
31
+ var CryptoJS__default = /*#__PURE__*/_interopDefaultLegacy(CryptoJS);
31
32
  var hexEncoder__default = /*#__PURE__*/_interopDefaultLegacy(hexEncoder);
33
+ var showdown__default = /*#__PURE__*/_interopDefaultLegacy(showdown);
32
34
  var OpenApiValidator__namespace = /*#__PURE__*/_interopNamespace(OpenApiValidator);
33
35
  var swaggerUi__default = /*#__PURE__*/_interopDefaultLegacy(swaggerUi);
34
36
  var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);
35
37
  var sha256__default = /*#__PURE__*/_interopDefaultLegacy(sha256);
38
+ var papaparse__default = /*#__PURE__*/_interopDefaultLegacy(papaparse);
36
39
  var Bottleneck__default = /*#__PURE__*/_interopDefaultLegacy(Bottleneck);
37
40
  var OpenAI__default = /*#__PURE__*/_interopDefaultLegacy(OpenAI);
38
41
 
@@ -50,7 +53,7 @@
50
53
  * @generated
51
54
  * @see https://github.com/webgptorg/promptbook
52
55
  */
53
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-99';
56
+ const PROMPTBOOK_ENGINE_VERSION = '0.113.0-0';
54
57
  /**
55
58
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
56
59
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1958,7 +1961,7 @@
1958
1961
  * @private internal helper function
1959
1962
  */
1960
1963
  function $randomToken(randomness) {
1961
- return crypto.randomBytes(randomness).toString('hex');
1964
+ return CryptoJS__default["default"].lib.WordArray.random(randomness).toString(CryptoJS__default["default"].enc.Hex);
1962
1965
  }
1963
1966
  // TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
1964
1967
  // TODO: Maybe use nanoid instead https://github.com/ai/nanoid
@@ -5502,7 +5505,7 @@
5502
5505
  * @public exported from `@promptbook/editable`
5503
5506
  */
5504
5507
  function knowledgeSourceContentToName(knowledgeSourceContent) {
5505
- const hash = cryptoJs.SHA256(hexEncoder__default["default"].parse(JSON.stringify(knowledgeSourceContent)))
5508
+ const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(JSON.stringify(knowledgeSourceContent)))
5506
5509
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
5507
5510
  .toString( /* hex */)
5508
5511
  .substring(0, 20);
@@ -6496,7 +6499,7 @@
6496
6499
  console.warn('CSV string contains carriage return characters, but in the CSV settings the `newline` setting does not include them. Autohealing the CSV string.');
6497
6500
  value = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
6498
6501
  }
6499
- const csv = papaparse.parse(value, settings);
6502
+ const csv = papaparse__default["default"].parse(value, settings);
6500
6503
  return csv;
6501
6504
  }
6502
6505
 
@@ -6581,10 +6584,10 @@
6581
6584
  i > index ? { ...row, [outputParameterName]: PENDING_VALUE_PLACEHOLDER } : row,
6582
6585
  );
6583
6586
  */
6584
- await onProgress(papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
6587
+ await onProgress(papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
6585
6588
  }
6586
6589
  }
6587
- return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6590
+ return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6588
6591
  },
6589
6592
  },
6590
6593
  {
@@ -6612,7 +6615,7 @@
6612
6615
  return /* not await */ mapCallback({ [key]: value }, index, array.length);
6613
6616
  }));
6614
6617
  }));
6615
- return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6618
+ return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
6616
6619
  },
6617
6620
  },
6618
6621
  ],
@@ -9269,7 +9272,8 @@
9269
9272
  * @private internal function of `$registeredLlmToolsMessage`
9270
9273
  */
9271
9274
  function createUsedEnvMessage() {
9272
- return `Unknown \`.env\` file` ;
9275
+ return `Unknown \`.env\` file`
9276
+ ;
9273
9277
  }
9274
9278
  // TODO: [®] DRY Register logic
9275
9279
  // TODO: [🧠][⚛] Maybe pass env as argument
@@ -9541,7 +9545,7 @@
9541
9545
  const { filename: sourceFilename, url } = source;
9542
9546
  const { rootDirname, cacheDirname, intermediateFilesStrategy, extension, isVerbose } = options;
9543
9547
  // TODO: [👬] DRY
9544
- const hash = cryptoJs.SHA256(
9548
+ const hash = CryptoJS__default["default"].SHA256(
9545
9549
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
9546
9550
  hexEncoder__default["default"].parse(sourceFilename || url || 'untitled'))
9547
9551
  .toString( /* hex */)
@@ -9632,7 +9636,7 @@
9632
9636
  * @private utility of `WebsiteScraper`
9633
9637
  */
9634
9638
  function createShowdownConverter() {
9635
- return new showdown.Converter({
9639
+ return new showdown__default["default"].Converter({
9636
9640
  flavor: 'github',
9637
9641
  /*
9638
9642
  > import showdownHighlight from 'showdown-highlight';
@@ -10824,7 +10828,7 @@
10824
10828
  * @public exported from `@promptbook/utils`
10825
10829
  */
10826
10830
  function computeHash(value) {
10827
- return cryptoJs.SHA256(hexEncoder__default["default"].parse(_spaceTrim.spaceTrim(valueToString(value)))).toString( /* hex */);
10831
+ return CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(_spaceTrim.spaceTrim(valueToString(value)))).toString( /* hex */);
10828
10832
  }
10829
10833
  // TODO: [🥬][🥬] Use this ACRY
10830
10834
 
@@ -11066,6 +11070,11 @@
11066
11070
  * - `isValidAgentUrl` *(this one)* which tests just agent URL
11067
11071
  * - `isValidPipelineUrl` which tests just pipeline URL
11068
11072
  *
11073
+ * Note: This is a pure structural validator and does not block private/internal network
11074
+ * addresses. Callers that fetch the URL server-side from an untrusted network context must
11075
+ * additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
11076
+ * outbound request.
11077
+ *
11069
11078
  * @public exported from `@promptbook/utils`
11070
11079
  */
11071
11080
  function isValidAgentUrl(url) {
@@ -11079,12 +11088,6 @@
11079
11088
  // TODO: [🐠]
11080
11089
  return false;
11081
11090
  }
11082
- /*
11083
- Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
11084
- if (isUrlOnPrivateNetwork(url)) {
11085
- return false;
11086
- }
11087
- */
11088
11091
  return true;
11089
11092
  }
11090
11093
  // TODO: [🐠] Maybe more info why the URL is invalid
@@ -11260,8 +11263,11 @@
11260
11263
  if (!trimmedContent) {
11261
11264
  return requirements;
11262
11265
  }
11263
- // Add goal as a proper h2 section to the system message
11264
- const goalSection = `## Goal\n\n${trimmedContent}`;
11266
+ const goalSection = _spaceTrim.spaceTrim((block) => `
11267
+ ## Goal
11268
+
11269
+ ${block(trimmedContent)}
11270
+ `);
11265
11271
  const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
11266
11272
  return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
11267
11273
  }
@@ -11783,8 +11789,11 @@
11783
11789
  if (!trimmedContent) {
11784
11790
  return requirements;
11785
11791
  }
11786
- // Add language as a bullet under a ## Language section
11787
- const languageSection = `## Language\n\n- Your language is ${trimmedContent}`;
11792
+ const languageSection = _spaceTrim.spaceTrim((block) => `
11793
+ ## Language
11794
+
11795
+ - Your language is ${block(trimmedContent)}
11796
+ `);
11788
11797
  return this.appendToSystemMessage(requirements, languageSection, '\n\n');
11789
11798
  }
11790
11799
  }
@@ -14250,21 +14259,22 @@
14250
14259
  * @private helper of `fractalAvatarVisual`
14251
14260
  */
14252
14261
  function drawDragonCurveLayer(context, points, options) {
14253
- const { size, primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
14262
+ const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
14254
14263
  const firstPoint = points[0];
14255
14264
  const lastPoint = points[points.length - 1];
14256
14265
  const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
14257
14266
  ribbonGradient.addColorStop(0, `${primaryColor}f2`);
14258
14267
  ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
14259
14268
  ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
14269
+ // Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
14270
+ // context.filter blur, which triggers a costly software rasterization pass every frame.
14260
14271
  context.save();
14261
14272
  context.beginPath();
14262
14273
  tracePolyline(context, points);
14263
- context.strokeStyle = `${shadowColor}82`;
14264
- context.lineWidth = strokeWidth * 1.8;
14274
+ context.strokeStyle = `${shadowColor}48`;
14275
+ context.lineWidth = strokeWidth * 4.5;
14265
14276
  context.lineJoin = 'round';
14266
14277
  context.lineCap = 'round';
14267
- context.filter = `blur(${size * 0.022}px)`;
14268
14278
  context.stroke();
14269
14279
  context.restore();
14270
14280
  context.beginPath();
@@ -14726,7 +14736,7 @@
14726
14736
  *
14727
14737
  * @private helper of `minecraft2AvatarVisual`
14728
14738
  */
14729
- const LIGHT_DIRECTION$2 = normalizeVector3({
14739
+ const LIGHT_DIRECTION$4 = normalizeVector3({
14730
14740
  x: 0.4,
14731
14741
  y: -0.65,
14732
14742
  z: 0.92,
@@ -14848,11 +14858,23 @@
14848
14858
  * @private helper of `minecraft2AvatarVisual`
14849
14859
  */
14850
14860
  function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
14861
+ const cx = size * 0.5 + interaction.gazeX * size * 0.03;
14862
+ const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
14863
+ const rx = size * (0.16 + interaction.intensity * 0.015);
14864
+ const ry = size * 0.055;
14865
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
14851
14866
  context.save();
14852
- context.fillStyle = `${palette.shadow}66`;
14853
- context.filter = `blur(${size * 0.02}px)`;
14867
+ context.translate(cx, cy);
14868
+ context.scale(1, ry / rx);
14869
+ const blurRadius = rx * 1.4;
14870
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
14871
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
14872
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
14873
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
14874
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
14875
+ context.fillStyle = shadowGradient;
14854
14876
  context.beginPath();
14855
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.03, size * 0.85 + Math.sin(timeMs / 880) * size * 0.01, size * (0.16 + interaction.intensity * 0.015), size * 0.055, 0, 0, Math.PI * 2);
14877
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
14856
14878
  context.fill();
14857
14879
  context.restore();
14858
14880
  }
@@ -14938,7 +14960,7 @@
14938
14960
  corners: projectedCorners,
14939
14961
  texture: faceDefinition.texture,
14940
14962
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
14941
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
14963
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
14942
14964
  outlineColor: cuboid.outlineColor,
14943
14965
  };
14944
14966
  });
@@ -15076,13 +15098,27 @@
15076
15098
  spotlight.addColorStop(1, `${palette.highlight}00`);
15077
15099
  context.fillStyle = spotlight;
15078
15100
  context.fillRect(0, 0, size, size);
15079
- context.save();
15080
- context.fillStyle = 'rgba(0, 0, 0, 0.22)';
15081
- context.filter = `blur(${size * 0.018}px)`;
15082
- context.beginPath();
15083
- context.ellipse(size * 0.5, size * 0.86, size * 0.2, size * 0.06, 0, 0, Math.PI * 2);
15084
- context.fill();
15085
- context.restore();
15101
+ {
15102
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
15103
+ const cx = size * 0.5;
15104
+ const cy = size * 0.86;
15105
+ const rx = size * 0.2;
15106
+ const ry = size * 0.06;
15107
+ const blurRadius = rx * 1.4;
15108
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
15109
+ shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
15110
+ shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
15111
+ shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
15112
+ shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
15113
+ context.save();
15114
+ context.translate(cx, cy);
15115
+ context.scale(1, ry / rx);
15116
+ context.fillStyle = shadowGradient;
15117
+ context.beginPath();
15118
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
15119
+ context.fill();
15120
+ context.restore();
15121
+ }
15086
15122
  drawVoxelCuboid(context, {
15087
15123
  x: bodyX,
15088
15124
  y: bodyY,
@@ -15611,7 +15647,7 @@
15611
15647
  *
15612
15648
  * @private helper of `octopus3AvatarVisual`
15613
15649
  */
15614
- function formatAlphaHex$1(opacity) {
15650
+ function formatAlphaHex$2(opacity) {
15615
15651
  return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
15616
15652
  .toString(16)
15617
15653
  .padStart(2, '0');
@@ -15990,7 +16026,7 @@
15990
16026
  context.beginPath();
15991
16027
  context.moveTo(-radiusX * 0.74, radiusY * 0.2);
15992
16028
  context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
15993
- context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
16029
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
15994
16030
  context.lineWidth = radiusX * 0.08;
15995
16031
  context.lineCap = 'round';
15996
16032
  context.stroke();
@@ -16071,7 +16107,7 @@
16071
16107
  context.beginPath();
16072
16108
  context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
16073
16109
  context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
16074
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
16110
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
16075
16111
  context.lineWidth = projectedRadiusX * 0.08;
16076
16112
  context.lineCap = 'round';
16077
16113
  context.stroke();
@@ -16117,7 +16153,7 @@
16117
16153
  *
16118
16154
  * @private helper of the 3D octopus avatar visuals
16119
16155
  */
16120
- function formatAlphaHex(opacity) {
16156
+ function formatAlphaHex$1(opacity) {
16121
16157
  return Math.round(clampNumber$1(opacity, 0, 1) * 255)
16122
16158
  .toString(16)
16123
16159
  .padStart(2, '0');
@@ -16129,11 +16165,40 @@
16129
16165
  *
16130
16166
  * @private helper of `octopus3dAvatarVisual`
16131
16167
  */
16132
- const LIGHT_DIRECTION$1 = normalizeVector3({
16168
+ const LIGHT_DIRECTION$3 = normalizeVector3({
16133
16169
  x: 0.48,
16134
16170
  y: -0.62,
16135
16171
  z: 0.94,
16136
16172
  });
16173
+ /**
16174
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
16175
+ *
16176
+ * @private helper of `octopus3dAvatarVisual`
16177
+ */
16178
+ const octopus3dStableStateCache = new WeakMap();
16179
+ /**
16180
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
16181
+ *
16182
+ * @private helper of `octopus3dAvatarVisual`
16183
+ */
16184
+ function getOctopus3dStableState(createRandom) {
16185
+ const cached = octopus3dStableStateCache.get(createRandom);
16186
+ if (cached !== undefined) {
16187
+ return cached;
16188
+ }
16189
+ const animationRandom = createRandom('octopus3d-animation-profile');
16190
+ const eyeRandom = createRandom('octopus3d-eye-profile');
16191
+ const leftEyePhaseOffset = eyeRandom() * 0.6;
16192
+ const rightEyePhaseOffset = eyeRandom() * 0.6;
16193
+ const state = {
16194
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
16195
+ animationPhase: animationRandom() * Math.PI * 2,
16196
+ leftEyePhaseOffset,
16197
+ rightEyePhaseOffset,
16198
+ };
16199
+ octopus3dStableStateCache.set(createRandom, state);
16200
+ return state;
16201
+ }
16137
16202
  /**
16138
16203
  * Proper 3D Octopus visual built from projected organic meshes and tentacles.
16139
16204
  *
@@ -16146,10 +16211,7 @@
16146
16211
  isAnimated: true,
16147
16212
  supportsPointerTracking: true,
16148
16213
  render({ context, size, palette, createRandom, timeMs, interaction }) {
16149
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
16150
- const animationRandom = createRandom('octopus3d-animation-profile');
16151
- const eyeRandom = createRandom('octopus3d-eye-profile');
16152
- const animationPhase = animationRandom() * Math.PI * 2;
16214
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
16153
16215
  const sceneCenterX = size * 0.5;
16154
16216
  const sceneCenterY = size * 0.56;
16155
16217
  const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
@@ -16246,12 +16308,12 @@
16246
16308
  x: -faceEyeSpacing,
16247
16309
  y: faceEyeYOffset,
16248
16310
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
16249
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
16311
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16250
16312
  drawProjectedOrganicEye(context, {
16251
16313
  x: faceEyeSpacing,
16252
16314
  y: faceEyeYOffset,
16253
16315
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
16254
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
16316
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16255
16317
  drawProjectedOrganicMouth(context, [
16256
16318
  {
16257
16319
  x: -mouthHalfWidth,
@@ -16295,14 +16357,28 @@
16295
16357
  /**
16296
16358
  * Draws the soft ground shadow below the octopus.
16297
16359
  *
16360
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
16361
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
16362
+ *
16298
16363
  * @private helper of `octopus3dAvatarVisual`
16299
16364
  */
16300
16365
  function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
16366
+ const cx = size * 0.5 + interaction.gazeX * size * 0.04;
16367
+ const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
16368
+ const rx = size * (0.18 + interaction.intensity * 0.02);
16369
+ const ry = size * 0.06;
16301
16370
  context.save();
16302
- context.fillStyle = `${palette.shadow}66`;
16303
- context.filter = `blur(${size * 0.022}px)`;
16371
+ context.translate(cx, cy);
16372
+ context.scale(1, ry / rx);
16373
+ const blurRadius = rx * 1.4;
16374
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
16375
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
16376
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
16377
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
16378
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
16379
+ context.fillStyle = shadowGradient;
16304
16380
  context.beginPath();
16305
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.04, size * 0.87 + Math.sin(timeMs / 920) * size * 0.008, size * (0.18 + interaction.intensity * 0.02), size * 0.06, 0, 0, Math.PI * 2);
16381
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
16306
16382
  context.fill();
16307
16383
  context.restore();
16308
16384
  }
@@ -16339,7 +16415,7 @@
16339
16415
  corners: projectedCorners,
16340
16416
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
16341
16417
  transformedCorners.length,
16342
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
16418
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
16343
16419
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
16344
16420
  outlineColor,
16345
16421
  });
@@ -16528,11 +16604,40 @@
16528
16604
  *
16529
16605
  * @private helper of `octopus3d2AvatarVisual`
16530
16606
  */
16531
- const LIGHT_DIRECTION = normalizeVector3({
16607
+ const LIGHT_DIRECTION$2 = normalizeVector3({
16532
16608
  x: 0.38,
16533
16609
  y: -0.6,
16534
16610
  z: 0.98,
16535
16611
  });
16612
+ /**
16613
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
16614
+ *
16615
+ * @private helper of `octopus3d2AvatarVisual`
16616
+ */
16617
+ const octopus3d2StableStateCache = new WeakMap();
16618
+ /**
16619
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
16620
+ *
16621
+ * @private helper of `octopus3d2AvatarVisual`
16622
+ */
16623
+ function getOctopus3d2StableState(createRandom) {
16624
+ const cached = octopus3d2StableStateCache.get(createRandom);
16625
+ if (cached !== undefined) {
16626
+ return cached;
16627
+ }
16628
+ const animationRandom = createRandom('octopus3d2-animation-profile');
16629
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
16630
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
16631
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
16632
+ const state = {
16633
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
16634
+ animationPhase: animationRandom() * Math.PI * 2,
16635
+ leftEyePhaseOffset,
16636
+ rightEyePhaseOffset,
16637
+ };
16638
+ octopus3d2StableStateCache.set(createRandom, state);
16639
+ return state;
16640
+ }
16536
16641
  /**
16537
16642
  * Octopus 3D 2 avatar visual.
16538
16643
  *
@@ -16545,10 +16650,7 @@
16545
16650
  isAnimated: true,
16546
16651
  supportsPointerTracking: true,
16547
16652
  render({ context, size, palette, createRandom, timeMs, interaction }) {
16548
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
16549
- const animationRandom = createRandom('octopus3d2-animation-profile');
16550
- const eyeRandom = createRandom('octopus3d2-eye-profile');
16551
- const animationPhase = animationRandom() * Math.PI * 2;
16653
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
16552
16654
  const sceneCenterX = size * 0.5;
16553
16655
  const sceneCenterY = size * 0.575;
16554
16656
  const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
@@ -16601,8 +16703,8 @@
16601
16703
  const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
16602
16704
  const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
16603
16705
  const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
16604
- drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
16605
- drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
16706
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16707
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
16606
16708
  drawProjectedOrganicMouth(context, [
16607
16709
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
16608
16710
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -16631,54 +16733,118 @@
16631
16733
  /**
16632
16734
  * Draws the soft floor shadow that anchors the single mesh in the frame.
16633
16735
  *
16736
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
16737
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
16738
+ *
16634
16739
  * @private helper of `octopus3d2AvatarVisual`
16635
16740
  */
16636
16741
  function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
16742
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
16743
+ const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
16744
+ const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
16745
+ const ry = size * 0.062;
16637
16746
  context.save();
16638
- context.fillStyle = `${palette.shadow}66`;
16639
- context.filter = `blur(${size * 0.024}px)`;
16747
+ context.translate(cx, cy);
16748
+ context.scale(1, ry / rx);
16749
+ const blurRadius = rx * 1.4;
16750
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
16751
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
16752
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
16753
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
16754
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
16755
+ context.fillStyle = shadowGradient;
16640
16756
  context.beginPath();
16641
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.045, size * 0.88 + Math.sin(timeMs / 940) * size * 0.008, size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018), size * 0.062, 0, 0, Math.PI * 2);
16757
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
16642
16758
  context.fill();
16643
16759
  context.restore();
16644
16760
  }
16761
+ /**
16762
+ * Number of latitude segments used by the single blobby octopus mesh.
16763
+ *
16764
+ * @private helper of `octopus3d2AvatarVisual`
16765
+ */
16766
+ const LATITUDE_PATCH_COUNT$2 = 12;
16767
+ /**
16768
+ * Number of longitude segments used by the single blobby octopus mesh.
16769
+ *
16770
+ * @private helper of `octopus3d2AvatarVisual`
16771
+ */
16772
+ const LONGITUDE_PATCH_COUNT$2 = 24;
16645
16773
  /**
16646
16774
  * Resolves all visible projected patches for the single blobby octopus mesh.
16647
16775
  *
16776
+ * Within a single frame, mesh corner samples and longitude-only lobe-wave values are
16777
+ * quantized to the patch grid and computed once each rather than re-evaluated for every
16778
+ * patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
16779
+ * `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
16780
+ * corners are shared between neighboring patches.
16781
+ *
16648
16782
  * @private helper of `octopus3d2AvatarVisual`
16649
16783
  */
16650
16784
  function resolveVisibleBlobbyOctopusPatches(options) {
16651
16785
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
16652
- const latitudePatchCount = 12;
16653
- const longitudePatchCount = 24;
16786
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
16787
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
16654
16788
  const surfacePatches = [];
16789
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
16790
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
16791
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
16792
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
16793
+ }
16794
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
16795
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
16796
+ }
16797
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
16798
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
16799
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
16800
+ }
16801
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
16802
+ const transformedCornerSamples = new Array(cornerCount);
16803
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
16804
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
16805
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
16806
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
16807
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
16808
+ const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
16809
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
16810
+ }
16811
+ }
16655
16812
  for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
16656
- const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
16657
- const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
16813
+ const startLatitude = latitudeBoundaries[latitudeIndex];
16814
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
16658
16815
  const centerLatitude = (startLatitude + endLatitude) / 2;
16659
16816
  const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
16817
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
16818
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
16660
16819
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
16661
- const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
16662
- const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
16820
+ const startLongitude = longitudeBoundaries[longitudeIndex];
16821
+ const endLongitude = longitudeBoundaries[longitudeIndex + 1];
16663
16822
  const centerLongitude = (startLongitude + endLongitude) / 2;
16664
- const localCorners = [
16665
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
16666
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
16667
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
16668
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
16823
+ const transformedCorners = [
16824
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
16825
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
16826
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
16827
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
16669
16828
  ];
16670
- const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
16671
16829
  const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
16672
16830
  if (surfaceNormal.z <= 0.01) {
16673
16831
  continue;
16674
16832
  }
16675
- const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
16833
+ const projectedCorners = [
16834
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
16835
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
16836
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
16837
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
16838
+ ];
16676
16839
  surfacePatches.push({
16677
16840
  corners: projectedCorners,
16678
- averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
16679
- transformedCorners.length,
16680
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
16681
- fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
16841
+ averageDepth: (transformedCorners[0].z +
16842
+ transformedCorners[1].z +
16843
+ transformedCorners[2].z +
16844
+ transformedCorners[3].z) /
16845
+ 4,
16846
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
16847
+ fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
16682
16848
  outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
16683
16849
  });
16684
16850
  }
@@ -16694,12 +16860,21 @@
16694
16860
  * @private helper of `octopus3d2AvatarVisual`
16695
16861
  */
16696
16862
  function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
16863
+ const { morphologyProfile, animationPhase, timeMs } = options;
16864
+ return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
16865
+ }
16866
+ /**
16867
+ * Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
16868
+ * to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
16869
+ *
16870
+ * @private helper of `octopus3d2AvatarVisual`
16871
+ */
16872
+ function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
16697
16873
  const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
16698
16874
  const cosineLatitude = Math.max(0, Math.cos(latitude));
16699
16875
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
16700
16876
  const upperBlend = Math.pow(1 - verticalProgress, 1.2);
16701
16877
  const lowerBlend = Math.pow(verticalProgress, 1.42);
16702
- const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
16703
16878
  const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
16704
16879
  const horizontalScale = 1.02 +
16705
16880
  skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
@@ -16737,7 +16912,7 @@
16737
16912
  *
16738
16913
  * @private helper of `octopus3d2AvatarVisual`
16739
16914
  */
16740
- function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
16915
+ function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
16741
16916
  const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
16742
16917
  if (tonalProgress < 0.16) {
16743
16918
  return palette.highlight;
@@ -16777,6 +16952,1384 @@
16777
16952
  context.restore();
16778
16953
  }
16779
16954
 
16955
+ /* eslint-disable no-magic-numbers */
16956
+ /**
16957
+ * Light direction used by the continuous octopus mesh shading.
16958
+ *
16959
+ * @private helper of `octopus3d3AvatarVisual`
16960
+ */
16961
+ const LIGHT_DIRECTION$1 = normalizeVector3({
16962
+ x: 0.34,
16963
+ y: -0.62,
16964
+ z: 1,
16965
+ });
16966
+ /**
16967
+ * Real-octopus tentacle count used by the continuous lower mesh.
16968
+ *
16969
+ * @private helper of `octopus3d3AvatarVisual`
16970
+ */
16971
+ const OCTOPUS_TENTACLE_COUNT$1 = 8;
16972
+ /**
16973
+ * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
16974
+ * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
16975
+ * React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
16976
+ *
16977
+ * @private helper of `octopus3d3AvatarVisual`
16978
+ */
16979
+ const stableStateCache = new WeakMap();
16980
+ /**
16981
+ * Returns the stable per-avatar state, computing it on first access and returning the cached
16982
+ * result on every subsequent call within the same `<Avatar/>` mount.
16983
+ *
16984
+ * @private helper of `octopus3d3AvatarVisual`
16985
+ */
16986
+ function getOctopus3d3StableState(createRandom) {
16987
+ const cached = stableStateCache.get(createRandom);
16988
+ if (cached !== undefined) {
16989
+ return cached;
16990
+ }
16991
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
16992
+ const animationRandom = createRandom('octopus3d3-animation-profile');
16993
+ const eyeRandom = createRandom('octopus3d3-eye-profile');
16994
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
16995
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
16996
+ const state = {
16997
+ morphologyProfile,
16998
+ animationPhase: animationRandom() * Math.PI * 2,
16999
+ leftEyePhaseOffset,
17000
+ rightEyePhaseOffset,
17001
+ tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
17002
+ };
17003
+ stableStateCache.set(createRandom, state);
17004
+ return state;
17005
+ }
17006
+ /**
17007
+ * Octopus 3D 3 avatar visual.
17008
+ *
17009
+ * @private built-in avatar visual
17010
+ */
17011
+ const octopus3d3AvatarVisual = {
17012
+ id: 'octopus3d3',
17013
+ title: 'Octopus 3D 3',
17014
+ description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
17015
+ isAnimated: true,
17016
+ supportsPointerTracking: true,
17017
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
17018
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
17019
+ const sceneCenterX = size * 0.5;
17020
+ const sceneCenterY = size * 0.535;
17021
+ const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
17022
+ const meshCenter = {
17023
+ x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
17024
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
17025
+ z: interaction.intensity * size * 0.018,
17026
+ };
17027
+ const rotationY = -0.1 +
17028
+ Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
17029
+ interaction.bodyOffsetX * 0.22 +
17030
+ interaction.gazeX * 0.88;
17031
+ const rotationX = -0.07 +
17032
+ Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
17033
+ interaction.bodyOffsetY * 0.08 -
17034
+ interaction.gazeY * 0.38;
17035
+ const surfaceOptions = {
17036
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
17037
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
17038
+ radiusZ: size *
17039
+ morphologyProfile.body.bodyRadiusRatio *
17040
+ (1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
17041
+ morphologyProfile,
17042
+ timeMs,
17043
+ animationPhase,
17044
+ tentacleProfiles,
17045
+ };
17046
+ const surfacePatches = resolveVisibleContinuousOctopusPatches({
17047
+ ...surfaceOptions,
17048
+ center: meshCenter,
17049
+ rotationX,
17050
+ rotationY,
17051
+ sceneCenterX,
17052
+ sceneCenterY,
17053
+ size,
17054
+ palette,
17055
+ });
17056
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
17057
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
17058
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
17059
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
17060
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
17061
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
17062
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
17063
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
17064
+ drawAvatarFrame(context, size, palette);
17065
+ drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
17066
+ drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
17067
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
17068
+ drawContinuousSurfacePatch(context, surfacePatch);
17069
+ }
17070
+ drawProjectedSurfaceCurrents({
17071
+ context,
17072
+ surfaceOptions,
17073
+ center: meshCenter,
17074
+ rotationX,
17075
+ rotationY,
17076
+ sceneCenterX,
17077
+ sceneCenterY,
17078
+ size,
17079
+ palette,
17080
+ morphologyProfile,
17081
+ timeMs,
17082
+ animationPhase,
17083
+ });
17084
+ drawProjectedTentacleSuckers({
17085
+ context,
17086
+ surfaceOptions,
17087
+ center: meshCenter,
17088
+ rotationX,
17089
+ rotationY,
17090
+ sceneCenterX,
17091
+ sceneCenterY,
17092
+ size,
17093
+ palette,
17094
+ });
17095
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17096
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17097
+ drawProjectedOrganicMouth(context, [
17098
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
17099
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
17100
+ sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
17101
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
17102
+ },
17103
+ };
17104
+ /**
17105
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
17106
+ *
17107
+ * @private helper of `octopus3d3AvatarVisual`
17108
+ */
17109
+ function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
17110
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
17111
+ const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
17112
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
17113
+ return {
17114
+ centerLongitude: -Math.PI * 0.86 +
17115
+ progress * Math.PI * 1.72 +
17116
+ (tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
17117
+ widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
17118
+ lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
17119
+ swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
17120
+ depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
17121
+ phase: tentacleRandom() * Math.PI * 2,
17122
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
17123
+ };
17124
+ });
17125
+ }
17126
+ /**
17127
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
17128
+ *
17129
+ * @private helper of `octopus3d3AvatarVisual`
17130
+ */
17131
+ function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
17132
+ const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.11, sceneCenterY - size * 0.17 + interaction.gazeY * size * 0.05, size * 0.04, sceneCenterX, sceneCenterY, size * (0.66 + interaction.intensity * 0.02));
17133
+ glowGradient.addColorStop(0, `${palette.highlight}66`);
17134
+ glowGradient.addColorStop(0.34, `${palette.accent}2e`);
17135
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
17136
+ context.fillStyle = glowGradient;
17137
+ context.fillRect(0, 0, size, size);
17138
+ const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs / 1550) * size * 0.05, sceneCenterY + size * 0.29, size * 0.06, sceneCenterX, sceneCenterY + size * 0.3, size * 0.54);
17139
+ lowerGradient.addColorStop(0, `${palette.secondary}25`);
17140
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
17141
+ context.fillStyle = lowerGradient;
17142
+ context.fillRect(0, 0, size, size);
17143
+ }
17144
+ /**
17145
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
17146
+ *
17147
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
17148
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
17149
+ *
17150
+ * @private helper of `octopus3d3AvatarVisual`
17151
+ */
17152
+ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
17153
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
17154
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
17155
+ const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
17156
+ const ry = size * 0.06;
17157
+ // Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
17158
+ // then fill with a radial gradient that approximates the blurry edge without context.filter.
17159
+ context.save();
17160
+ context.translate(cx, cy);
17161
+ context.scale(1, ry / rx);
17162
+ const blurRadius = rx * 1.4;
17163
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
17164
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
17165
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
17166
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
17167
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
17168
+ context.fillStyle = shadowGradient;
17169
+ context.beginPath();
17170
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
17171
+ context.fill();
17172
+ context.restore();
17173
+ }
17174
+ /**
17175
+ * Number of latitude segments used by the continuous Octopus 3D 3 mesh.
17176
+ *
17177
+ * @private helper of `octopus3d3AvatarVisual`
17178
+ */
17179
+ const LATITUDE_PATCH_COUNT$1 = 16;
17180
+ /**
17181
+ * Number of longitude segments used by the continuous Octopus 3D 3 mesh.
17182
+ *
17183
+ * @private helper of `octopus3d3AvatarVisual`
17184
+ */
17185
+ const LONGITUDE_PATCH_COUNT$1 = 40;
17186
+ /**
17187
+ * Resolves visible projected patches for the continuous octopus mesh.
17188
+ *
17189
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
17190
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
17191
+ * than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
17192
+ * for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
17193
+ * triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
17194
+ *
17195
+ * @private helper of `octopus3d3AvatarVisual`
17196
+ */
17197
+ function resolveVisibleContinuousOctopusPatches(options) {
17198
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
17199
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
17200
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
17201
+ const surfacePatches = [];
17202
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
17203
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
17204
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
17205
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
17206
+ }
17207
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
17208
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
17209
+ }
17210
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
17211
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
17212
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
17213
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
17214
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
17215
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
17216
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
17217
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
17218
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
17219
+ }
17220
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
17221
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
17222
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
17223
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
17224
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
17225
+ }
17226
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
17227
+ const transformedCornerSamples = new Array(cornerCount);
17228
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
17229
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
17230
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
17231
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
17232
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
17233
+ const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
17234
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
17235
+ }
17236
+ }
17237
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
17238
+ const startLatitude = latitudeBoundaries[latitudeIndex];
17239
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
17240
+ const centerLatitude = (startLatitude + endLatitude) / 2;
17241
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
17242
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
17243
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
17244
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
17245
+ const transformedCorners = [
17246
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
17247
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
17248
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
17249
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
17250
+ ];
17251
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
17252
+ if (surfaceNormal.z <= 0.008) {
17253
+ continue;
17254
+ }
17255
+ const projectedCorners = [
17256
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
17257
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
17258
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
17259
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
17260
+ ];
17261
+ surfacePatches.push({
17262
+ corners: projectedCorners,
17263
+ averageDepth: (transformedCorners[0].z +
17264
+ transformedCorners[1].z +
17265
+ transformedCorners[2].z +
17266
+ transformedCorners[3].z) /
17267
+ 4,
17268
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
17269
+ fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
17270
+ outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
17271
+ });
17272
+ }
17273
+ }
17274
+ return surfacePatches;
17275
+ }
17276
+ /**
17277
+ * Samples one point on the continuous Octopus 3D 3 surface.
17278
+ *
17279
+ * The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
17280
+ * tentacled while still being rendered as one connected blobby mesh.
17281
+ *
17282
+ * @private helper of `octopus3d3AvatarVisual`
17283
+ */
17284
+ function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
17285
+ return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
17286
+ }
17287
+ /**
17288
+ * Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
17289
+ * values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
17290
+ *
17291
+ * The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
17292
+ * corner grid, so the same longitude is reused across every latitude row and each
17293
+ * tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
17294
+ *
17295
+ * @private helper of `octopus3d3AvatarVisual`
17296
+ */
17297
+ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
17298
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
17299
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
17300
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
17301
+ const upperBlend = Math.pow(1 - verticalProgress, 1.28);
17302
+ const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
17303
+ const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
17304
+ const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
17305
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
17306
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
17307
+ animationPhase * 0.6 +
17308
+ timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
17309
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
17310
+ (0.3 + lowerBlend * 0.7);
17311
+ const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
17312
+ lowerBlend *
17313
+ tentacleInfluence.core *
17314
+ tentacleInfluence.swayScale;
17315
+ const horizontalScale = 1.04 +
17316
+ mantleRipple +
17317
+ lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
17318
+ lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
17319
+ upperBlend * 0.08;
17320
+ const depthScale = 1.06 +
17321
+ upperBlend * 0.16 +
17322
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
17323
+ lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
17324
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
17325
+ const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
17326
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
17327
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
17328
+ const lowerDrop = lowerBlend *
17329
+ radiusY *
17330
+ (0.18 +
17331
+ tentacleInfluence.core *
17332
+ (0.38 +
17333
+ tentacleInfluence.lengthScale * 0.22 +
17334
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
17335
+ return {
17336
+ x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
17337
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
17338
+ upperBlend * radiusY * 0.1 +
17339
+ lowerDrop +
17340
+ Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
17341
+ Math.cos(timeMs / 880 + tentacleInfluence.phase) *
17342
+ lowerBlend *
17343
+ tipBlend *
17344
+ tentacleInfluence.core *
17345
+ radiusY *
17346
+ 0.034,
17347
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
17348
+ Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
17349
+ lowerBlend *
17350
+ tentacleInfluence.core *
17351
+ radiusZ *
17352
+ 0.04,
17353
+ };
17354
+ }
17355
+ /**
17356
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
17357
+ *
17358
+ * @private helper of `octopus3d3AvatarVisual`
17359
+ */
17360
+ function resolveContinuousTentacleInfluence(options, longitude) {
17361
+ let totalWeight = 0;
17362
+ let weightedSin = 0;
17363
+ let weightedCos = 0;
17364
+ let weightedWidthScale = 0;
17365
+ let weightedLengthScale = 0;
17366
+ let weightedSwayScale = 0;
17367
+ let weightedDepthScale = 0;
17368
+ let weightedPhase = 0;
17369
+ for (const tentacleProfile of options.tentacleProfiles) {
17370
+ const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
17371
+ const width = 0.2 * tentacleProfile.widthScale;
17372
+ const weight = Math.exp(-(distance * distance) / (width * width));
17373
+ totalWeight += weight;
17374
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
17375
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
17376
+ weightedWidthScale += tentacleProfile.widthScale * weight;
17377
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
17378
+ weightedSwayScale += tentacleProfile.swayScale * weight;
17379
+ weightedDepthScale += tentacleProfile.depthScale * weight;
17380
+ weightedPhase += tentacleProfile.phase * weight;
17381
+ }
17382
+ if (totalWeight < 0.0001) {
17383
+ return {
17384
+ core: 0,
17385
+ centerLongitude: longitude,
17386
+ widthScale: 1,
17387
+ lengthScale: 1,
17388
+ swayScale: 1,
17389
+ depthScale: 1,
17390
+ phase: 0,
17391
+ };
17392
+ }
17393
+ return {
17394
+ core: clampNumber$1(totalWeight, 0, 1),
17395
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
17396
+ widthScale: weightedWidthScale / totalWeight,
17397
+ lengthScale: weightedLengthScale / totalWeight,
17398
+ swayScale: weightedSwayScale / totalWeight,
17399
+ depthScale: weightedDepthScale / totalWeight,
17400
+ phase: weightedPhase / totalWeight,
17401
+ };
17402
+ }
17403
+ /**
17404
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
17405
+ *
17406
+ * @private helper of `octopus3d3AvatarVisual`
17407
+ */
17408
+ function resolveContinuousLobeWave(options, longitude) {
17409
+ const { morphologyProfile, animationPhase, timeMs } = options;
17410
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
17411
+ animationPhase +
17412
+ timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
17413
+ 1) /
17414
+ 2);
17415
+ }
17416
+ /**
17417
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
17418
+ *
17419
+ * @private helper of `octopus3d3AvatarVisual`
17420
+ */
17421
+ function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
17422
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
17423
+ if (tonalProgress < 0.14) {
17424
+ return palette.highlight;
17425
+ }
17426
+ if (tonalProgress < 0.32) {
17427
+ return palette.secondary;
17428
+ }
17429
+ if (tonalProgress < 0.72) {
17430
+ return forwardness > 0.55 ? palette.secondary : palette.primary;
17431
+ }
17432
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
17433
+ }
17434
+ /**
17435
+ * Draws one projected mesh patch with soft shading and a subtle edge.
17436
+ *
17437
+ * @private helper of `octopus3d3AvatarVisual`
17438
+ */
17439
+ function drawContinuousSurfacePatch(context, surfacePatch) {
17440
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
17441
+ if (surfacePatch.lightIntensity > 0) {
17442
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
17443
+ }
17444
+ else if (surfacePatch.lightIntensity < 0) {
17445
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
17446
+ }
17447
+ context.save();
17448
+ context.beginPath();
17449
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
17450
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
17451
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
17452
+ }
17453
+ context.closePath();
17454
+ context.strokeStyle = surfacePatch.outlineColor;
17455
+ context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
17456
+ context.lineJoin = 'round';
17457
+ context.stroke();
17458
+ context.restore();
17459
+ }
17460
+ /**
17461
+ * Draws projected mantle-current lines on the front of the mesh.
17462
+ *
17463
+ * @private helper of `octopus3d3AvatarVisual`
17464
+ */
17465
+ function drawProjectedSurfaceCurrents(options) {
17466
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
17467
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
17468
+ const centerIndex = (currentCount - 1) / 2;
17469
+ context.save();
17470
+ context.lineCap = 'round';
17471
+ context.lineJoin = 'round';
17472
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
17473
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
17474
+ const projectedPoints = [];
17475
+ for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
17476
+ const progress = sampleIndex / 7;
17477
+ const latitude = -0.46 + progress * 0.74;
17478
+ const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
17479
+ const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
17480
+ if (scenePoint.z > center.z - size * 0.016) {
17481
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
17482
+ }
17483
+ }
17484
+ if (projectedPoints.length < 3) {
17485
+ continue;
17486
+ }
17487
+ context.beginPath();
17488
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
17489
+ for (const projectedPoint of projectedPoints.slice(1)) {
17490
+ context.lineTo(projectedPoint.x, projectedPoint.y);
17491
+ }
17492
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
17493
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
17494
+ context.stroke();
17495
+ }
17496
+ context.restore();
17497
+ }
17498
+ /**
17499
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
17500
+ *
17501
+ * @private helper of `octopus3d3AvatarVisual`
17502
+ */
17503
+ function drawProjectedTentacleSuckers(options) {
17504
+ const { surfaceOptions, size } = options;
17505
+ const { timeMs } = surfaceOptions;
17506
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
17507
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
17508
+ continue;
17509
+ }
17510
+ for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
17511
+ const latitude = 0.52 + suckerIndex * 0.14;
17512
+ const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
17513
+ const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
17514
+ drawProjectedSurfaceSpot({
17515
+ ...options,
17516
+ latitude,
17517
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
17518
+ radiusScale: size * (0.0065 - suckerIndex * 0.0007),
17519
+ });
17520
+ }
17521
+ }
17522
+ }
17523
+ /**
17524
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
17525
+ *
17526
+ * @private helper of `octopus3d3AvatarVisual`
17527
+ */
17528
+ function drawProjectedSurfaceSpot(options) {
17529
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
17530
+ const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
17531
+ const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
17532
+ const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
17533
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
17534
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
17535
+ return;
17536
+ }
17537
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
17538
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
17539
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
17540
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
17541
+ radiusScale *
17542
+ 0.74, size * 0.003, size * 0.018);
17543
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
17544
+ radiusScale *
17545
+ 0.52, size * 0.0024, size * 0.014);
17546
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
17547
+ context.save();
17548
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
17549
+ context.rotate(rotation);
17550
+ context.beginPath();
17551
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
17552
+ context.fillStyle = `${palette.highlight}73`;
17553
+ context.fill();
17554
+ context.strokeStyle = `${palette.highlight}99`;
17555
+ context.lineWidth = Math.max(0.7, size * 0.0028);
17556
+ context.stroke();
17557
+ context.restore();
17558
+ }
17559
+ /**
17560
+ * Resolves a signed angular distance from the source longitude to the target longitude.
17561
+ *
17562
+ * @private helper of `octopus3d3AvatarVisual`
17563
+ */
17564
+ function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
17565
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
17566
+ }
17567
+ /**
17568
+ * Smoothly maps a value between two bounds into `[0, 1]`.
17569
+ *
17570
+ * @private helper of `octopus3d3AvatarVisual`
17571
+ */
17572
+ function smoothStep$1(edgeStart, edgeEnd, value) {
17573
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
17574
+ return progress * progress * (3 - 2 * progress);
17575
+ }
17576
+
17577
+ /* eslint-disable no-magic-numbers */
17578
+ /**
17579
+ * Light direction used by the continuous Octopus 3D 4 mesh shading.
17580
+ *
17581
+ * @private helper of `octopus3d4AvatarVisual`
17582
+ */
17583
+ const LIGHT_DIRECTION = normalizeVector3({
17584
+ x: 0.32,
17585
+ y: -0.66,
17586
+ z: 1,
17587
+ });
17588
+ /**
17589
+ * Rim-light direction used to brighten the side of the silhouette.
17590
+ *
17591
+ * @private helper of `octopus3d4AvatarVisual`
17592
+ */
17593
+ const RIM_LIGHT_DIRECTION = normalizeVector3({
17594
+ x: -0.7,
17595
+ y: -0.18,
17596
+ z: 0.55,
17597
+ });
17598
+ /**
17599
+ * Real-octopus tentacle count used by the continuous lower mesh.
17600
+ *
17601
+ * @private helper of `octopus3d4AvatarVisual`
17602
+ */
17603
+ const OCTOPUS_TENTACLE_COUNT = 8;
17604
+ /**
17605
+ * Number of seeded skin spots painted across the octopus body.
17606
+ *
17607
+ * @private helper of `octopus3d4AvatarVisual`
17608
+ */
17609
+ const SKIN_SPOT_COUNT = 14;
17610
+ /**
17611
+ * Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
17612
+ *
17613
+ * @private helper of `octopus3d4AvatarVisual`
17614
+ */
17615
+ const octopus3d4StableStateCache = new WeakMap();
17616
+ /**
17617
+ * Returns the stable per-avatar state, computing it on first access and caching afterwards.
17618
+ *
17619
+ * @private helper of `octopus3d4AvatarVisual`
17620
+ */
17621
+ function getOctopus3d4StableState(createRandom) {
17622
+ const cached = octopus3d4StableStateCache.get(createRandom);
17623
+ if (cached !== undefined) {
17624
+ return cached;
17625
+ }
17626
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
17627
+ const animationRandom = createRandom('octopus3d4-animation-profile');
17628
+ const eyeRandom = createRandom('octopus3d4-eye-profile');
17629
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
17630
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
17631
+ const state = {
17632
+ morphologyProfile,
17633
+ animationPhase: animationRandom() * Math.PI * 2,
17634
+ leftEyePhaseOffset,
17635
+ rightEyePhaseOffset,
17636
+ tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
17637
+ skinSpots: createBlobbySkinSpots(createRandom),
17638
+ };
17639
+ octopus3d4StableStateCache.set(createRandom, state);
17640
+ return state;
17641
+ }
17642
+ /**
17643
+ * Octopus 3D 4 avatar visual.
17644
+ *
17645
+ * @private built-in avatar visual
17646
+ */
17647
+ const octopus3d4AvatarVisual = {
17648
+ id: 'octopus3d4',
17649
+ title: 'Octopus 3D 4',
17650
+ description: 'Cute blobby 3D octopus rendered as one smooth continuous mesh with naturally undulating tentacles, rich multi-pass shading, pigment spots, and cursor-aware head and eyes.',
17651
+ isAnimated: true,
17652
+ supportsPointerTracking: true,
17653
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
17654
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
17655
+ const sceneCenterX = size * 0.5;
17656
+ const sceneCenterY = size * 0.535;
17657
+ const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
17658
+ const meshCenter = {
17659
+ x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
17660
+ y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
17661
+ z: interaction.intensity * size * 0.02,
17662
+ };
17663
+ const rotationY = -0.08 +
17664
+ Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
17665
+ interaction.bodyOffsetX * 0.24 +
17666
+ interaction.gazeX * 0.98;
17667
+ const rotationX = -0.07 +
17668
+ Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
17669
+ interaction.bodyOffsetY * 0.08 -
17670
+ interaction.gazeY * 0.42;
17671
+ const surfaceOptions = {
17672
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
17673
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
17674
+ radiusZ: size *
17675
+ morphologyProfile.body.bodyRadiusRatio *
17676
+ (1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
17677
+ morphologyProfile,
17678
+ timeMs,
17679
+ animationPhase,
17680
+ tentacleProfiles,
17681
+ };
17682
+ const surfacePatches = resolveVisibleBlobbyContinuousPatches({
17683
+ ...surfaceOptions,
17684
+ center: meshCenter,
17685
+ rotationX,
17686
+ rotationY,
17687
+ sceneCenterX,
17688
+ sceneCenterY,
17689
+ size,
17690
+ palette,
17691
+ });
17692
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
17693
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
17694
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
17695
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
17696
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
17697
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
17698
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
17699
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
17700
+ drawAvatarFrame(context, size, palette);
17701
+ drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
17702
+ drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
17703
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
17704
+ drawBlobbyContinuousSurfacePatch(context, surfacePatch);
17705
+ }
17706
+ drawBlobbySkinSpots({
17707
+ context,
17708
+ surfaceOptions,
17709
+ center: meshCenter,
17710
+ rotationX,
17711
+ rotationY,
17712
+ sceneCenterX,
17713
+ sceneCenterY,
17714
+ size,
17715
+ palette,
17716
+ skinSpots,
17717
+ });
17718
+ drawBlobbyContinuousCurrents({
17719
+ context,
17720
+ surfaceOptions,
17721
+ center: meshCenter,
17722
+ rotationX,
17723
+ rotationY,
17724
+ sceneCenterX,
17725
+ sceneCenterY,
17726
+ size,
17727
+ palette,
17728
+ morphologyProfile,
17729
+ timeMs,
17730
+ animationPhase,
17731
+ });
17732
+ drawBlobbyContinuousSuckers({
17733
+ context,
17734
+ surfaceOptions,
17735
+ center: meshCenter,
17736
+ rotationX,
17737
+ rotationY,
17738
+ sceneCenterX,
17739
+ sceneCenterY,
17740
+ size,
17741
+ palette,
17742
+ });
17743
+ drawBlobbyContinuousGloss({
17744
+ context,
17745
+ surfaceOptions,
17746
+ center: meshCenter,
17747
+ rotationX,
17748
+ rotationY,
17749
+ sceneCenterX,
17750
+ sceneCenterY,
17751
+ size,
17752
+ palette,
17753
+ });
17754
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17755
+ drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
17756
+ drawProjectedOrganicMouth(context, [
17757
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
17758
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
17759
+ sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
17760
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
17761
+ },
17762
+ };
17763
+ /**
17764
+ * Creates seeded tentacle-lobe profiles around the visible lower octopus body.
17765
+ *
17766
+ * @private helper of `octopus3d4AvatarVisual`
17767
+ */
17768
+ function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
17769
+ return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
17770
+ const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
17771
+ const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
17772
+ return {
17773
+ centerLongitude: -Math.PI * 0.9 +
17774
+ progress * Math.PI * 1.8 +
17775
+ (tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
17776
+ widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
17777
+ lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
17778
+ swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
17779
+ depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
17780
+ curlScale: 0.62 + tentacleRandom() * 0.46,
17781
+ primaryPhase: tentacleRandom() * Math.PI * 2,
17782
+ secondaryPhase: tentacleRandom() * Math.PI * 2,
17783
+ suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
17784
+ };
17785
+ });
17786
+ }
17787
+ /**
17788
+ * Creates seeded skin pigment spots distributed across the upper octopus mesh.
17789
+ *
17790
+ * @private helper of `octopus3d4AvatarVisual`
17791
+ */
17792
+ function createBlobbySkinSpots(createRandom) {
17793
+ const spotRandom = createRandom('octopus3d4-skin-spots');
17794
+ return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
17795
+ latitude: -0.45 + spotRandom() * 0.7,
17796
+ longitude: -0.5 + spotRandom() * 1.0,
17797
+ radiusScale: 0.0028 + spotRandom() * 0.0052,
17798
+ opacity: 0.16 + spotRandom() * 0.22,
17799
+ }));
17800
+ }
17801
+ /**
17802
+ * Draws the soft underwater atmosphere behind the continuous octopus mesh.
17803
+ *
17804
+ * @private helper of `octopus3d4AvatarVisual`
17805
+ */
17806
+ function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
17807
+ const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.12, sceneCenterY - size * 0.18 + interaction.gazeY * size * 0.05, size * 0.04, sceneCenterX, sceneCenterY, size * (0.68 + interaction.intensity * 0.025));
17808
+ glowGradient.addColorStop(0, `${palette.highlight}74`);
17809
+ glowGradient.addColorStop(0.32, `${palette.accent}32`);
17810
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
17811
+ context.fillStyle = glowGradient;
17812
+ context.fillRect(0, 0, size, size);
17813
+ const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs / 1500) * size * 0.05, sceneCenterY + size * 0.3, size * 0.06, sceneCenterX, sceneCenterY + size * 0.3, size * 0.56);
17814
+ lowerGradient.addColorStop(0, `${palette.secondary}28`);
17815
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
17816
+ context.fillStyle = lowerGradient;
17817
+ context.fillRect(0, 0, size, size);
17818
+ }
17819
+ /**
17820
+ * Draws the soft lower shadow that anchors the octopus in the avatar frame.
17821
+ *
17822
+ * @private helper of `octopus3d4AvatarVisual`
17823
+ */
17824
+ function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
17825
+ const cx = size * 0.5 + interaction.gazeX * size * 0.046;
17826
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
17827
+ const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
17828
+ const ry = size * 0.062;
17829
+ context.save();
17830
+ context.translate(cx, cy);
17831
+ context.scale(1, ry / rx);
17832
+ const blurRadius = rx * 1.42;
17833
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
17834
+ shadowGradient.addColorStop(0, `${palette.shadow}82`);
17835
+ shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
17836
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
17837
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
17838
+ context.fillStyle = shadowGradient;
17839
+ context.beginPath();
17840
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
17841
+ context.fill();
17842
+ context.restore();
17843
+ }
17844
+ /**
17845
+ * Number of latitude segments used by the continuous Octopus 3D 4 mesh.
17846
+ *
17847
+ * @private helper of `octopus3d4AvatarVisual`
17848
+ */
17849
+ const LATITUDE_PATCH_COUNT = 20;
17850
+ /**
17851
+ * Number of longitude segments used by the continuous Octopus 3D 4 mesh.
17852
+ *
17853
+ * @private helper of `octopus3d4AvatarVisual`
17854
+ */
17855
+ const LONGITUDE_PATCH_COUNT = 48;
17856
+ /**
17857
+ * Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
17858
+ *
17859
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
17860
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
17861
+ * than re-evaluated for every patch corner.
17862
+ *
17863
+ * @private helper of `octopus3d4AvatarVisual`
17864
+ */
17865
+ function resolveVisibleBlobbyContinuousPatches(options) {
17866
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
17867
+ const latitudePatchCount = LATITUDE_PATCH_COUNT;
17868
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT;
17869
+ const surfacePatches = [];
17870
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
17871
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
17872
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
17873
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
17874
+ }
17875
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
17876
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
17877
+ }
17878
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
17879
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
17880
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
17881
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
17882
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
17883
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
17884
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
17885
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
17886
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
17887
+ }
17888
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
17889
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
17890
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
17891
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
17892
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
17893
+ }
17894
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
17895
+ const transformedCornerSamples = new Array(cornerCount);
17896
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
17897
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
17898
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
17899
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
17900
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
17901
+ const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
17902
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
17903
+ }
17904
+ }
17905
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
17906
+ const startLatitude = latitudeBoundaries[latitudeIndex];
17907
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
17908
+ const centerLatitude = (startLatitude + endLatitude) / 2;
17909
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
17910
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
17911
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
17912
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
17913
+ const transformedCorners = [
17914
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
17915
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
17916
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
17917
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
17918
+ ];
17919
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
17920
+ if (surfaceNormal.z <= 0.006) {
17921
+ continue;
17922
+ }
17923
+ const projectedCorners = [
17924
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
17925
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
17926
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
17927
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
17928
+ ];
17929
+ const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
17930
+ const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
17931
+ surfacePatches.push({
17932
+ corners: projectedCorners,
17933
+ averageDepth: (transformedCorners[0].z +
17934
+ transformedCorners[1].z +
17935
+ transformedCorners[2].z +
17936
+ transformedCorners[3].z) /
17937
+ 4,
17938
+ lightIntensity,
17939
+ rimLightIntensity,
17940
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
17941
+ outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
17942
+ });
17943
+ }
17944
+ }
17945
+ return surfacePatches;
17946
+ }
17947
+ /**
17948
+ * Samples one point on the continuous Octopus 3D 4 surface.
17949
+ *
17950
+ * @private helper of `octopus3d4AvatarVisual`
17951
+ */
17952
+ function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
17953
+ return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
17954
+ }
17955
+ /**
17956
+ * Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
17957
+ *
17958
+ * The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
17959
+ * across every latitude row and each tentacle/lobe value is computed once per frame instead
17960
+ * of `latitudePatchCount * 4` times.
17961
+ *
17962
+ * @private helper of `octopus3d4AvatarVisual`
17963
+ */
17964
+ function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
17965
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
17966
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
17967
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
17968
+ const upperBlend = Math.pow(1 - verticalProgress, 1.32);
17969
+ const lowerBlend = smoothStep(0.34, 1, verticalProgress);
17970
+ const tipBlend = smoothStep(0.66, 1, verticalProgress);
17971
+ const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
17972
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
17973
+ const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
17974
+ animationPhase * 0.6 +
17975
+ timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
17976
+ (0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
17977
+ (0.32 + lowerBlend * 0.7);
17978
+ const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
17979
+ lowerBlend *
17980
+ tentacleInfluence.core *
17981
+ tentacleInfluence.swayScale;
17982
+ const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
17983
+ lowerBlend *
17984
+ tentacleInfluence.core *
17985
+ tentacleInfluence.swayScale *
17986
+ 0.42;
17987
+ const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
17988
+ tipBlend *
17989
+ tentacleInfluence.core *
17990
+ tentacleInfluence.curlScale *
17991
+ 0.36;
17992
+ const horizontalScale = 1.06 +
17993
+ mantleRipple +
17994
+ lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
17995
+ lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
17996
+ upperBlend * 0.08;
17997
+ const depthScale = 1.08 +
17998
+ upperBlend * 0.16 +
17999
+ Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
18000
+ lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
18001
+ Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
18002
+ const tentacleTubeRadius = lowerBlend *
18003
+ tentacleInfluence.core *
18004
+ (0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
18005
+ radiusX;
18006
+ const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
18007
+ const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
18008
+ const lowerDrop = lowerBlend *
18009
+ radiusY *
18010
+ (0.2 +
18011
+ tentacleInfluence.core *
18012
+ (0.42 +
18013
+ tentacleInfluence.lengthScale * 0.24 +
18014
+ (morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
18015
+ const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
18016
+ return {
18017
+ x: Math.sin(effectiveLongitude) * planarRadiusX +
18018
+ combinedTentacleSway +
18019
+ tentacleCurl * radiusX * 0.18,
18020
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
18021
+ upperBlend * radiusY * 0.12 +
18022
+ lowerDrop +
18023
+ Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
18024
+ Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
18025
+ lowerBlend *
18026
+ tipBlend *
18027
+ tentacleInfluence.core *
18028
+ radiusY *
18029
+ 0.04,
18030
+ z: Math.cos(effectiveLongitude) * planarRadiusZ +
18031
+ Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
18032
+ lowerBlend *
18033
+ tentacleInfluence.core *
18034
+ radiusZ *
18035
+ 0.044 +
18036
+ tentacleCurl * radiusZ * 0.14,
18037
+ };
18038
+ }
18039
+ /**
18040
+ * Blends nearby seeded tentacle profiles at one mesh longitude.
18041
+ *
18042
+ * @private helper of `octopus3d4AvatarVisual`
18043
+ */
18044
+ function resolveBlobbyTentacleInfluence(options, longitude) {
18045
+ let totalWeight = 0;
18046
+ let weightedSin = 0;
18047
+ let weightedCos = 0;
18048
+ let weightedWidthScale = 0;
18049
+ let weightedLengthScale = 0;
18050
+ let weightedSwayScale = 0;
18051
+ let weightedDepthScale = 0;
18052
+ let weightedCurlScale = 0;
18053
+ let weightedPrimaryPhase = 0;
18054
+ let weightedSecondaryPhase = 0;
18055
+ for (const tentacleProfile of options.tentacleProfiles) {
18056
+ const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
18057
+ const width = 0.22 * tentacleProfile.widthScale;
18058
+ const weight = Math.exp(-(distance * distance) / (width * width));
18059
+ totalWeight += weight;
18060
+ weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
18061
+ weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
18062
+ weightedWidthScale += tentacleProfile.widthScale * weight;
18063
+ weightedLengthScale += tentacleProfile.lengthScale * weight;
18064
+ weightedSwayScale += tentacleProfile.swayScale * weight;
18065
+ weightedDepthScale += tentacleProfile.depthScale * weight;
18066
+ weightedCurlScale += tentacleProfile.curlScale * weight;
18067
+ weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
18068
+ weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
18069
+ }
18070
+ if (totalWeight < 0.0001) {
18071
+ return {
18072
+ core: 0,
18073
+ centerLongitude: longitude,
18074
+ widthScale: 1,
18075
+ lengthScale: 1,
18076
+ swayScale: 1,
18077
+ depthScale: 1,
18078
+ curlScale: 1,
18079
+ primaryPhase: 0,
18080
+ secondaryPhase: 0,
18081
+ };
18082
+ }
18083
+ return {
18084
+ core: clampNumber$1(totalWeight, 0, 1),
18085
+ centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
18086
+ widthScale: weightedWidthScale / totalWeight,
18087
+ lengthScale: weightedLengthScale / totalWeight,
18088
+ swayScale: weightedSwayScale / totalWeight,
18089
+ depthScale: weightedDepthScale / totalWeight,
18090
+ curlScale: weightedCurlScale / totalWeight,
18091
+ primaryPhase: weightedPrimaryPhase / totalWeight,
18092
+ secondaryPhase: weightedSecondaryPhase / totalWeight,
18093
+ };
18094
+ }
18095
+ /**
18096
+ * Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
18097
+ *
18098
+ * @private helper of `octopus3d4AvatarVisual`
18099
+ */
18100
+ function resolveBlobbyLobeWave(options, longitude) {
18101
+ const { morphologyProfile, animationPhase, timeMs } = options;
18102
+ return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
18103
+ animationPhase +
18104
+ timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
18105
+ 1) /
18106
+ 2);
18107
+ }
18108
+ /**
18109
+ * Resolves one base fill tone for a patch on the continuous octopus mesh.
18110
+ *
18111
+ * @private helper of `octopus3d4AvatarVisual`
18112
+ */
18113
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
18114
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
18115
+ if (tonalProgress < 0.12) {
18116
+ return palette.highlight;
18117
+ }
18118
+ if (tonalProgress < 0.3) {
18119
+ return palette.secondary;
18120
+ }
18121
+ if (tonalProgress < 0.72) {
18122
+ return forwardness > 0.56 ? palette.secondary : palette.primary;
18123
+ }
18124
+ return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
18125
+ }
18126
+ /**
18127
+ * Draws one projected mesh patch with multi-pass shading and a soft edge.
18128
+ *
18129
+ * @private helper of `octopus3d4AvatarVisual`
18130
+ */
18131
+ function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
18132
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
18133
+ if (surfacePatch.lightIntensity > 0) {
18134
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
18135
+ }
18136
+ else if (surfacePatch.lightIntensity < 0) {
18137
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
18138
+ }
18139
+ if (surfacePatch.rimLightIntensity > 0.04) {
18140
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
18141
+ }
18142
+ context.save();
18143
+ context.beginPath();
18144
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
18145
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
18146
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
18147
+ }
18148
+ context.closePath();
18149
+ context.strokeStyle = surfacePatch.outlineColor;
18150
+ context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
18151
+ context.lineJoin = 'round';
18152
+ context.stroke();
18153
+ context.restore();
18154
+ }
18155
+ /**
18156
+ * Draws projected mantle-current lines on the front of the mesh.
18157
+ *
18158
+ * @private helper of `octopus3d4AvatarVisual`
18159
+ */
18160
+ function drawBlobbyContinuousCurrents(options) {
18161
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
18162
+ const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
18163
+ const centerIndex = (currentCount - 1) / 2;
18164
+ context.save();
18165
+ context.lineCap = 'round';
18166
+ context.lineJoin = 'round';
18167
+ for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
18168
+ const baseLongitude = (currentIndex - centerIndex) * 0.15;
18169
+ const projectedPoints = [];
18170
+ for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
18171
+ const progress = sampleIndex / 8;
18172
+ const latitude = -0.48 + progress * 0.78;
18173
+ const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
18174
+ const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
18175
+ if (scenePoint.z > center.z - size * 0.016) {
18176
+ projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
18177
+ }
18178
+ }
18179
+ if (projectedPoints.length < 3) {
18180
+ continue;
18181
+ }
18182
+ context.beginPath();
18183
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
18184
+ for (const projectedPoint of projectedPoints.slice(1)) {
18185
+ context.lineTo(projectedPoint.x, projectedPoint.y);
18186
+ }
18187
+ context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
18188
+ context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
18189
+ context.stroke();
18190
+ }
18191
+ context.restore();
18192
+ }
18193
+ /**
18194
+ * Draws small projected sucker highlights on the waving lower mesh lobes.
18195
+ *
18196
+ * @private helper of `octopus3d4AvatarVisual`
18197
+ */
18198
+ function drawBlobbyContinuousSuckers(options) {
18199
+ const { surfaceOptions, size } = options;
18200
+ const { timeMs } = surfaceOptions;
18201
+ for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
18202
+ if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
18203
+ continue;
18204
+ }
18205
+ for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
18206
+ const latitude = 0.5 + suckerIndex * 0.12;
18207
+ const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
18208
+ const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
18209
+ drawBlobbyContinuousSurfaceSpot({
18210
+ ...options,
18211
+ latitude,
18212
+ longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
18213
+ radiusScale: size * (0.0068 - suckerIndex * 0.0006),
18214
+ });
18215
+ }
18216
+ }
18217
+ }
18218
+ /**
18219
+ * Draws seeded pigment spots across the upper mesh for a richer skin texture.
18220
+ *
18221
+ * @private helper of `octopus3d4AvatarVisual`
18222
+ */
18223
+ function drawBlobbySkinSpots(options) {
18224
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
18225
+ for (const skinSpot of skinSpots) {
18226
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
18227
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
18228
+ if (sceneCenterPoint.z <= center.z - size * 0.01) {
18229
+ continue;
18230
+ }
18231
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
18232
+ const spotRadius = size * skinSpot.radiusScale;
18233
+ context.save();
18234
+ context.beginPath();
18235
+ context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
18236
+ context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
18237
+ context.fill();
18238
+ context.restore();
18239
+ }
18240
+ }
18241
+ /**
18242
+ * Draws one tiny projected surface spot by sampling local mesh tangents.
18243
+ *
18244
+ * @private helper of `octopus3d4AvatarVisual`
18245
+ */
18246
+ function drawBlobbyContinuousSurfaceSpot(options) {
18247
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
18248
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
18249
+ const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
18250
+ const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
18251
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
18252
+ if (sceneCenterPoint.z <= center.z - size * 0.012) {
18253
+ return;
18254
+ }
18255
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
18256
+ const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
18257
+ const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
18258
+ const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
18259
+ radiusScale *
18260
+ 0.78, size * 0.003, size * 0.02);
18261
+ const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
18262
+ radiusScale *
18263
+ 0.54, size * 0.0024, size * 0.015);
18264
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
18265
+ context.save();
18266
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
18267
+ context.rotate(rotation);
18268
+ context.beginPath();
18269
+ context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
18270
+ context.fillStyle = `${palette.highlight}80`;
18271
+ context.fill();
18272
+ context.strokeStyle = `${palette.highlight}a8`;
18273
+ context.lineWidth = Math.max(0.7, size * 0.0028);
18274
+ context.stroke();
18275
+ context.restore();
18276
+ }
18277
+ /**
18278
+ * Draws a soft, slowly drifting gloss highlight on the front of the mesh.
18279
+ *
18280
+ * @private helper of `octopus3d4AvatarVisual`
18281
+ */
18282
+ function drawBlobbyContinuousGloss(options) {
18283
+ const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
18284
+ const { timeMs, animationPhase } = surfaceOptions;
18285
+ const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
18286
+ const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
18287
+ const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
18288
+ const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
18289
+ if (sceneCenterPoint.z <= center.z) {
18290
+ return;
18291
+ }
18292
+ const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
18293
+ const glossRadius = size * 0.058;
18294
+ context.save();
18295
+ const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
18296
+ glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
18297
+ glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
18298
+ glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
18299
+ context.fillStyle = glossGradient;
18300
+ context.beginPath();
18301
+ context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
18302
+ context.fill();
18303
+ context.restore();
18304
+ }
18305
+ /**
18306
+ * Resolves a signed angular distance from the source longitude to the target longitude.
18307
+ *
18308
+ * @private helper of `octopus3d4AvatarVisual`
18309
+ */
18310
+ function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
18311
+ return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
18312
+ }
18313
+ /**
18314
+ * Smoothly maps a value between two bounds into `[0, 1]`.
18315
+ *
18316
+ * @private helper of `octopus3d4AvatarVisual`
18317
+ */
18318
+ function smoothStep(edgeStart, edgeEnd, value) {
18319
+ const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
18320
+ return progress * progress * (3 - 2 * progress);
18321
+ }
18322
+ /**
18323
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
18324
+ *
18325
+ * @private helper of `octopus3d4AvatarVisual`
18326
+ */
18327
+ function formatAlphaHex(opacity) {
18328
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
18329
+ .toString(16)
18330
+ .padStart(2, '0');
18331
+ }
18332
+
16780
18333
  /* eslint-disable no-magic-numbers */
16781
18334
  /**
16782
18335
  * Octopus avatar visual.
@@ -17547,6 +19100,8 @@
17547
19100
  octopus3AvatarVisual,
17548
19101
  octopus3dAvatarVisual,
17549
19102
  octopus3d2AvatarVisual,
19103
+ octopus3d3AvatarVisual,
19104
+ octopus3d4AvatarVisual,
17550
19105
  asciiOctopusAvatarVisual,
17551
19106
  minecraftAvatarVisual,
17552
19107
  minecraft2AvatarVisual,
@@ -18226,6 +19781,70 @@
18226
19781
  }
18227
19782
  // Note: [💞] Ignore a discrepancy between file name and entity name
18228
19783
 
19784
+ /**
19785
+ * META VISIBILITY commitment definition.
19786
+ *
19787
+ * The `META VISIBILITY` commitment stores whether an agent is public, private, or unlisted.
19788
+ * Agents Server mirrors this value into the database for efficient filtering, but the book
19789
+ * commitment remains the editable source of truth.
19790
+ *
19791
+ * @private Metadata-only commitment used by Agents Server.
19792
+ */
19793
+ class MetaVisibilityCommitmentDefinition extends BaseCommitmentDefinition {
19794
+ constructor() {
19795
+ super('META VISIBILITY');
19796
+ }
19797
+ /**
19798
+ * Short one-line description of META VISIBILITY.
19799
+ */
19800
+ get description() {
19801
+ return 'Set whether the agent is private, unlisted, or public.';
19802
+ }
19803
+ /**
19804
+ * Icon for this commitment.
19805
+ */
19806
+ get icon() {
19807
+ return '👁️';
19808
+ }
19809
+ /**
19810
+ * Markdown documentation for META VISIBILITY commitment.
19811
+ */
19812
+ get documentation() {
19813
+ return _spaceTrim.spaceTrim(`
19814
+ # META VISIBILITY
19815
+
19816
+ Sets the agent visibility used by Agents Server.
19817
+
19818
+ ## Allowed values
19819
+
19820
+ - \`PRIVATE\` - accessible only to signed-in users with access.
19821
+ - \`UNLISTED\` - accessible by direct link but hidden from public listings.
19822
+ - \`PUBLIC\` - visible in public listings and accessible by anyone.
19823
+
19824
+ ## Key aspects
19825
+
19826
+ - Does not modify the agent's behavior, system message, or tools.
19827
+ - Whitespace and letter case are normalized when persisted.
19828
+ - If multiple \`META VISIBILITY\` commitments are present, persistence keeps one normalized value.
19829
+ - Agents Server mirrors the value into the database for filtering, but the book is the source of truth.
19830
+
19831
+ ## Example
19832
+
19833
+ \`\`\`book
19834
+ Helpful Assistant
19835
+
19836
+ GOAL Be helpful and friendly.
19837
+ META VISIBILITY PUBLIC
19838
+ \`\`\`
19839
+ `);
19840
+ }
19841
+ applyToAgentModelRequirements(requirements, content) {
19842
+ // META VISIBILITY is metadata only and does not alter model requirements.
19843
+ return requirements;
19844
+ }
19845
+ }
19846
+ // Note: [💞] Ignore a discrepancy between file name and entity name
19847
+
18229
19848
  /**
18230
19849
  * META VOICE commitment definition
18231
19850
  *
@@ -19835,7 +21454,15 @@
19835
21454
  * Builds the teammate request text, optionally including context.
19836
21455
  */
19837
21456
  function buildTeammateRequest(message, context) {
19838
- return context ? `${message}\n\nContext:\n${context}` : message;
21457
+ if (!context) {
21458
+ return message;
21459
+ }
21460
+ return _spaceTrim.spaceTrim((block) => `
21461
+ ${block(message)}
21462
+
21463
+ Context:
21464
+ ${block(context)}
21465
+ `);
19839
21466
  }
19840
21467
  /**
19841
21468
  * Builds a minimal chat prompt for teammate calls.
@@ -23824,7 +25451,11 @@
23824
25451
  const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
23825
25452
  const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
23826
25453
  const contentToReturn = wasCharacterTruncated
23827
- ? `${lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS)}\n\n[...truncated...]`
25454
+ ? _spaceTrim.spaceTrim((block) => `
25455
+ ${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
25456
+
25457
+ [...truncated...]
25458
+ `)
23828
25459
  : lineRangedContent;
23829
25460
  const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
23830
25461
  if (wasCharacterTruncated) {
@@ -24600,6 +26231,40 @@
24600
26231
  }
24601
26232
  // Note: [💞] Ignore a discrepancy between file name and entity name
24602
26233
 
26234
+ /**
26235
+ * Supported visibility states for persisted agents.
26236
+ *
26237
+ * @public exported from `@promptbook/core`
26238
+ */
26239
+ const AGENT_VISIBILITY_VALUES = ['PRIVATE', 'UNLISTED', 'PUBLIC'];
26240
+ /**
26241
+ * Returns `true` when the value is one of supported visibility states.
26242
+ *
26243
+ * @param value - Raw value to validate.
26244
+ * @returns Whether the value is a valid `AgentVisibility`.
26245
+ *
26246
+ * @public exported from `@promptbook/core`
26247
+ */
26248
+ function isAgentVisibility(value) {
26249
+ return typeof value === 'string' && AGENT_VISIBILITY_VALUES.includes(value);
26250
+ }
26251
+ /**
26252
+ * Normalizes raw visibility text into a supported value.
26253
+ *
26254
+ * @param value - Raw visibility value.
26255
+ * @returns Normalized visibility, or `null` when invalid.
26256
+ *
26257
+ * @public exported from `@promptbook/core`
26258
+ */
26259
+ function normalizeAgentVisibility(value) {
26260
+ if (typeof value !== 'string') {
26261
+ return null;
26262
+ }
26263
+ const normalized = value.trim().toUpperCase();
26264
+ return isAgentVisibility(normalized) ? normalized : null;
26265
+ }
26266
+ // Note: [💞] Ignore a discrepancy between file name and entity name
26267
+
24603
26268
  /**
24604
26269
  * Maximum allowed source length for create-agent payloads.
24605
26270
  *
@@ -26900,6 +28565,7 @@
26900
28565
  new MetaDomainCommitmentDefinition(),
26901
28566
  new MetaDisclaimerCommitmentDefinition(),
26902
28567
  new MetaInputPlaceholderCommitmentDefinition(),
28568
+ new MetaVisibilityCommitmentDefinition(),
26903
28569
  new MetaCommitmentDefinition(),
26904
28570
  new MetaVoiceCommitmentDefinition(),
26905
28571
  new NoteCommitmentDefinition('NOTE'),
@@ -31942,6 +33608,7 @@
31942
33608
  'META COLOR': applyMetaColorContent,
31943
33609
  'META FONT': applyMetaFontContent,
31944
33610
  'META VOICE': applyMetaVoiceContent,
33611
+ 'META VISIBILITY': applyMetaVisibilityContent,
31945
33612
  };
31946
33613
  /**
31947
33614
  * Applies META-style commitments that mutate parsed profile metadata.
@@ -31971,6 +33638,10 @@
31971
33638
  applyMetaAvatarContent(state, metaValue);
31972
33639
  return;
31973
33640
  }
33641
+ if (metaTypeRaw.toUpperCase() === 'VISIBILITY') {
33642
+ applyMetaVisibilityContent(state, metaValue);
33643
+ return;
33644
+ }
31974
33645
  const metaType = normalizeTo_camelCase(metaTypeRaw);
31975
33646
  state.meta[metaType] = metaValue;
31976
33647
  }
@@ -32047,6 +33718,15 @@
32047
33718
  function applyMetaVoiceContent(state, content) {
32048
33719
  state.meta.voice = _spaceTrim.spaceTrim(content);
32049
33720
  }
33721
+ /**
33722
+ * Applies META VISIBILITY content into the normalized `meta.visibility` field.
33723
+ */
33724
+ function applyMetaVisibilityContent(state, content) {
33725
+ const visibility = normalizeAgentVisibility(content);
33726
+ if (visibility) {
33727
+ state.meta.visibility = visibility;
33728
+ }
33729
+ }
32050
33730
  /**
32051
33731
  * Normalizes the separator in the content
32052
33732
  *
@@ -32759,11 +34439,19 @@
32759
34439
  try {
32760
34440
  const json = JSON.parse(content);
32761
34441
  const formattedJson = JSON.stringify(json, null, 4);
32762
- return `\`\`\`json\n${formattedJson}\n\`\`\``;
34442
+ return _spaceTrim.spaceTrim((block) => `
34443
+ \`\`\`json
34444
+ ${block(formattedJson)}
34445
+ \`\`\`
34446
+ `);
32763
34447
  }
32764
34448
  catch (error) {
32765
34449
  // If JSON is invalid, still import it but maybe not as pretty JSON
32766
- return `\`\`\`json\n${content}\n\`\`\``;
34450
+ return _spaceTrim.spaceTrim((block) => `
34451
+ \`\`\`json
34452
+ ${block(content)}
34453
+ \`\`\`
34454
+ `);
32767
34455
  }
32768
34456
  },
32769
34457
  };
@@ -32787,7 +34475,11 @@
32787
34475
  import(content, mimeType) {
32788
34476
  const extension = mimeTypeToExtension(mimeType);
32789
34477
  const codeBlockType = extension || 'txt';
32790
- return `\`\`\`${codeBlockType}\n${content}\n\`\`\``;
34478
+ return _spaceTrim.spaceTrim((block) => `
34479
+ \`\`\`${codeBlockType}
34480
+ ${block(content)}
34481
+ \`\`\`
34482
+ `);
32791
34483
  },
32792
34484
  };
32793
34485
 
@@ -33034,11 +34726,19 @@
33034
34726
  const examples = [];
33035
34727
  const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
33036
34728
  if (initialMessage) {
33037
- examples.push(`**Agent:**\n${initialMessage}`);
34729
+ examples.push(_spaceTrim.spaceTrim((block) => `
34730
+ **Agent:**
34731
+ ${block(initialMessage)}
34732
+ `));
33038
34733
  }
33039
34734
  if (samples && samples.length > 0) {
33040
34735
  for (const sample of samples) {
33041
- examples.push(`**User:** ${sample.question}\n\n**Agent:**\n${sample.answer}`);
34736
+ examples.push(_spaceTrim.spaceTrim((block) => `
34737
+ **User:** ${block(String(sample.question))}
34738
+
34739
+ **Agent:**
34740
+ ${block(sample.answer)}
34741
+ `));
33042
34742
  }
33043
34743
  }
33044
34744
  return examples;
@@ -34360,7 +36060,9 @@
34360
36060
  if (!executionTools || !executionTools.script) {
34361
36061
  throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
34362
36062
  }
34363
- return Array.isArray(executionTools.script) ? executionTools.script : [executionTools.script];
36063
+ return Array.isArray(executionTools.script)
36064
+ ? executionTools.script
36065
+ : [executionTools.script];
34364
36066
  }
34365
36067
  /**
34366
36068
  * Resolves the assistant-visible AgentKit tool response while preserving structured tool result data.
@@ -37422,7 +39124,7 @@
37422
39124
  return { skippedReason: 'invalid_data_url' };
37423
39125
  }
37424
39126
  return {
37425
- file: new File([parsed.buffer], parsed.filename, {
39127
+ file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
37426
39128
  type: parsed.mimeType,
37427
39129
  }),
37428
39130
  sizeBytes: parsed.buffer.length,
@@ -39170,7 +40872,7 @@
39170
40872
  * Computes one stable hash from a JSON-serializable value.
39171
40873
  */
39172
40874
  function computeJsonHash$1(value) {
39173
- return cryptoJs.SHA256(JSON.stringify(value)).toString();
40875
+ return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
39174
40876
  }
39175
40877
  /**
39176
40878
  * Handles OpenAI AgentKit-backed executions for `AgentLlmExecutionTools`.
@@ -39328,7 +41030,7 @@
39328
41030
  * Computes one stable hash from a JSON-serializable value.
39329
41031
  */
39330
41032
  function computeJsonHash(value) {
39331
- return cryptoJs.SHA256(JSON.stringify(value)).toString();
41033
+ return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
39332
41034
  }
39333
41035
  /**
39334
41036
  * Removes assistant-managed requirements before the prompt is executed via OpenAI Assistants.
@@ -39664,7 +41366,11 @@
39664
41366
  };
39665
41367
  }
39666
41368
  return {
39667
- content: `${content.slice(0, Math.max(0, maxCharacters))}\n\n[...truncated...]`,
41369
+ content: _spaceTrim.spaceTrim((block) => `
41370
+ ${block(content.slice(0, Math.max(0, maxCharacters)))}
41371
+
41372
+ [...truncated...]
41373
+ `),
39668
41374
  isTruncated: true,
39669
41375
  };
39670
41376
  }
@@ -40130,7 +41836,7 @@
40130
41836
  * Returns a virtual model name representing the agent behavior.
40131
41837
  */
40132
41838
  get modelName() {
40133
- const hash = cryptoJs.SHA256(hexEncoder__default["default"].parse(this.options.agentSource)).toString( /* hex */);
41839
+ const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(this.options.agentSource)).toString( /* hex */);
40134
41840
  const agentId = hash.substring(0, 10);
40135
41841
  return (normalizeToKebabCase(this.title) + '-' + agentId);
40136
41842
  }